Apple Pay and Google Pay Top-Up

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

Before You Start

Read the following guides before proceeding:

GuideWhy
React Native SDKSurfaces, isolation model and flows
SDK Setupenvironment, clientId, Google Pay intents
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
quote{ estimationId, sourceAmount: { amount, currency } }estimation_id and source_amount / source_currency from POST /api/v1/cards/topup/estimate
providerConfigThe wallet provider block, passed verbatim as an objectThe topup.providers.<name> block of your app configuration
providerThe provider name the block belongs toYour app configuration
auth{ checkoutToken } — token mode onlyaccess_token from POST /api/v1/cards/topup/token

Trap: pass providerConfig 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. resolveWalletProviderBlock(topup, provider)
picks the block out of your app configuration.


Choose a Mode

Token modeLegacy mode
auth{ checkoutToken }Omitted
Who executes the top-upThe checkout, with the restricted tokenYour backend
Events after the Pay sheetexecuting → executed or executeFailedtoken
What reaches JavaScriptThe top-up id and statuswalletPaymentToken for your backend to relay
Custom Tab launchSupportedNot supported

Token mode keeps the wallet payment token out of JavaScript. A failed token mint is not fatal in-app: arm
the checkout 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.

The estimation has a short TTL. When it expires the checkout emits estimationExpired; re-price and pass a
new quote.


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 * as React from 'react';
import {
  WirexWalletAvailabilityProbe, useWalletAvailability,
  type WirexWalletConfig, type WirexWalletProviderConfig,
} from '@wirexapp/mobile-sdk';

const config: WirexWalletConfig = { environment: 'test' };

export function PayButtonGate({ providerConfig }: { providerConfig: WirexWalletProviderConfig }) {
  const availability = useWalletAvailability(config, { providerConfig });
  return (
    <>
      <WirexWalletAvailabilityProbe config={config} providerConfig={providerConfig} />
      {availability?.applePay || availability?.googlePay ? <PayEntryPoint /> : null}
    </>
  );
}

useWalletAvailability returns { applePay, googlePay } from the native canMakePayments check, or null
until the probe resolves. Mount the probe with the real provider block before showing a Pay button.

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

import * as React from 'react';
import {
  WirexWalletCheckoutView,
  type WirexWalletCheckoutEvent, type WirexWalletProviderConfig, type WirexWalletQuote,
} from '@wirexapp/mobile-sdk';

export function Checkout({ quote, providerConfig, provider, checkoutToken }: {
  quote: WirexWalletQuote; providerConfig: WirexWalletProviderConfig; provider: string; checkoutToken?: string;
}) {
  const [height, setHeight] = React.useState<number | undefined>();
  const onEvent = (event: WirexWalletCheckoutEvent) => {
    switch (event.type) {
      case 'naturalHeight': return setHeight(event.height);
      case 'estimationExpired': return repriceAndRearm();
      case 'token': return sendToBackend(event.token.walletPaymentToken);
      case 'executed': return showResult(event.result.id, event.result.status);
      case 'executeFailed': return showError(event.error.reason);
      case 'failed': return showError(event.error.kind);
      default: return;
    }
  };
  return (
    <WirexWalletCheckoutView
      config={{ environment: 'test' }}
      quote={quote}
      providerConfig={providerConfig}
      provider={provider}
      auth={checkoutToken ? { checkoutToken } : undefined}
      onEvent={onEvent}
      style={{ width: '100%', height }}
    />
  );
}

There is no imperative API. To re-arm, pass a new quote — and in token mode a fresh auth — and the view
re-arms.

Events

event.typeModeMeaning
readyBothThe checkout is rendered
naturalHeightBothevent.height — apply it to the view's style
sheetOpenBothThe Pay sheet is open
cancelledBothThe user closed the Pay sheet
attemptFailedBothevent.reason — the attempt failed; the checkout stays armed
estimationExpiredBothRe-price on your backend and pass a new quote
tokenLegacyevent.token.walletPaymentToken — your backend executes the top-up with it
executingTokenThe checkout is executing the top-up
executedTokenevent.result.id, event.result.status — the top-up was executed
executeFailedTokenevent.error.reason — execute was rejected
failedBothevent.error.kind — the checkout failed
diagnostic, debugBothDiagnostics; ignore in production

Legacy Mode: Execute on Your Backend

Pass event.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.

import {
  buildWalletLaunchUrl, isWalletReturnLink, parseWalletReturnLink,
  type WirexWalletConfig, type WirexWalletQuote,
} from '@wirexapp/mobile-sdk';

export const launchUrl = (config: WirexWalletConfig, checkoutToken: string, quote: WirexWalletQuote, provider: string, returnUrl: string) =>
  buildWalletLaunchUrl(config, { checkoutToken, quote, provider, returnUrl });

export function readReturn(url: string | undefined, returnUrl: string, quote: WirexWalletQuote) {
  if (!url || !isWalletReturnLink(url, returnUrl)) return 'unknown';
  const back = parseWalletReturnLink(url);
  if (back.estimationId && back.estimationId !== quote.estimationId) return 'unknown';
  switch (back.outcome) {
    case 'completed': case 'pending': case 'failed': return back.outcome;
    default: return 'unknown';
  }
}
  • returnUrl must be an https App Link verified for your app (assetlinks.json). Any installed app can
    claim a custom scheme.
  • Open the URL with an auth-session API, for example expo-web-browser's openAuthSessionAsync(url, returnUrl).
  • 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 already_executed and no outcome — as unknown and
    reconcile from the top-up status.

Did this page help you?