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:
| Guide | Why |
|---|---|
| Flutter SDK | Surfaces, isolation model and flows |
| SDK Setup | Environment name and clientId |
| Top-Up Transfer | Estimate 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.
| Input | What it is | Source |
|---|---|---|
quote | WirexWalletQuote(estimationId:, sourceAmount: WirexWalletMoney(amount:, currency:)). Optional: destinationAmount, fee, rate, expiresAtMs | estimation_id and source_amount / source_currency from POST /api/v1/cards/topup/estimate |
providerConfig | WirexWalletProviderConfig(rawMap) — the wallet provider block, passed verbatim | The topup.providers.<name> block of your app configuration |
auth | WirexWalletCheckoutAuth(checkoutToken:) — token mode only | access_token from POST /api/v1/cards/topup/token |
Trap:
WirexWalletMoney.amountis 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_payorgoogle_paykey 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 mode | Legacy mode | |
|---|---|---|
auth | WirexWalletCheckoutAuth(checkoutToken:) | null |
| Who executes the top-up | The checkout, with the restricted token | Your backend |
| Events after the Pay sheet | WirexWalletCheckoutExecuting → Executed or ExecuteFailed | WirexWalletCheckoutToken |
| What reaches Dart | The top-up id and status | walletPaymentToken for your backend to relay |
| Custom Tab launch | Supported | Not 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
checkoutTokenper armed quote. Mint a new one on every re-arm. estimationIdandcheckoutTokencome 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
| Event | Mode | Meaning |
|---|---|---|
WirexWalletCheckoutReady | Both | The checkout is rendered |
WirexWalletCheckoutNaturalHeight | Both | height — apply it to the widget's box. Also available as controller.naturalHeight |
WirexWalletCheckoutSheetOpen | Both | The Pay sheet is open |
WirexWalletCheckoutCancelled | Both | The user closed the Pay sheet |
WirexWalletCheckoutAttemptFailed | Both | reason — the attempt failed; the checkout stays armed |
WirexWalletCheckoutEstimationExpired | Both | Re-price and rearm |
WirexWalletCheckoutToken | Legacy | token.walletPaymentToken — your backend executes the top-up with it |
WirexWalletCheckoutExecuting | Token | The checkout is executing the top-up |
WirexWalletCheckoutExecuted | Token | result.id, result.status — the top-up was executed |
WirexWalletCheckoutExecuteFailed | Token | error.reason — execute was rejected |
WirexWalletCheckoutFailed | Both | Terminal. error is WirexWalletError { kind, code, statusCode, detail } |
WirexWalletCheckoutDiagnostic, WirexWalletCheckoutDebug | Both | Diagnostics. 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,
};
}returnUrlmust 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 thancompleted,pendingorfailed— includingalreadyExecutedand no outcome — as unknown and
reconcile from the top-up status.
Updated about 3 hours ago

