Accept Tap to Pay payments
This guide walks through integrating Tap to Pay using Payabli’s mobile SDK. To learn how Tap to Pay works, see the Tap to Pay overview.
Prerequisites
Before you start, confirm you have:
- A physical device. An iPhone XS or newer running iOS 16.7 or later, in a region where Apple supports Tap to Pay on iPhone. The Simulator can’t take a Tap to Pay payment.
- Payabli sandbox credentials. A Payabli sandbox
entryPointwith Tap to Pay enabled. Contact your Payabli representative if you don’t have one yet. - An OAuth2 client ID and client secret. See Permissions for the required permissions.
- Your app registered as an authorized app. Your app’s App ID (
<APP_ID_PREFIX>.<BUNDLE_ID>, where the prefix is your Team ID for most apps) must be one of the paypoint’s authorized apps before device attestation succeeds. See Integration steps. - Apple’s Tap to Pay entitlement.
com.apple.developer.proximity-reader.payment.acceptance. Request it through the Apple Developer Portal. - Xcode 15 or later and Swift 5.9 or later.
Apple’s Tap to Pay entitlement (com.apple.developer.proximity-reader.payment.acceptance) needs Apple’s approval before you can use it. Request it through the Apple Developer Portal. Approval can take 2–8 weeks. Apply for this entitlement as soon as you decide to integrate Tap to Pay. Don’t wait until the rest of your integration is ready.
Your app’s provisioning profile also needs com.apple.developer.devicecheck.appattest-environment, set to development for development builds or production for release builds. This value must match the environment you pass to PayabliConfig.
Permissions
Provision an OAuth2 client ID and client secret with the following permissions:
tools_initpos_createinboundpayments_create
On a device with no setup stored for the paypoint, credentials without tools_init or pos_create leave the session at .failed(reason: .configurationRejected). Credentials without inboundpayments_create reach .ready, but charge(...) then throws a TapToPayError whose type is .permissionDenied, before the card is read.
See OAuth authentication to provision credentials and request an access token.
Integration steps
Register your app, set up a backend token proxy, then work through the SDK from installation to your first charge:
Register your app as an authorized app
Before your app can complete device attestation, register its App ID as one of the paypoint’s authorized apps. An App ID is your App ID prefix and your bundle ID, joined by a dot. For example, in TEAM123456.com.yourcompany.app, the App ID prefix is TEAM123456 and the bundle ID is com.yourcompany.app. The prefix is your Team ID for most apps, but an older App ID can have a different prefix, which your Apple Developer account shows. Register once per paypoint for each bundle ID you ship, including debug and white-label builds. Your code never passes the App ID: the SDK reads it from the signed app and sends it when the device attests.
In the Payabli Portal at the paypoint level, go to Pay In > Devices, then choose Authorized apps from the dropdown menu. Authorized apps appears only at the paypoint level, not at the organization level. Or register the app from your backend or a provisioning script:
deviceOs must be "ios". friendlyName is optional. This endpoint is idempotent on (entryPoint, deviceOs, appId), so it’s safe to call from an automated provisioning script. The call needs the pos_create permission.
If the App ID isn’t registered, attestation is refused with an HTTP 403. initialize() throws a TapToPayError whose type is .permissionDenied, and sessionState is .failed(reason: .configurationRejected). Register the app, then call initialize() again.
Build the backend token proxy
Tap to Pay uses the same access token model as the rest of the mobile SDK: your backend holds the Payabli clientSecret, exchanges it for a short-lived access token, and never exposes it to the app. See Backend token endpoint in the Tap to Pay component reference for more information.
Install the SDK
Add PayabliSDKTapToPay from the sdk-ios Swift package and link the PayabliSDKTapToPay product. See Installation in the Tap to Pay component reference for more information.
Initialize and handle first-time activation
The interactive walkthrough displays code examples alongside step-by-step explanations.
Loading walkthrough...
On a device’s first launch, initialize() throws a TapToPayError whose type is .devicePendingActivation, and sessionState is .pendingActivation(activationId:). See How first-time activation works for the full sequence. Handle it by prompting for a code and calling activateDevice(activationCode:). Activation is per device and per paypoint, not per user. A reinstall, a restore to a new device, or a new device needs a new code.
The code is six digits and can start with zero, so keep it as a string. It’s valid for 30 minutes, and asking again before it expires returns the same code, so it’s safe to ask again if the operator needs it resent. Issue it in one of these ways:
- From the Payabli Portal: At the paypoint level, go to Pay In > Devices, then choose Generate activation code from the dropdown menu. You can also generate a code at the organization level from Pay In > Devices > All devices, where you select the paypoint first.
- From your backend: Read the device’s activation ID from
.pendingActivation(activationId:)and send it to your backend. Your backend calls Generate Tap to Pay activation code with the paypoint’s entrypoint and the activation ID in the request’sdeviceIdfield, and returns the code to the app. Anyone who can call this route on your backend can set up a device to take payments for your paypoint, so authenticate the caller the same way your token endpoint does.
If the merchant hasn’t accepted Apple’s Tap to Pay terms, initialize() stops at .pendingTerms and throws a TapToPayError whose type is .termsNotAccepted. A merchant accepts the terms once per merchant, not once per device. After they accept on one device, other devices for the same merchant don’t ask again. Call presentTerms() from a screen where someone with the authority to accept is present, confirm with areTermsAccepted(), then call initialize() again. See Apple’s Tap to Pay on iPhone Human Interface Guidelines for when to show the terms and to whom, and linkAccount(using:) for how acceptance works.
/// Add the imports and token provider
Import PayabliSDKCore and PayabliSDKTapToPay, and add a function that exchanges credentials with your backend for a Payabli access token. See Integration steps for the backend side of this exchange.
/// Start the session and create PayabliTTP
Start one PayabliSession with your entrypoint, environment, and token provider, then call PayabliTTP.create(), which runs on that session. create() throws a TapToPayError whose type is .sessionNotInitialized if no session has been started. You don’t pass your App ID: the SDK reads it from the signed app, and it must already be registered as one of the paypoint’s authorized apps. The environment you pass must match the appattest-environment entitlement in your provisioning profile.
/// Handle first-time activation and Apple’s terms
Call initialize() on launch. On a device’s first run, it throws a TapToPayError whose type is .devicePendingActivation instead of completing, and sessionState is .pendingActivation(activationId:). Get a one-time activation code for the device and deliver it to the user. The SDK doesn’t generate the code itself. Prompt the user for the code, call activateDevice(activationCode:), then initialize again. If the merchant hasn’t accepted Apple’s Tap to Pay terms, initialize() throws a TapToPayError whose type is .termsNotAccepted. Present the terms with presentTerms(), confirm with areTermsAccepted(), then initialize again.
Process a payment
start() handles its own errors, so finishing it doesn’t mean setup succeeded. Offer a charge only when ttp.isReady is true. charge(...) also accepts a session in the .sessionExpired state, and refreshes it before it reads the card:
charge(type:paymentDetails:customer:invoice:orderDescription:) does three things in order: it calls POST /MoneyIn/initiate (which creates the transaction’s paymentTransId), prompts the NFC card read, then calls PATCH /MoneyIn/update/{paymentTransId}. See Implementation reference for the full parameter list.
charge(...) returns a TransactionResult only when the payment is approved. It has a single field, paymentTransId. Store it alongside your order record, because it’s the identifier you use to find or reconcile the transaction later.
Handle the outcome
When charge(...) throws a TapToPayError, check its capture value to decide what to do next:
.notCharged: The card wasn’t charged. You can retry..unknown: The outcome isn’t known. Look uperror.paymentTransIdwith Get transaction details before you charge again. When there’s no ID, find the transaction in the Payabli Portal..charged: The card was charged, but a later step failed. Don’t charge again. Reconcile the payment.
These error types need their own handling: .cardDeclined (ask for another card), .paymentOutcomeUnknown (look up the transaction, and don’t retry), .devicePendingActivation (enter an activation code), and .termsNotAccepted (present Apple’s terms). See Errors for the full list.
Testing
Tap to Pay requires a physical device to test. There are two ways to test Tap to Pay in your app:
- NFC test cards. The recommended option during development and testing. A physical card is easier to use than a second device, because you don’t have to unlock a phone and tap it for every test payment. Check with your Payabli representative for supported test card vendors and ordering details.
- A second device with a test wallet card. Create an Apple Pay sandbox tester account, sign in to a second iPhone with it, then add test cards to Wallet on that device. Tap the provisioned card against the device running your app. This option is free and works well for routine development. You should still use test cards before you finish your integration.
Next steps
Tap to Pay documents the full PayabliTTP implementation reference, plus a complete example app you can clone and run.
Related resources
See these related resources to help you get the most out of Payabli.
Prerequisites
- Tap to Pay overview - Learn how Tap to Pay lets your app accept contactless, card-present payments with no external hardware