Accept Apple Pay™ Recurring Payments

This guide explains how to use Apple Pay Merchant Tokens (MPANs) with MoneyHash APIs and React Native SDK to enable recurring, unscheduled, and automatic reload payments using Apple Pay network tokens.

Overview

Apple Pay Merchant Tokens (MPANs) allow you to securely reuse Apple Pay network tokens for recurring and merchant-initiated transactions (MITs).
MoneyHash supports this by converting Apple Pay tokens into Universal Card Tokens, which you can use across payment providers.

This flow lets you:

  1. Tokenize an Apple Pay network token via MoneyHash's React Native SDK.
  2. Store it as a reusable universal card token in the Vault.
  3. Charge it for both CIT and MIT unscheduled payments.

Apple provides MPAN tokens for various payment scenarios:

  • Recurring Payments - Subscriptions, memberships, and regular billing cycles
  • Automatic Reload - Topping up accounts when balances fall below thresholds
  • Deferred Payment - Pre-authorization for services delivered at a later date

When using MPANs, the payment flow includes additional configuration in the Apple Pay™ payment request to specify the type of recurring payment and associated parameters.


Getting Started

Prerequisites

Before starting, make sure you have:

  • A payment flow that exposes self‑serve Apple Pay as an express method (so Apple Pay appears in expressMethods with nativePayData).
  • Integrate MoneyHash React Native SDK v4.0.0+ (for tokenizeReceipt).
  • Apple Pay configured in your iOS app with proper entitlements.
  • A customer in MoneyHash.

Step-by-Step Integration

Step 1 — From backend create or get customer

  1. Use the Customer API to either create a new customer (POST /api/v1.4/customers/) or reuse an existing one.
  2. Store the customer ID. You will:
    1. send it when creating the card token intent
    2. reuse it on all CIT / MIT payment intents.

Step 2 — From backend create a card token intent

(send the customer here, and card type universal)

Create a card intent / card token intent so you can tokenize the Apple Pay card into a UNIVERSAL card token. This uses the Card Token APIs mentioned in "Save card for future use".

Example (shape only, exact schema per your internal API):

POST /api/v1.4/tokens/cards/

{
  "customer": "<CUSTOMER_ID>",
  "card_token_type": "UNIVERSAL", // key bit for cross‑provider usage
  "webhook_url": "https://example.com/webhook",
  "metadata": {
    "source": "apple_pay_network_token"
  }
}

You'll get back something like:

{
  "id": "<CARD_TOKEN_INTENT_ID>",
  ...
}

Keep cardTokenIntentId – the SDK will need it when calling moneyHashSDK.tokenizeReceipt.

This is conceptually the same "card intent" used in

Save card for future use


Step 3 — Using SDK get all methods, send the flow here

(keep the returned method id here)

In your React Native app, call getMethods and pass the flowId of a flow that exposes Apple Pay as an express method.

const moneyHashSDK = new MoneyHashSDKBuilder().build();
try {
  const methods = await moneyHashSDK.getMethods({
        currency: "USD",
        amount: 20,
        customer: "<customer_id>",
        flowId: "<flow_id>",
      });

  // Find Apple Pay express method
  const applePayMethod = methods.expressMethods?.find(
        (method) => method.id === "APPLE_PAY" || method.type === "APPLE_PAY"
      );

  if (!applePayMethod || !applePayMethod.nativePayData) {
    throw new Error('Apple Pay method not available');	
	}

  const applePayData = applePayMethod.nativePayData as ApplePayData;

  // Native Pay config from MoneyHash
  // This is what Apple Pay docs refer to as applePayNativeData
  const methodId = applePayData.methodID!;

} catch (error) {
  throw error;
}
  • Keep methodId (applePayData.methodID):
    • It's used today in Apple Pay merchant validation.
    • In this new flow you will also pass it to tokenizeReceipt.

You can also optionally use fields from applePayData to pre‑configure the Apple Pay request (amount, supported networks, country, currency) instead of hard‑coding them.


Step 4 — Generate Apple Pay receipt using MoneyHash SDK

Use the MoneyHash SDK's generateApplePayReceipt method with the Apple Pay data from Step 3. This method handles the Apple Pay sheet presentation and processes the payment automatically.

