spare_link 0.1.0-beta.2
spare_link: ^0.1.0-beta.2 copied to clipboard
Flutter SDK for Spare's redirect-based open-banking payment flows. Provides bank selection, consent orchestration, crash-safe session recovery, and sandbox/production support for KSA, UAE, and BAH.
spare_link #
A Flutter SDK for Spare's redirect-based open-banking payment flows. Handles bank selection, consent orchestration, deep-link return, and crash-safe session recovery — so your app only needs to launch the flow and handle the result.
Features #
- Full-screen SDK-owned payment flow launched with a single
await - Bank selection and consent details screens
- Redirect handoff to the bank and return via deep link
- Crash-safe session recovery via
SpareLink.resumeIfNeeded() - Sandbox and production environments
- KSA, UAE, and BAH tenant support
- Sealed
PaymentResulttype for exhaustive handling - Swappable
SpareLink.delegatefor unit testing
Installation #
dependencies:
spare_link: ^0.1.0
Or via the command line:
flutter pub add spare_link
Platform setup #
Android #
Add an intent-filter in AndroidManifest.xml for your return URI scheme:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" android:host="payment-return" />
</intent-filter>
iOS #
Add your URL scheme to Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
Forward incoming URIs to the SDK in both cases:
SpareLink.handleRedirect(incomingUri);
Usage #
Start a payment flow #
Obtain a linkToken from your backend (via the Spare Link API), then launch the flow:
import 'package:spare_link/spare_link.dart';
final result = await SpareLink.start(
context,
config: SpareLinkConfig(
linkToken: linkToken, // obtained from your backend
environment: PaymentEnvironment.sandbox,
tenant: SpareApiTenant.uae,
),
callbacks: SpareLinkCallbacks(
onEvent: (SpareLinkEvent event) => debugPrint('event: ${event.name}'),
onSuccess: (String? exchangeCode, String? linkSessionId) {
// called immediately on success, before the flow closes
},
onExit: (PaymentResult result) => debugPrint('exit: ${result.status.name}'),
),
);
switch (result) {
case PaymentSuccess(:final exchangeCode):
// Exchange the code with your backend to confirm the payment
case PaymentCancelled():
// User tapped back or dismissed the flow
case PaymentFailed(:final errorCode):
// Show error to user; errorCode and message are available
case PaymentAbandoned():
// Flow closed without completing (e.g. app was backgrounded)
}
The redirect URLs (successRedirectUrl, failureRedirectUrl) are set server-side when your backend creates the link token — they are not part of SpareLinkConfig.
Handle deep-link return #
Forward every payment-return URI your platform delivers to:
SpareLink.handleRedirect(uri);
The recommended place is a WidgetsBindingObserver:
@override
Future<bool> didPushRouteInformation(RouteInformation routeInformation) async {
final uri = routeInformation.uri;
if (uri.scheme == 'myapp') {
SpareLink.handleRedirect(uri);
return true;
}
return super.didPushRouteInformation(routeInformation);
}
Crash-safe recovery on cold start #
Call this once during app initialisation (e.g. in initState of your root widget) to resume any payment session that was interrupted by a crash or forced quit:
await SpareLink.resumeIfNeeded(
navigator: navigatorKey.currentState,
config: SpareLinkResumeConfig(
environment: PaymentEnvironment.sandbox,
tenant: SpareApiTenant.uae,
),
callbacks: SpareLinkCallbacks(
onExit: (PaymentResult result) => debugPrint('recovered: ${result.status.name}'),
),
);
resumeIfNeeded returns null if there is no pending session to recover.
Using with go_router #
spare_link works with MaterialApp.router and go_router without any SDK changes.
- Call
SpareLink.start(...)from any widget context under your router. - Forward return deep links to
SpareLink.handleRedirect(uri). - Optionally call
SpareLink.resumeIfNeeded(...)on app startup.
Universal links (optional, recommended) #
Spare's hosted Link return page (POST /link/authorize) tries a universal link on the Spare return host first, then falls back to your custom scheme URL from successRedirectUrl / failureRedirectUrl.
After bank authorisation, the hosted page:
- Navigates to
https://<spare-return-host>/link/app-return?exchange_code=…&fallback=… - If the OS does not open your app, redirects to your custom scheme URL with the same
exchange_code
Merchant app setup #
| Platform | Custom scheme | Universal link |
|---|---|---|
| Android | intent-filter for your scheme |
Verified App Link intent-filter for https://<spare-return-host>/link/app-return |
| iOS | CFBundleURLSchemes in Info.plist |
Associated Domains: applinks:<spare-return-host> |
| Both | Forward scheme URIs to SpareLink.handleRedirect |
Forward https://…/link/app-return?… to SpareLink.handleRedirect |
Verification files are served by the Link service:
GET /.well-known/apple-app-site-associationGET /.well-known/assetlinks.json
Your app bundle ID / package name must be listed in Spare's link app-return config for verification to succeed.
Deep-link requirements summary #
| Platform | Action required |
|---|---|
| Android | Add intent-filter in AndroidManifest.xml |
| iOS | Add URL scheme to Info.plist |
| Both | Call SpareLink.handleRedirect(uri) in your URI handler |
Contributing #
Pull requests are welcome. The main branch requires:
- At least one approval from a code owner (other than the PR author)
- Passing CI (analyze, format, test)
- No direct pushes — all changes go through a PR
See CODEOWNERS for the approval team.
License #
BSD 3-Clause © Spare Technologies