Apple Pay
Apple Pay is self-submitting: the PKPaymentButton it renders is the Pay button. The tap opens PassKit's sheet, the authorization selects the method as primary and runs /validate.
Setup
| Product | PurseCheckoutApplePay (brings PassKit) |
| Install | PluginRegistry.shared.install(ApplePayModule(configuration:)), once, before you build a checkout |
| Entitlement | Apple Pay (In-App Payments) enabled in Signing & Capabilities, listing the same merchant identifier |
| Dashboard | Apple Pay activated on the entity with a gateway (Adyen, Ingenico/Worldline, or another partner). Country, currency, capabilities and accepted networks are configured there and reach the app through /init |
| Keys | None in the app. A payment processing certificate must exist for the merchant identifier with that PSP |
The merchant identifier is the one thing no server can supply: it is bound to your app's entitlement, so it comes from your code.
PluginRegistry.shared.install(ApplePayModule(configuration: ApplePayConfiguration(
merchantIdentifier: "merchant.com.example.store", // must match the entitlement exactly
merchantName: "Example Store" // the sheet's total line, when /init names nobody
)))
Everything else about the sheet comes from /init: the SDK shapes the request per PSP variant (Adyen, Ingenico/Worldline, or the default), and falls back to Cartes Bancaires, Mastercard and Visa when the response lists no networks.
Render
Render the element as soon as the method is listed, and prepare() it: that is the POST /init that configures the sheet, and the button cannot present without it. Never call setPrimary for it, and never place a Pay button of yours next to it: submit() is a no-op for a self-submitting element.
PaymentElementView(element)
.frame(height: 48) // the branded button is the submit
.task { await element.prepare() }
element.isReady turns true once /init answered. hasUI is true, selfSubmitting is true.
Outcomes
| Outcome | When |
|---|---|
completed(ValidationResult) | The sheet was authorized and /validate answered without a redirect |
returnedFromRedirect(url:) | A further step followed and the shopper came back. Confirm the payment status on your backend |
canceled | The shopper dismissed the sheet without authorizing. Nothing was charged; leave them able to try again or pick another method |
failed(PurseError) | /init failed, the sheet could not present, or the token could not be read |
/init failing, the sheet failing to present and an unreadable token are also reported as plugin events (initFailed, clientError): the method moves into PARTNER_UI_FATAL_ERROR and stays unavailable for the session (Method states).
A failure from prepare() means the button was never usable: report it where the shopper already is rather than on a payment-result screen, and leave the other methods selectable.
When the device cannot pay
Two different degradations:
| Situation | What happens |
|---|---|
ApplePayModule installed with no configuration | The method stays listed. Its element has hasUI false; submitting reports failed with Apple Pay needs a merchant id: install ApplePayModule(configuration:) |
PKPaymentAuthorizationController.canMakePayments() is false | When the checkout is created, the plugin refuses the device: the method moves into PLUGIN_INCOMPATIBLE, which is filtered, and disappears from paymentMethods. Apple requires the button be hidden on such a device. An element built before that check lands has hasUI false and submit does nothing: no button, no outcome |
An element without UI never reports readiness, so element.isReady also stays false. Gate on hasUI or isReady before you reserve room for the button, so an express row does not render an empty box.
Two sheets, one session
A second submit() while one is in flight returns the in-flight task rather than starting another payment, and a second /validate for one session is refused by the API. After a successful payment the session is spent.
Apple Pay has no sandbox flag in this SDK: nothing maps a PurseEnvironment to an Apple Pay environment. Use a sandbox tester account on a real device. The simulator cannot produce a usable payment token.
Troubleshooting
| Symptom | Cause |
|---|---|
| The method is not listed | canMakePayments() is false on this device (PLUGIN_INCOMPATIBLE), or the session does not offer applepay |
| The method is listed but the button never appears | The module was installed without a configuration |
failed, message lists the entitlement, the certificate and the request | PassKit refused to present and does not say why. Work down that list: the merchant id must be in the In-App Payments entitlement, a processing certificate must exist for it with this PSP, and the request's country, currency and networks must be usable on the device |
failed with PARTNER_UI_ERROR, message Apple Pay /init returned no plugin_result.logs | Apple Pay is offered but not configured on the gateway. Check the dashboard |
failed with PARTNER_UI_ERROR, message Apple Pay /init did not return a country code (or currency) | The gateway's /init answer is incomplete. PKPaymentRequest refuses to present without them, so the SDK fails here where the cause is still known |
failed, message call prepare() before present() | The button was tapped before prepare() completed. Start it when the element is created |
The method is listed but has no UI and submitting reports METHOD_NOT_IMPLEMENTED | ApplePayModule was never installed. See Payment methods |