try {
  // MoneyHash SDK handles the entire Apple Pay flow including:
  // 1. Presenting the Apple Pay sheet
  // 2. Handling user authorization 
  // 3. Processing the payment token
  // 4. Returning the receipt for tokenization
  const nativePayReceipt = await moneyHashSDK.generateApplePayReceipt({
      depositAmount: amount,
      applePayData: applePayData,
    });

  console.log('✅ Apple Pay receipt generated successfully');
  console.log(nativePayReceipt);
	const recept = nativePayReceipt.receipt

} catch (error) {
    console.error('❌ Failed to generate Apple Pay receipt:', error);
    throw error;
}

The MoneyHash SDK's generateApplePayReceipt method automatically:

  • Configures the Apple Pay request with the appropriate payment details
  • Presents the Apple Pay authorization sheet to the user
  • Handles the payment authorization flow
  • Processes the Apple Pay token and converts it to a receipt format suitable for tokenization
  • Returns a NativePayReceipt object that contains the receipt string that can be passed directly to tokenizeReceipt

Step 5 — Submit receipt to moneyHashSDK.tokenizeReceipt

(with card token intent id, method id → get card token id back)

Once you have:

  • cardTokenIntentId (Step 2),
  • methodId (from applePayData.methodID in Step 3), and
  • applePayReceipt (Step 4),

call the React Native SDK helper:

try {
  const cardTokenId = await moneyHashSDK.tokenizeReceipt({
      receipt,              // Receipt generated from Apple Pay
      methodId,             // applePayData.methodID
      cardTokenIntentId,    // from backend card intent creation
    });

  // cardTokenId is a MoneyHash "card token" backed by a network token
  console.log('✅ Card token created:', cardTokenId);
  return cardTokenId;

} catch (error) {
    console.error('❌ Failed to tokenize receipt:', error);
    throw error;
}

This mirrors the "Save card for future use" flow (card intent + tokenization), but instead of cardForm.createCardToken, you're feeding in an Apple Pay receipt via tokenizeReceipt.

Keep cardTokenId — it's what you'll pass into the CIT and MIT payment intents.


Step 6 — Create the CIT payment intent

(card token id + customer + CIT data + pay with network token + recurring data, payment type UNSCHEDULED)

Now you create the first unscheduled CIT payment using:

  • the Apple Pay card token (cardTokenId),
  • payment_type: "UNSCHEDULED",
  • "merchant_initiated": false,
  • "paying_with_network_token": true,
  • an agreement_id in recurring_data.

Example:

POST /api/v1.4/payments/intent/

{
  "amount": 20,
  "amount_currency": "USD",
  "operation": "purchase",
  "customer": "8c1a11c0-6ec6-4888-9e5c-8d07d7b5fed0",
  "card_token": "<APPLE_PAY_CARD_TOKEN_ID>",   // from tokenizeReceipt
  "merchant_initiated": false,                 // CIT
  "payment_type": "UNSCHEDULED",
  "paying_with_network_token": true,
  "recurring_data": {
    "agreement_id": "[YOUR_GENERATED_UNSCHEDULED_ID]"
  },
  "webhook_url": "https://example.com/webhook"
}

Compared to the generic Unscheduled payments – Initial transaction example, the main differences are:

  • you already have a card_token from Apple Pay (so you don't need tokenize_card: true here),
  • and you explicitly mark this as a network token payment with "paying_with_network_token": true.

The customer still participates actively (CIT), but the credential being used is a stored network token, not a raw PAN.


Step 7 — Create the MIT payment intent

(card token id + customer + MIT data + pay with network token + recurring data, payment type UNSCHEDULED)

Every subsequent charge in the agreement is a MIT unscheduled payment that:

  • reuses the same card_token (Apple Pay network token),
  • sets "merchant_initiated": true,
  • keeps "payment_type": "UNSCHEDULED",
  • sets "paying_with_network_token": true,
  • and sends the same agreement_id in recurring_data.

Example:

POST /api/v1.4/payments/intent/

{
  "amount": 20,
  "amount_currency": "USD",
  "operation": "purchase",
  "customer": "8c1a11c0-6ec6-4888-9e5c-8d07d7b5fed0",
  "card_token": "<APPLE_PAY_CARD_TOKEN_ID>",
  "merchant_initiated": true,                 // MIT
  "payment_type": "UNSCHEDULED",
  "paying_with_network_token": true,
  "recurring_data": {
    "agreement_id": "[YOUR_GENERATED_UNSCHEDULED_ID]"
  },
  "webhook_url": "https://example.com/webhook"
}

This is equivalent to "Request an unscheduled payment" in the docs, with the extra hint that:

  • the card token is a network token provisioned via Apple Pay, and
  • the intent is explicitly flagged with "paying_with_network_token": true.