Mintcash
Guides

Accept Apple Pay

Offer Apple Pay on the hosted payment page, as an Apple Pay-only page or as a standalone button: what to send your account manager, how the payment flows and what to test before going live.

MintCash supports Apple Pay wherever the underlying provider does. How you integrate depends on the Payment Provider abilities. This guide walks through what it requires of you, the integration path, and what to consider during testing.

Before you start

Complete the setup of your Apple Pay developer account first — Apple Merchant ID and merchant identity certificate. In case you work with a Payment Provider that have it set up for you, you just need to collect those IDs for us. Then, provide the credentials to your account manager and that's it. All the configuration will happen on MintCash side, and because the Apple Pay button renders on our Hosted Payment Page, domain verification is handled on our domain.

Integration

Once your account manager configures your account, the Apple Pay button is rendered on the Hosted Payment Page by default (default or customized HPP). Every one-time payment you create via the API will display the option to proceed with Apple Pay through our platform. Decryption can happen on the provider's side or ours, depending on the provider's capabilities. The integration you write is the same either way.

Rendering diagram…
  1. You create the payment; we respond with the redirectUrl.
  2. The payment page displays the available payment methods, including Apple Pay.
  3. Apple Pay authenticates the customer and returns the encrypted payment data, which we pass to the provider.
  4. If the issuer requires extra verification (3DS or OTP), the customer completes it in place on the page.
  5. We send the webhook to your callbackUrl with the final state.

Apple Pay is for one-time payments

Apple Pay charges are not tokenized for reuse — they never produce a saved card token, and they can't fund a subscription's renewals. So the button is hidden on subscription checkouts (and the wallet endpoints refuse them). Use the card flow for subscriptions and saved-card charges.

Apple Pay-only checkout

If you already collect card payments elsewhere, or simply want a one-tap page, you can ask for a payment page that offers only Apple Pay. Set applePayOnly: true when you create the payment:

const r = await fetch("https://sandbox.mintcash.me/payments", {
  method: "POST",
  headers: {
    Authorization: `Basic ${btoa(`${publicKey}:${secretKey}`)}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    externalId: orderId,
    amount: 49.99,
    currency: "USD",
    applePayOnly: true,
    customer: {
      externalId: customerId,
      email: customer.email,
      name: customer.name,
    },
  }),
});

const { redirectUrl } = await r.json();

The flag applies to that payment alone, so you can run both versions side by side: payments created without it keep the standard page. Everything else about the integration stays the same, whether you redirect or embed the page: same redirectUrl, same 3DS handling, same webhooks.

On an Apple Pay-only page:

  • Only the Apple Pay button is shown. The card form, Google Pay, billing fields and the save-card option are all hidden.
  • Unsupported devices get a clear message. If the browser can't make Apple Pay payments (for example Chrome on Windows, or a device with no card in Wallet), the page says Apple Pay isn't available and asks the customer to open it in Safari on an Apple device, instead of showing an empty checkout.
  • Cancelling isn't final. If the customer closes the Apple Pay sheet, the page tells them they haven't been charged and they can tap the button again.
  • Declines and errors show the same feedback as the standard page.

Apple Pay must be enabled on your account

If Apple Pay isn't enabled for your account, applePayOnly is ignored and the standard payment page renders, so customers can still pay by card.

Embedded Apple Pay-only pages

When the page is embedded in an iframe, it sends two extra signals so your page can react:

  • A mintcash:wallet message, once, as soon as it knows whether Apple Pay is available on this device. Use it to hide the frame and show your own payment options when available is false.
  • A mintcash:checkout message with status cancelled when the customer closes the Apple Pay sheet without paying. The session stays open, so they can tap again.
window.addEventListener("message", (event) => {
  if (event.origin !== MINTCASH_CHECKOUT_ORIGIN) return;
  const msg = event.data;
  if (msg?.type === "mintcash:wallet" && msg.wallet === "apple_pay") {
    if (!msg.available) showOtherPaymentOptions();
  }
  if (msg?.type === "mintcash:checkout" && msg.status === "cancelled") {
    unlockYourCheckout(); // nothing was charged
  }
});

Standalone Apple Pay button

You can also place a single Apple Pay button on your own checkout, next to your existing payment options, before any payment exists. The payment is only created once the customer taps the button, so you don't create payments for customers who never pick Apple Pay.

The button is an Apple Pay-only page you embed in an iframe, so it needs your embed origins registered (see Embed the checkout). It doesn't work as a redirect.

Rendering diagram…

Create the button

From your server, call POST /wallet-buttons with the amount and currency of the order. Nothing is stored on our side yet. The response gives you a signed token and the url to embed, valid for 15 minutes (expiresAt).

const r = await fetch("https://sandbox.mintcash.me/wallet-buttons", {
  method: "POST",
  headers: {
    Authorization: `Basic ${btoa(`${publicKey}:${secretKey}`)}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ amount: 49.99, currency: "USD" }),
});

