Apple Pay is a complete checkout flow, not only a button. A reliable implementation coordinates merchant configuration, device capability, a correct payment request, secure backend processing and clear recovery when authorization fails.
Understand the end-to-end responsibility
The app presents Apple Pay and collects an encrypted payment token after the user authorizes the purchase. The backend or payment service provider then processes that token. The app should never treat presentation of the payment sheet as proof that the order is paid.
Complete merchant configuration first
Before writing checkout code, configure the Merchant ID, enable the Apple Pay capability for the correct target and align the identifier with your payment processor. Confirm supported networks, countries and currencies with the processor because those values are product and provider decisions.
Keep production and testing configurations deliberate. A mismatch between entitlements, merchant identifiers and processor configuration can make a correct UI implementation fail at authorization time.
Build the payment request from server-owned totals
The backend should remain the source of truth for price, fees, discounts and currency. The app can display the breakdown, but it should create the payment request from a validated checkout response rather than recomputing commercial rules locally.
let request = PKPaymentRequest()
request.merchantIdentifier = configuration.merchantIdentifier
request.countryCode = checkout.countryCode
request.currencyCode = checkout.currencyCode
request.supportedNetworks = configuration.supportedNetworks
request.merchantCapabilities = [.capability3DS]
request.paymentSummaryItems = checkout.summaryItemsThe final summary item represents the merchant and total amount. Keep labels understandable so the payment sheet matches the checkout screen.
Check capability before showing the button
Only show Apple Pay when the device and configuration can support it. If the user has no eligible card, guide them toward setup or another payment method rather than presenting a dead end.
Capability checks improve UX, but the backend must still validate every transaction. Client-side checks are not security controls.
Send the token once and make retries safe
After authorization, transmit the payment token to your backend over TLS together with an order or reservation identifier. Use an idempotency key so a timeout or repeated callback cannot create duplicate charges or duplicate bookings.
- Create the order or reservation before payment when the domain requires it.
- Generate one payment reference for the checkout attempt.
- Reuse the same idempotency key for safe retries.
- Store the processor result and map it to a clear product state.
Separate payment success from product confirmation
Travel, ticketing and inventory products often require a confirmation step after payment. Model payment and fulfillment as related but distinct states. If payment succeeds and fulfillment fails, the backend needs a recovery strategy such as retry, reconciliation or refund handling.
The app should show a pending state when the result is not yet final instead of guessing success or failure.
Handle failure as a designed flow
Plan for cancellation, unsupported devices, network timeout, processor decline, backend validation error and delayed confirmation. Each state needs a user-safe message and a technical identifier for support and logs.
Do not display raw gateway responses. Map them into product-level errors and keep detailed diagnostics in secure observability systems.
Test more than the happy path
- The amount and currency match the backend checkout response.
- Cancellation returns the user to a stable screen.
- Repeated callbacks do not duplicate a charge.
- Timeouts can be reconciled by payment reference.
- The app does not confirm the product before backend success.
- Analytics avoid storing sensitive payment data.
