Apple Pay and Google Pay Top-Up

Embed the Wirex-hosted Apple Pay and Google Pay checkout in a Flutter app for a top-up your backend priced, in token or legacy mode.

Before You Start

Read the following guides before proceeding:

GuideWhy
Flutter SDKSurfaces, isolation model and flows
SDK SetupEnvironment name and clientId
Top-Up TransferEstimate with apple_pay / google_pay and the wallet-method execute body

Overview

WirexWalletCheckoutView embeds the Wirex-hosted checkout for a top-up your backend has already priced. The
checkout renders the quote and the wallet button; the Pay sheet is native. Your app needs no Apple Pay
entitlement and no merchant identifier.

A wallet top-up requires the WalletTopup capability — separate from CardTopup. Check it via
GET /api/v2/user before offering the Pay button.

InputWhat it isSource
quoteWirexWalletQuote(estimationId:, sourceAmount: WirexWalletMoney(amount:, currency:)). Optional: destinationAmount, fee, rate, expiresAtMsestimation_id and source_amount / source_currency from POST /api/v1/cards/topup/estimate
providerConfigWirexWalletProviderConfig(rawMap) — the wallet provider block, passed verbatimThe topup.providers.<name> block of your app configuration
authWirexWalletCheckoutAuth(checkoutToken:) — token mode onlyaccess_token from POST /api/v1/cards/topup/token

Trap: WirexWalletMoney.amount is a decimal string ('10.00'), not a number.

Trap: pass the provider block exactly as received. Do not re-case keys or add defaults — an absent
apple_pay or google_pay key means that wallet is not offered.
WirexWalletProviderConfigResolver.resolve(topupJson, providerName) picks the block out of your app
configuration and reports when it is absent or unusable.


Choose a Mode

Token modeLegacy mode
authWirexWalletCheckoutAuth(checkoutToken:)null
Who executes the top-upThe checkout, with the restricted tokenYour backend
Events after the Pay sheetWirexWalletCheckoutExecuting → Executed or ExecuteFailedWirexWalletCheckoutToken
What reaches DartThe top-up id and statuswalletPaymentToken for your backend to relay
Custom Tab launchSupportedNot supported

The mode is fixed when the view is created. A failed token mint is not fatal in-app: create the view in
legacy mode instead.


Step 1: Price the Top-Up

Your backend calls POST /api/v1/cards/topup/estimate with payment_method set to apple_pay (iOS) or
google_pay (Android). Request and response: Estimate Top-Up.


Step 2: Mint a Checkout Token (Token Mode)

Your backend calls POST /api/v1/cards/topup/token with the user token and passes access_token from the
response to the app as checkoutToken. The token is accepted only by the top-up estimate and execute
endpoints. Request, response and code: Restricted Top-Up Token.

  • One checkoutToken per armed quote. Mint a new one on every re-arm.
  • estimationId and checkoutToken come from two independent calls; neither is derived from the other.
  • Never hand the user's session bearer to the checkout — pass only the restricted token.

Step 3: Check Availability

import 'package:wirex_mobile_sdk/wirex_mobile_sdk.dart';

Future<WirexWalletConfig> walletConfig() async =>
    WirexWalletConfig(environment: await WirexWalletEnvironment.resolve('test'));

Future<WirexWalletAvailabilityChecker> probe(WirexWalletConfig config, WirexWalletProviderConfig providerConfig) async {
  final WirexWalletAvailabilityChecker checker =
      WirexWalletAvailabilityChecker(config: config, providerConfig: providerConfig);
  checker.onChange = (WirexWalletAvailability a) => showPayButton(a.applePay || a.googlePay);
  await checker.start();
  return checker;
}

onChange reports the native canMakePayments answer as applePay / googlePay. Start the checker with the
real provider block before showing a Pay button, and await checker.stop() when done.

WirexWalletConfig(cache:) takes an optional WirexWalletAvailabilityCache key-value store so the answer
survives relaunch. The SDK writes one key with two booleans.

Note: the iOS Simulator reports Apple Pay unavailable and cannot present the Pay sheet. Test on a
device with a provisioned card.


Step 4: Render the Checkout

class _CheckoutState extends State<Checkout> {
  WirexWalletCheckoutController? _controller;

