spare_link 0.1.0-beta.3 copy "spare_link: ^0.1.0-beta.3" to clipboard
spare_link: ^0.1.0-beta.3 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 #

pub package License: BSD-3-Clause

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 PaymentResult type for exhaustive handling
  • Stream<SpareLinkEvent> for analytics and lifecycle observability
  • SpareLink.test() constructor for unit testing with injectable dependencies

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:

if (await spareLink.handleRedirect(incomingUri)) {
  return true; // consumed — skip other deep-link handlers
}

Usage #

Create one SpareLink instance per environment and reuse it for every flow:

import 'package:spare_link/spare_link.dart';

final spareLink = SpareLink(
  environment: PaymentEnvironment.sandbox,
  tenant: SpareApiTenant.uae,
);

spareLink.events.listen((event) {
  switch (event) {
    case SpareLinkSessionLoaded(:final linkToken, :final linkSessionId):
      debugPrint('loaded $linkToken → $linkSessionId');
    case SpareLinkSucceeded(:final result):
      debugPrint('succeeded: ${result.status.name}');
    default:
      break;
  }
});

Start a payment flow #

Obtain a linkToken from your backend (via the Spare Link API), then launch the flow:

final session = await spareLink.start(
  navigator: Navigator.of(context),
  linkToken: linkToken,
);
debugPrint('session.linkToken=${session.linkToken}'); // available immediately
final result = await session.result;

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.

Forward every payment-return URI your platform delivers to:

if (await spareLink.handleRedirect(uri)) return true;

The recommended place is a WidgetsBindingObserver:

@override
Future<bool> didPushRouteInformation(RouteInformation routeInformation) async {
  final uri = routeInformation.uri;
  if (uri.scheme == 'myapp') {
    if (await 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:

final recovery = await spareLink.resumeIfNeeded(
  navigator: navigatorKey.currentState,
);

switch (recovery) {
  case SessionRecovered(:final result):
    debugPrint('recovered: ${result.status.name}');
  case NoRecoveryNeeded(:final reason, :final linkToken):
    if (reason == NoRecoveryReason.navigatorRequired) {
      // Retry with a navigator when UI is ready
      debugPrint('pending link token: $linkToken');
    }
}

Using with go_router #

spare_link works with MaterialApp.router and go_router without any SDK changes.

  1. Call spareLink.start(navigator:, linkToken:) from any widget with a NavigatorState.
  2. Forward return deep links to spareLink.handleRedirect(uri).
  3. Optionally call spareLink.resumeIfNeeded(...) on app startup.

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:

  1. Navigates to https://<spare-return-host>/link/app-return?exchange_code=…&fallback=…
  2. 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-association
  • GET /.well-known/assetlinks.json

Your app bundle ID / package name must be listed in Spare's link app-return config for verification to succeed.

Identifier glossary #

Dart field Link API / redirect Meaning
linkToken link_token, header x-link-token JWS minted by POST /link/token/payment; passed to SpareLink.start()
linkSessionId link_session_id Server-assigned session id from GET /link/status or PATCH /link
exchangeCode exchange_code One-time code on the bank return redirect after /link/authorize
merchantRef merchantRef Merchant order reference on the bank return redirect

Bank return URIs use exchange_code + merchantRef on success (not session_id).

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

1
likes
0
points
132
downloads

Publisher

unverified uploader

Weekly Downloads

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.

Homepage
Repository (GitHub)
View/report issues

Topics

#payments #open-banking #fintech #sdk

License

unknown (license)

Dependencies

device_info_plus, flutter, http, logging, shared_preferences, url_launcher, uuid

More

Packages that depend on spare_link