Tap to Pay
Tap to Pay is Payabli’s native mobile component for accepting contactless, card-present payments in your app. It reads the payor’s card, phone, or watch over NFC directly, so your app never touches raw card or bank data. See Tap to Pay overview for how the payment flow works conceptually, or Pay In Payment Flow if you’re looking for Payabli’s card/ACH component instead.
Right now, only iOS is supported.
Prerequisites
Before you start, confirm you have:
- A physical device. An iPhone XS or newer running iOS 16.7 or later.
- Payabli sandbox credentials. A Payabli sandbox
entryPointwith Tap to Pay enabled. Contact your Payabli representative if you don’t have one yet. - Your app registered on the paypoint allowlist. Your app’s
appId(<TEAM_ID>.<BUNDLE_ID>) must be authorized on the entrypoint 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 PayabliTTP.
Installation
Install PayabliSDKTapToPay from the sdk-ios Swift package:
Add the package
In Xcode, choose File → Add Packages… and enter the sdk-ios repository URL:
Or add it to your Package.swift dependencies:
Backend token endpoint
The Tap to Pay component authenticates every request with a short-lived Payabli access token, obtained through a tokenProvider closure you implement.
Your backend holds the Payabli clientId and clientSecret, exchanges them for the access token, and never exposes them to your mobile app.
Create the project
This example server uses JavaScript with Node.js and Express.
Create a new Node.js project and enable ES modules:
Implementation walkthrough
This walkthrough builds a minimal Tap to Pay charge, from constructing PayabliTTP through handling the result:
/// Add the import and token provider
Import PayabliSDKTapToPay and add a function that exchanges credentials with your backend for a Payabli access token, the same way the manual entry flow does.
/// Create the PayabliTTP instance
Construct PayabliTTP with your entryPoint, the app’s appId (<TEAM_ID>.<BUNDLE_ID>), and your token provider. The appId must already be authorized on the entrypoint’s allowlist.
/// Initialize and handle first-time activation
Call initialize() before the first charge. On a device’s first launch, it throws PayabliTTPError.devicePendingActivation until the partner backend delivers an activation code and your app calls activateDevice(activationCode:).
/// Charge a payment
Call charge(type:paymentDetails:) when ttp.isReady is true. Hold the device near the customer’s card, phone, or watch when prompted.
This walkthrough covers the component-level constructor, initialize(), and charge(). For the full Tap to Pay setup (paypoint registration, Apple entitlements, and device activation), see Accept Tap to Pay payments.
Implementation reference
PayabliTTP and charge(...) are the two types you construct and call directly. There’s no hosted form for Tap to Pay.
PayabliTTP constructor
A valid short-lived bearer token issued by your backend. Used for the first request.
Invoked to get a new token after a 401 Unauthorized response. Concurrent refresh attempts are deduplicated.
The entrypoint slug provisioned by Payabli.
<TEAM_ID>.<BUNDLE_ID>. Must already be authorized on the entrypoint’s allowlist.
.sandbox or .production. Must match the app’s appattest-environment entitlement.
charge(...) parameters
The charge(...) method returns a TransactionResult, which has a single field: paymentTransId. Use it to track and reconcile the payment.
Must be .sale.
properties
Must be greater than 0.
Omit to authorize in the merchant’s configured processor currency.
Cardholder snapshot, persisted at /initiate. Defaults to anonymous. Every field is optional; blank values are ignored.
properties
Payabli’s internal customer ID, when the customer is already registered.
Invoice metadata, persisted at /initiate.
properties
Invoice reference forwarded to the backend and the processor.
Optional free-form description forwarded to the backend.
Errors
PayabliTTPError covers the Tap to Pay session and charge lifecycle. Catch specific cases before the general case so you can respond appropriately:
If updateFailed occurs, the transaction may still be authorized by the processor, but PayabliTTPError.updateFailed(reason:) only carries a failure reason, not the transaction ID. initiateFailed, in contrast, means no transaction was created at all, so it’s always safe to retry.
Capturing paymentTransId
Because updateFailed doesn’t carry paymentTransId, capture it earlier by subscribing to ttp.events() and storing the ID from .chargeInitiated(paymentTransId:), which fires as soon as /initiate succeeds, before any card is read:
Example app
The sdk-ios repository includes PayabliDemo, a SwiftUI example app that exercises PayabliPayInPaymentFlow and PayabliTTP (card/ACH capture and Tap to Pay), alongside a bundled local Node.js token server for testing.
Configure your secrets
Copy the secrets template, then fill in your sandbox identifiers and configure Pay In Payment Flow to use the bundled local token server:
/// Set your sandbox identifiers
Copy Secrets.swift.sample to Secrets.swift, then set your sandbox entryPoint and appId (<TEAM_ID>.<BUNDLE_ID>):
/// Configure Pay In Payment Flow to use the local token server
fetchPaymentMethodAccessToken() calls partnerPaymentMethodAccessTokenEndpoint. Set it to the bundled LocalTokenServer so the Pay In Payment Flow tabs work without a real backend:
Start the local token server
In a separate terminal, start the bundled LocalTokenServer:
Edit .env and set PAYABLI_ACCESS_TOKEN to a short-lived sandbox access token, then start the server:
See the LocalTokenServer README for the credential-exchange mode and physical-device networking notes.
Run the app
Open PayabliDemo.xcodeproj in Xcode, select the PayabliDemo scheme, and run it. The Pay In Payment Flow tabs work in the Simulator. Tap to Pay requires a physical device. See Requirements.
The example app assumes a sandbox entryPoint with card, ACH, and Tap to Pay enabled. Contact your Payabli representative if your sandbox paypoint doesn’t have these enabled yet.
Related resources
See these related resources to help you get the most out of Payabli.
Prerequisites
- Mobile components overview - Learn how Payabli’s native mobile SDK components let you accept payments in your iOS app