  @override
  Widget build(BuildContext context) => WirexWalletCheckoutView(
        config: widget.config,
        quote: widget.quote,
        providerConfig: widget.providerConfig,
        auth: widget.checkoutToken == null ? null : WirexWalletCheckoutAuth(checkoutToken: widget.checkoutToken!),
        onCreated: (WirexWalletCheckoutController c) => _controller = c,
        onEvent: (WirexWalletCheckoutEvent event) {
          switch (event) {
            case WirexWalletCheckoutNaturalHeight(:final height):
              setState(() => _height = height);
            case WirexWalletCheckoutEstimationExpired():
              repriceAndRearm();
            case WirexWalletCheckoutToken(:final token):
              sendToBackend(token.walletPaymentToken);
            case WirexWalletCheckoutExecuted(:final result):
              showResult(result.id, result.status);
            case WirexWalletCheckoutExecuteFailed(:final error):
              showError(error.reason);
            case WirexWalletCheckoutFailed(:final error):
              showError(error.kind.wire);
            default:
              break;
          }
        },
      );

  Future<void> rearm(WirexWalletQuote quote, String? checkoutToken) => _controller!.rearm(
        quote,
        widget.providerConfig,
        auth: checkoutToken == null ? null : WirexWalletCheckoutAuth(checkoutToken: checkoutToken),
      );

  @override
  void dispose() {
    _controller?.destroy();
    super.dispose();
  }
}

The estimation has a short TTL. On WirexWalletCheckoutEstimationExpired, re-price on your backend and call
controller.rearm(quote, providerConfig, auth:) — in token mode with a freshly minted token. A changed
estimationId also re-arms the view. Destroy the controller in dispose.

Events

EventModeMeaning
WirexWalletCheckoutReadyBothThe checkout is rendered
WirexWalletCheckoutNaturalHeightBothheight — apply it to the widget's box. Also available as controller.naturalHeight
WirexWalletCheckoutSheetOpenBothThe Pay sheet is open
WirexWalletCheckoutCancelledBothThe user closed the Pay sheet
WirexWalletCheckoutAttemptFailedBothreason — the attempt failed; the checkout stays armed
WirexWalletCheckoutEstimationExpiredBothRe-price and rearm
WirexWalletCheckoutTokenLegacytoken.walletPaymentToken — your backend executes the top-up with it
WirexWalletCheckoutExecutingTokenThe checkout is executing the top-up
WirexWalletCheckoutExecutedTokenresult.id, result.status — the top-up was executed
WirexWalletCheckoutExecuteFailedTokenerror.reason — execute was rejected
WirexWalletCheckoutFailedBothTerminal. error is WirexWalletError { kind, code, statusCode, detail }
WirexWalletCheckoutDiagnostic, WirexWalletCheckoutDebugBothDiagnostics. Debug fires only with debug: true

Legacy Mode: Execute on Your Backend

Pass token.walletPaymentToken as wallet_payment_token to POST /api/v1/cards/topup/execute, together
with the other wallet-method fields — see Execute Top-Up.


Custom Tab Launch (Android)

When your own WebView cannot complete Google Pay, open the checkout in a Chrome Custom Tab. Token mode only.
The SDK builds the URL and parses the return link; it opens no browser itself.

Future<String> launchUrl(WirexWalletConfig config, String checkoutToken, WirexWalletQuote quote, String provider, String returnUrl) =>
    WirexWalletLaunch.buildUrl(config, WirexWalletLaunchParams(
      checkoutToken: checkoutToken, quote: quote, provider: provider, returnUrl: returnUrl,
    ));

SpecWalletLaunchOutcome? readReturn(String? url, String returnUrl, WirexWalletQuote quote) {
  if (url == null || !WirexWalletReturnLink.matches(url, returnUrl)) return null;
  final WirexWalletReturnLink back = WirexWalletReturnLink.parse(url);
  if (back.estimationId != null && back.estimationId != quote.estimationId) return null;
  return switch (back.outcome) {
    SpecWalletLaunchOutcome.completed || SpecWalletLaunchOutcome.pending || SpecWalletLaunchOutcome.failed => back.outcome,
    _ => null,
  };
}
  • returnUrl must be an https App Link verified for your app (assetlinks.json). Any installed app can
    claim a custom scheme.
  • Android cannot close the tab for you, and a closed tab says nothing about the payment. Treat every ending
    other than completed, pending or failed — including alreadyExecuted and no outcome — as unknown and
    reconcile from the top-up status.

Did this page help you?