const { token, url, expiresAt } = await r.json();

If the order total changes, create a new button: the payment must match the button's amount and currency exactly.

Embed it

Point an iframe at url. The allow="payment" attribute is required for Apple Pay to run inside a frame.

<iframe src="{{url}}" width="100%" height="80" frameborder="0" allow="payment"></iframe>

The button sends mintcash:wallet as soon as it knows whether Apple Pay is available, and mintcash:resize to keep the frame sized to its content. Hide the frame when available is false.

Create the payment when the customer taps

When the customer taps the button, the Apple Pay sheet opens and the frame sends your page a mintcash:wallet:start message. Create the payment on your server with the button's token as walletToken, then answer with the checkoutToken from the response, echoing the same requestId. You have 15 seconds to answer.

window.addEventListener("message", async (event) => {
  if (event.origin !== MINTCASH_CHECKOUT_ORIGIN) return;
  const msg = event.data;
  if (msg?.type !== "mintcash:wallet:start") return;

  lockYourCheckout(); // the Apple Pay sheet is open
  try {
    // Your endpoint calls POST /payments with walletToken: token
    const { checkoutToken } = await createPaymentOnYourServer(orderId);
    event.source.postMessage(
      { type: "mintcash:wallet:session", requestId: msg.requestId, sessionToken: checkoutToken },
      event.origin,
    );
  } catch {
    event.source.postMessage(
      { type: "mintcash:wallet:session", requestId: msg.requestId, error: "refused" },
      event.origin,
    );
  }
});

On your server, it's an ordinary POST /payments call plus the token:

body: JSON.stringify({
  externalId: orderId,
  amount: 49.99, // must match the button
  currency: "USD", // must match the button
  walletToken: token,
  customer: { externalId: customerId, email: customer.email, name: customer.name },
}),

A few rules apply to walletToken:

  • The amount, currency, merchant and environment (test or live) must match the button, or the request fails with 400.
  • It implies applePayOnly: true, and it can't be combined with cardToken.
  • An expired button (older than 15 minutes) fails with 409. Create a new button.
  • Reuse the same externalId if the customer taps again. Thanks to idempotency you get the same payment back and no second charge is created, even after the button has expired.

Answering with error (for example when the order can no longer be placed) cancels the attempt: the frame posts mintcash:checkout with status cancelled so you can unlock your checkout. The same happens if you don't answer within 15 seconds.

Handle the outcome

From here it's the embedded checkout as usual: the frame handles 3DS in place and posts mintcash:checkout with the result (see the message reference). Fulfil the order on the payment.succeeded webhook, never on the browser message.

What to test

Run each flow you plan to ship in sandbox before going live, on Safari for both iOS and macOS:

  • Happy path: an Apple Pay authorisation that lands on payment.succeeded.
  • 3DS challenge: confirm the challenge renders and finalises successfully.
  • Webhook delivery: confirm your endpoint receives the final event.
  • Cancel: close the Apple Pay sheet and confirm the customer can try again (and, if embedded, that you receive cancelled).
  • Unsupported browser: if you use the Apple Pay-only page or the standalone button, open it in a browser without Apple Pay (for example Chrome on Windows) and confirm the fallback works.
  • Standalone button: confirm a second tap reuses the same payment, and that an error answer unlocks your checkout.