Apple Tap To Pay SDK
Apple Tap to Pay SDK — Technical Integration Guide
Overview
The NI Apple Tap to Pay SDK (ATTP SDK) enables iOS applications to accept contactless payments directly on iPhone — no external card reader required. The SDK wraps Apple's Tap to Pay on iPhone framework and communicates exclusively with NI's Digital Platform (DP) APIs to handle authentication, payment acquisition, and transaction management.
Supported payment methods: Contactless credit/debit cards (Visa, Mastercard), Apple Pay, and other digital wallets.
Regional availability: United Arab Emirates (UAE) only.
Device requirements:
- iPhone XS or later (iPhone 11+ recommended)
- iOS 18.0 or later (beta iOS versions are not supported)
- iCloud sign-in active
- Face ID, Touch ID, or passcode configured
- Screen lock passcode set
Integration Flow
The SDK has three primary operation sequences:
- Authorization + proximity reader initialization —
prepare()authenticates using a device token (attpAccessToken) and fetches aproximityReaderToken(valid 48 hours). - Payment acquisition —
processPayment()uses the active device token to execute a purchase. - Transaction management —
getTransactions()/getTransactionDetails()/refundTransaction()/voidTransaction()for post-payment operations.
Onboarding Prerequisites
Before any integration work, complete the following platform setup via the NI portal:
- Configure hierarchy — Set up merchant, outlet(s), MID, TID, channel, and device.
- Configure SoftPOS settings — Enable SoftPOS in the outlet/device settings.
- Enable SoftPOS proposition service — Activate the SoftPOS proposition service for the merchant.
- Create an API key — Generate an API key via a service account and distribute it to the merchant.
Step 1: Get Access Token
Exchange the apiKey for a short-lived bearer access token (expires in 8 hours).
Request
POST {API_GATEWAY_URL}/identity/auth/access-token
| Header | Value |
|---|---|
| Content-Type | application/vnd.ni-identity.v1+json |
| Accept | application/vnd.ni-identity.v1+json |
| Authorization | Bearer {api_key} |
Request Body
{}Response (200 OK)
{
"access_token": "<bearer_token>",
"expires_in": 28800,
"token_type": "bearer"
}Step 2: Get Device Token (attpAccessToken)
The device token identifies the merchant/outlet/device in the DP hierarchy. This is the authToken parameter passed to all SDK methods.
Request
POST {API_GATEWAY_URL}/identity/outlets/{outletRef}/devices/auth
| Header | Value |
|---|---|
| Content-Type | application/vnd.ni-identity.v1+json |
| Accept | application/vnd.ni-identity.v1+json |
| Authorization | Bearer {fabrick_service_token} |
Request Body
{
"deviceCode": "<device_code>",
"userCode": "<user_code>",
"tid": "<tid>",
"deviceIdentifier": "<deviceIdentifier>"
}Response (200 OK)
{
"access_token": "<bearer_token>",
"expires_in": 28800,
"token_type": "bearer"
}Note: Use this
access_tokenvalue asauthTokenin all SDK method calls.
Obtaining userCode, deviceCode, and TID for Testing
To generate an attpAccessToken for testing, use the Postman collection (NI_attpAccessToken.postman_collection.json) with the NI UAT.postman_environment.json environment:
- Trigger Generate Fabrick token → copy
access_tokenfrom response. - Open the
NI UATenvironment, paste the value intofabrick_access_token(current value) and save. - Open Generate Device token, set the body parameters:
userCode— Search Fabrick Wallet Service logs for"FB REST Response GET"+"/users". TakeuserCodefrom any user in the array. Alternatively: navigate to Shift Management (user icon, top-right) in the app.deviceCode— Search Fabrick Wallet Service logs for"/terminals". TakevirtualTerminalCodefrom any terminal. Alternatively: navigate to Settings > Terminals in the app.tid— From the same terminal entry, takevirtualTerminalTid.
- Trigger the request → copy
access_tokenfrom response. This is yourattpAccessToken. - For the test app: update
mockAuthTokeninViewModel.swiftand relaunch.
Step 3: Get Payment Card Reader Token
Called internally by the SDK during prepare(). Documented here for reference — you pass the device token to prepare() and the SDK handles this call automatically.
Request
POST {SDK_API_URL}/softpos/payment-card-reader-token
| Header | Value |
|---|---|
| Content-Type | application/vnd.ni-softpos.v1+json |
| Accept | application/vnd.ni-softpos.v1+json |
| Authorization | Bearer {device_token} |
Request Body
{
"currency": "AED"
}Response (200 OK)
{
"token": "<payment-card-reader-token>"
}Token expiry: 48 hours. After expiry, call
prepare()again to refresh.
iOS SDK Integration
Step 1: App Entitlements
Your app must have the Tap to Pay on iPhone entitlement from Apple Developer. Apply via your Apple Developer account, then add the following to your app's .entitlements file:
| Key | Type | Value |
|---|---|---|
com.apple.developer.proximity-reader.payment.acceptance | Boolean | true |
Ensure your provisioning profile includes this entitlement.
Step 2: Embed the SDK Framework
- Open your project in Xcode.
- Go to Project Settings > General.
- Scroll to Frameworks, Libraries, and Embedded Content.
- Click + and select
AppleTapToPaySDK.xcframework. - Set Embed to "Embed & Sign".
The framework must be embedded and signed. Linking without embedding will cause a runtime failure.
Step 3: Initialize AppleTapToPayManager
Create one instance of AppleTapToPayManager at app launch before processing any payments. All SDK operations are performed through this instance.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
serverScheme | String | Yes | — | "https" or "http" |
serverHost | String | Yes | — | Digital Platform host address |
serverPort | Int | Yes | — | Server port (typically 443) |
serverBasePath | String | Yes | — | Base path for API endpoints |
locationTrackingEnabled | Bool | No | false | Enable location tracking |
logEnabled | Bool | No | false | Enable debug logging |
Example (Swift)
let tapToPayManager = AppleTapToPayManager(
serverScheme: "https",
serverHost: "api-gateway.ngenius-payments.com",
serverPort: 443,
serverBasePath: "/softpos",
locationTrackingEnabled: true,
logEnabled: true
)
tapToPayManager.onError = { error in
switch error {
case .deviceNotSupported:
print("Device does not support Tap to Pay.")
case .initializationError(let message):
print("SDK init failed: \(message)")
default:
print("Unknown error: \(error)")
}
}Initialization Errors
| Error Code | Description | Resolution |
|---|---|---|
deviceNotSupported (100) | Device does not support NFC/Tap to Pay | Ensure iPhone XS or later |
initializationError (101) | Internal network client failed to initialize | Check errorMessage for details |
Step 4: Check SoftPOS Compatibility
Call isSoftPOSSupported() before any Tap to Pay operation to confirm the device meets hardware requirements.
switch tapToPayManager.isSoftPOSSupported() {
case 0:
print("Device supports SoftPOS.")
case -2:
print("Device does not support SoftPOS.")
default:
print("Unknown status.")
}Return Values
| Value | Meaning |
|---|---|
0 | Device is compatible (iPhone XS or newer) |
-2 | Device is too old to support Apple Tap to Pay |
Step 5: Prepare the SDK (Reader Initialization)
prepare() must be called before processing any payment. It fetches and caches the proximity reader token, handles account linking and Terms & Conditions presentation if needed, and creates a payment card reader session.
Method signature
prepare(
authToken: String,
currencyCode: String,
onProgress: ((Int) -> Void)?,
onSuccess: () -> Void,
onError: (AppleTapToPaySDKError) -> Void
)Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
authToken | String | Yes | Device token (attpAccessToken) |
currencyCode | String | Yes | ISO 4217 currency code (e.g., "AED") |
onProgress | ((Int) -> Void)? | No | Reader session creation progress (1–100) |
onSuccess | () -> Void | Yes | Called when SDK is ready to process payments |
onError | (AppleTapToPaySDKError) -> Void | Yes | Called on failure |
Example (Swift)
await tapToPayManager.prepare(
authToken: deviceToken,
currencyCode: "AED",
onProgress: { progress in
print("Preparing: \(progress)%")
},
onSuccess: {
print("SDK ready. You can now process payments.")
},
onError: { error in
print("Prepare failed: \(error.localizedDescription)")
}
)Common errors from prepare()
| Error Code | Code # | Description |
|---|---|---|
invalidAuthToken | 102 | Token missing or expired |
networkError | 110 | Could not connect to Digital Platform |
readerTokenNotAvailable | 501 | Reader token could not be fetched |
termsAndConditionsError | 520 | User must accept Terms & Conditions |
Step 6: Check Account Linking
Before the first payment, verify that the merchant account is linked (Terms & Conditions accepted).
Method signature
isAccountLinked(
authToken: String,
currencyCode: String,
onSuccess: (Bool) -> Void,
onError: (AppleTapToPaySDKError) -> Void
)Example (Swift)
tapToPayManager.isAccountLinked(
authToken: deviceToken,
currencyCode: "AED",
onSuccess: { isLinked in
if isLinked {
print("Account linked — ready for payments.")
} else {
print("Account not linked. Prompt T&C acceptance.")
}
},
onError: { error in
print("Account link check failed: \(error.localizedDescription)")
}
)If isLinked returns false, the merchant must:
- Obtain a JWT from NI (includes merchant identifier).
- Call
linkAccount(using:)on aPaymentCardReaderinstance. - The system presents the Terms & Conditions UI — merchant must accept.
Once accepted, the account stays linked across device switches.
Payment Operations
Purchase (Direct Payment)
processPayment() initiates a contactless payment. Internally calls POST /softpos/payment/card/purchase.
Method signature
processPayment(
authToken: String,
paymentType: PaymentType,
amount: String,
currencyCode: String,
dryRun: Bool,
onProgress: ((Int) -> Void)?,
onSuccess: (PaymentResponse) -> Void,
onError: (AppleTapToPaySDKError) -> Void
)Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
authToken | String | Yes | Device token |
paymentType | PaymentType | Yes | Payment type (e.g., .contactless) |
amount | String | Yes | Amount in microcurrency (no decimal — e.g. "12550" for AED 125.50) |
currencyCode | String | Yes | ISO 4217 (e.g., "AED") |
dryRun | Bool | No | true = test mode, no real transaction. Default: false |
onProgress | ((Int) -> Void)? | No | Progress updates (1–100) |
onSuccess | (PaymentResponse) -> Void | Yes | Called with payment result |
onError | (AppleTapToPaySDKError) -> Void | Yes | Called on failure |
Amount formatting
| Currency | Standard | Microcurrency (pass as amount) |
|---|---|---|
| AED 125.50 | 125.50 | "12550" |
| USD 10.00 | 10.00 | "1000" |
Example (Swift)
tapToPayManager.processPayment(
authToken: deviceToken,
paymentType: .contactless,
amount: "12550",
currencyCode: "AED",
dryRun: false,
onProgress: { progress in
print("Payment progress: \(progress)%")
},
onSuccess: { response in
print("Payment successful.")
print("Reference: \(response.reference)")
print("Order reference: \(response.orderReference)")
print("State: \(response.state)")
},
onError: { error in
print("Payment failed: \(error.localizedDescription)")
}
)processPayment() Error Codes
| Error Code | Code # | Description |
|---|---|---|
deviceNotSupported | 100 | Device older than iPhone XS |
initializationError | 101 | SDK or network client not initialized |
invalidCurrencyCode | 202 | Currency code is not a valid 3-character ISO string |
invalidAmount | 203 | Transaction amount is negative |
readerTokenNotAvailable | 501 | Reader token could not be retrieved |
readerTokenInvalid | 510 | Reader token is invalid or missing |
termsAndConditionsError | 520 | Merchant has not accepted Terms & Conditions |
processPaymentReadError | 530 | Card reader UI dismissed or failed to present |
invalidPaymentProcessResponse | 210 | DP response could not be parsed into PaymentResponse |
networkUnauthorizedError | 111 | HTTP 401/403 — authentication failure |
networkError | 110 | General network failure |
genericError | 1 | Unexpected error — check errorMessage |
Auth + Capture (Two-Step Payment)
Note: The
/authoriseendpoint is available directly at the DP level but is not exposed through the SDK'sprocessPayment()method. Use this flow for server-side integration or scenarios requiring deferred capture.
Auth Request
POST {SDK_API_URL}/softpos/payment/card/authorise
Authorization: Bearer {device_token}
Content-Type: application/vnd.ni-softpos.v1+json
{
"language": "EN",
"locale": "",
"tid": "<tid>",
"payment": "<CARD_ENV_ENCRYPTED_DATA>"
}Auth Response (200 OK)
{
"reference": "d2994a07-da67-49dc-8bf1-f3c4173b7329",
"state": "AUTHORISED",
"updateDateTime": "2023-07-19T10:08:23.725Z",
"amount": {
"currencyCode": "AED",
"value": 2500
},
"outletId": "87f2ebb4-6301-4b13-b59e-ea15ecc07b43",
"orderReference": "c4155842-84b1-4025-8890-f9a6b5c14f32",
"authResponse": {
"authorizationCode": "448430",
"success": true,
"resultCode": "00",
"resultMessage": "Successful approval/completion",
"mid": "434334344242"
}
}Capture Request
Use orderReference and paymentReference from the auth response.
POST {SDK_API_URL}/softpos/payment/card/orders/{orderRef}/payments/{paymentRef}/capture
Authorization: Bearer {device_token}
Content-Type: application/vnd.ni-softpos.v1+json
{
"amount": {
"currencyCode": "AED",
"value": 1000
}
}Capture Response (200 OK)
{
"reference": "d2994a07-da67-49dc-8bf1-f3c4173b7329",
"state": "PARTIALLY_CAPTURED",
"amount": {
"currencyCode": "AED",
"value": 2500
},
"orderReference": "c4155842-84b1-4025-8890-f9a6b5c14f32",
"_embedded": {
"cnp:capture": [
{
"amount": { "currencyCode": "AED", "value": 200 },
"createdTime": "2023-07-19T10:10:30.687Z",
"state": "SUCCESS"
}
]
}
}Transaction Management
Retrieve Transactions List
getTransactions() fetches a paginated list of transactions for the merchant associated with the device token. Maps to GET /reporting/payments internally.
Method signature
getTransactions(
limit: Int,
offset: Int,
authToken: String,
onSuccess: (GetTransactionsResponse) -> Void,
onError: (AppleTapToPaySDKError) -> Void
)Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | Int | Yes | Max records to return |
offset | Int | Yes | Starting index (0-based) |
authToken | String | Yes | Device token |
Example (Swift)
tapToPayManager.getTransactions(
limit: 20,
offset: 0,
authToken: deviceToken,
onSuccess: { response in
print("Loaded \(response.items.count) transactions")
response.items.forEach { txn in
print("\(txn.reference) — \(txn.status) — \(txn.amount)")
}
},
onError: { error in
print("Failed to load transactions: \(error.errorMessage)")
}
)GetTransactionsResponse
{
"items": [
{
"reference": "txn_12345",
"createdDateTime": "2024-02-18T12:34:56Z",
"action": "PURCHASE",
"currencyCode": "AED",
"amount": "100.00",
"refundedAmount": null,
"status": "SUCCESS",
"channel": "SoftPOS",
"outletName": "Merchant A",
"cardScheme": "VISA",
"cardholderName": "John Doe",
"cardPan": "**** **** **** 1234",
"lastEvent": "Completed",
"rrn": "123456789",
"authCode": "987654"
}
]
}Error Codes
| Error Code | Code # | Description |
|---|---|---|
initializationError | 101 | Network client not initialized |
networkError | 110 | Network failure |
invalidAuthToken | 102 | Auth token invalid or expired |
dataParsingError | 310 | Could not decode GetTransactionsResponse |
genericError | 1 | Unexpected error |
Retrieve Transaction Details
getTransactionDetails() fetches order-level details including whether refund or void is available. Maps to GET /reporting/orders internally.
Method signature
getTransactionDetails(
reference: String,
authToken: String,
onSuccess: (GetTransactionDetailsResponse) -> Void,
onError: (AppleTapToPaySDKError) -> Void
)Example (Swift)
tapToPayManager.getTransactionDetails(
reference: "txn_123456",
authToken: deviceToken,
onSuccess: { details in
print("Can refund: \(details.refundEnabled)")
print("Can void: \(details.voidEnabled)")
},
onError: { error in
print("Details fetch failed: \(error.localizedDescription)")
}
)SDK Response
{
"refundEnabled": true,
"voidEnabled": false
}Get Order Details API (Full Response)
For complete order data including capture history, payment info, and event log, call the DP reporting API directly:
GET {SDK_API_URL}/softpos/reporting/orders/{orderRef}
Authorization: Bearer {device_token}
Accept: application/vnd.ni-softpos.v1+json
Response (200 OK)
{
"reference": "616241a9-c4ba-42e2-a35d-db2ac48213bd",
"action": "AUTH",
"amount": {
"currencyCode": "AED",
"value": 10100,
"formattedValue": "AED101.00"
},
"status": "OPEN",
"channel": "SoftPOS",
"createDateTime": "2023-08-25T09:11:59.314Z",
"outletId": "87f2ebb4-6301-4b13-b59e-ea15ecc07b43",
"outletName": "Testuser",
"device": {
"reference": "cd516042-59dc-4951-aa06-6275a2a38434",
"code": "000C69",
"name": "iPhoneX"
},
"tid": "434334344299",
"paymentCards": [
{
"expiry": "2025-12",
"cardholderName": "Test Card",
"paymentMethod": "MASTERCARD",
"paymentReference": "f9682e21-8853-4bbf-915b-7e38948dda2c",
"cardScheme": "MASTERCARD",
"paymentCurrency": "AED",
"paymentAmount": 10100,
"maskedPanNumber": "511111******1118"
}
],
"paymentInfo": {
"authorised": { "value": 10100, "currencyCode": "AED", "minorUnit": 2 },
"refunded": { "value": 0, "currencyCode": "AED", "minorUnit": 2 },
"captured": { "value": 6600, "currencyCode": "AED", "minorUnit": 2 },
"net": { "value": 6600, "currencyCode": "AED", "minorUnit": 2 },
"pending": { "value": 3500, "currencyCode": "AED", "minorUnit": 2 }
},
"captureStatus": "PARTIALLY_CAPTURED",
"capturesNRefunds": [
{
"eventGroupType": "PARTIAL_CAPTURE",
"eventName": "PARTIALLY_CAPTURED",
"status": "SUCCESS",
"timestamp": "2023-08-25T09:12:19.123Z",
"payload": {
"reference": "616241a9-c4ba-42e2-a35d-db2ac48213bd",
"amount": { "currencyCode": "AED", "value": 3300 },
"paymentProcessor": "MPGS"
},
"action": "VOID_CAPTURE"
}
],
"_embedded": {
"events": []
}
}getTransactionDetails() Error Codes
| Error Code | Code # | Description |
|---|---|---|
networkError | 110 | Network failure |
networkUnauthorizedError | 111 | HTTP 401/403 |
genericError | 1 | Unexpected error |
Refund a Transaction
Maps to POST /payment/card/orders/{orderReference}/actions/refund internally.
Method signature
refundTransaction(
authToken: String,
transactionId: String,
amount: String,
currencyCode: String,
onSuccess: (RefundTransactionResponse) -> Void,
onError: (AppleTapToPaySDKError) -> Void
)Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
authToken | String | Yes | Device token |
transactionId | String | Yes | Transaction reference to refund |
amount | String | Yes | Refund amount in microcurrency |
currencyCode | String | Yes | ISO 4217 (e.g., "AED") |
Example (Swift)
tapToPayManager.refundTransaction(
authToken: deviceToken,
transactionId: "txn_123456",
amount: "12550",
currencyCode: "AED",
onSuccess: { response in
print("Refund ID: \(response.refundId), Status: \(response.status)")
},
onError: { error in
print("Refund failed: \(error.localizedDescription)")
}
)Response
{
"refundId": "rfn_987654",
"status": "SUCCESS"
}Error Codes
| Error Code | Code # | Description |
|---|---|---|
networkError | 110 | Network failure |
invalidTransactionId | — | Transaction ID not found |
invalidAmount | 203 | Refund amount exceeds original |
networkUnauthorizedError | 111 | HTTP 401/403 |
dataParsingError | 610 | Response parsing failure |
genericError | 1 | Unexpected error |
Void a Transaction
Maps to POST /payment/card/orders/{orderReference}/actions/cancel internally.
Method signature
voidTransaction(
authToken: String,
transactionId: String,
onSuccess: (VoidTransactionResponse) -> Void,
onError: (AppleTapToPaySDKError) -> Void
)Example (Swift)
tapToPayManager.voidTransaction(
authToken: deviceToken,
transactionId: "txn_123456",
onSuccess: { response in
print("Void ID: \(response.voidId), Status: \(response.status)")
},
onError: { error in
print("Void failed: \(error.localizedDescription)")
}
)Response
{
"voidId": "vld_987654",
"status": "SUCCESS"
}Error Codes
| Error Code | Code # | Description |
|---|---|---|
networkError | 110 | Network failure |
invalidTransactionId | — | Transaction ID not found |
networkUnauthorizedError | 111 | HTTP 401/403 |
dataParsingError | 710 | Response parsing failure |
genericError | 1 | Unexpected error |
Unbind Device TID Mapping
If a TID was previously mapped to an older device or SDK installation that has since been uninstalled, you must unbind it before re-registering.
Request
DELETE {SDK_API_URL}/softpos/outlets/{outletRef}/devices/tids/{tid}/unbind
Authorization: Bearer {device_token}
Content-Type: application/vnd.ni-softpos.v1+json
Response
HTTP 204 NO_CONTENT
No response body on success.
Complete Error Code Reference
SDK Errors
| Code | Description |
|---|---|
| 1 | Generic error — check error message for details |
| 100 | Device does not support Apple Tap to Pay |
| 101 | Initialization error — check error message |
| 102 | Invalid auth token |
| 110 | Generic network communication error |
| 111 | Request unauthorized (HTTP 401/403) |
| 112 | Proposition service not enabled |
| 113 | Invalid PAN (Primary Account Number) |
| 114 | Unsupported card scheme |
| 115 | Invalid Terminal ID (TID) |
| 116 | Unsupported order action |
| 118 | Amount limit exceeded |
| 119 | Operation cannot be performed |
Payment Processing Errors
| Code | Description |
|---|---|
| 201 | Invalid transaction type |
| 202 | Invalid currency code |
| 203 | Invalid transaction amount (e.g., negative) |
| 210 | Invalid payment response from Digital Platform |
Transaction Retrieval Errors
| Code | Description |
|---|---|
| 301 | Transactions cannot be retrieved |
| 310 | Invalid transaction response from server |
Refund Errors
| Code | Description |
|---|---|
| 610 | Invalid refund transaction response from server |
Void Errors
| Code | Description |
|---|---|
| 710 | Invalid void transaction response from server |
Proximity Reader Errors
| Code | Description |
|---|---|
| 501 | Unable to retrieve reader token from Digital Platform |
| 510 | Invalid reader token provided |
| 515 | Account linking check failed |
| 516 | Configuration error during PaymentCardReader preparation |
| 520 | Error during Terms & Conditions presentation |
| 530 | Card reader UI dismissed or failed to show |
Location Tracking Errors
| Code | Description |
|---|---|
| 801 | Location manager not initialized |
| 802 | User denied location tracking permission |
| 803 | Error receiving location updates |
| 810 | Error retrieving country (reverse geocoding failed) |
Best Practices
Pre-warm the reader session. Call prepare() at app launch or when the payment screen becomes visible — before the merchant needs to tap. This eliminates reader initialization delay at checkout.
Cache and refresh the device token. The device token expires in 8 hours. Implement token refresh logic and pass the refreshed token to each SDK method call.
Reader token expiry. The payment card reader token is valid for 48 hours. Track the expiry and call prepare() before it lapses to avoid readerTokenNotAvailable errors mid-payment.
Check compatibility first. Always call isSoftPOSSupported() at launch. If the return value is -2, disable the payment feature and surface an appropriate message before the user reaches checkout.
Follow Apple's Human Interface Guidelines for Tap to Pay on iPhone. Promotional content must align with Apple's official branding standards.
Dry run testing. Set dryRun: true in processPayment() to run integration tests without posting real transactions to the Digital Platform.
FAQ
Is Tap to Pay on iPhone secure?
Yes. Card numbers and PINs are never stored on the device or sent to Apple's servers. For transactions above the contactless limit, PIN entry occurs directly on the iPhone — Apple prevents screenshots or screen recording during this step.
Which payment methods are supported?
Contactless credit and debit cards (Visa, Mastercard), Apple Pay (iPhone and Apple Watch), and other NFC digital wallets. Cards must display the contactless symbol.
Is there a maximum transaction amount?
No fixed cap. Transactions above the contactless limit prompt PIN entry on the iPhone.
Is Tap to Pay available outside the UAE?
No. This integration is currently UAE-only.
What if a TID is already mapped to an old device?
Call the Unbind API (DELETE /softpos/outlets/{outletRef}/devices/tids/{tid}/unbind) to remove the old mapping before registering the new device.
Can I use this SDK on iOS beta?
No. Tap to Pay on iPhone is not supported on iOS beta versions.
Updated about 2 hours ago
