Skip to main content

Apple Tap to Pay

Apple Tap to Pay is used like any other reader in the Payments SDK. When the device supports Tap to Pay and its installer is registered, it appears in Scanning as an available reader.

Prerequisites

  • Add TapToPayHardware from https://bitbucket.org/geopagos-sdk/ios-taptopaysdk-package.git using Swift Package Manager.
  • Run the app on iOS 16.7 or later. Latest iOS version is recommended.
  • Add the com.apple.developer.proximity-reader.payment.acceptance entitlement to the app target.
  • Enable Tap to Pay for the integrating app in the Apple portal.
important

Contact Geopagos to learn how to enable the entitlement on your Apple account and how to obtain the token required by Apple Tap to Pay.

warning

App Transport Security (ATS) is mandatory for apps integrating Apple Tap to Pay. Make sure ATS stays enabled in your app's Info.plist and that no exceptions (NSAllowsArbitraryLoads, insecure NSExceptionDomains, etc.) are added for the domains your app uses to communicate with its backend. ATS is a process-level setting read from the host app's Info.plist; it cannot be configured or enforced by the SDK on the integrator's behalf.

Design and marketing resources

Before submitting your app for review, make sure to follow Apple's guidelines for how Tap to Pay on iPhone should look and be marketed to your merchants:

Security certification

App developers who use the Geopagos's Payments SDK to add Tap to Pay on iPhone to their app shouldn't require additional PCI certifications or other security evaluations specific to Tap to Pay on iPhone.

Register the reader

Import the Tap to Pay module and add its installer to PaymentsConfiguration:

import Payments
import TapToPayHardware

let tapToPayInstaller = TapToPayReaderInstaller(
tokenProvider: { reason in
switch reason {
case .setup:
// Provide a token, force account linking if required.
return .provide(token, requiresRelink: false)
case let .rejected(rejection):
// Obtain a new token or cancel the connection.
return .cancel
}
},
onAccountLinkingRequired: { reason in
// Show your app's explanatory message before Apple presents its terms.
return .confirm
},
onReaderNotice: { notice in
// Update your UI or telemetry with the informational notice.
}
)

let configuration = try PaymentsConfiguration.Builder(
endpoint: URL(string: GEOPAGOS_ENDPOINT)!,
readerInstallers: [tapToPayInstaller]
).build()

PaymentsSDK.configure(paymentsConfiguration: configuration)

After creating the installer, call isTapToPaySupported() to check whether the current device supports Tap to Pay.

var readerInstallers: [ReaderInstaller] = [
/* Other readers */
]

let tapToPayInstaller = TapToPayReaderInstaller(
tokenProvider: { _ in .provide(token, requiresRelink: false) },
onAccountLinkingRequired: { _ in .confirm },
onReaderNotice: { _ in }
)

if tapToPayInstaller.isTapToPaySupported() {
readerInstallers.append(tapToPayInstaller)
}

Prepare the connection

Preparing the Tap to Pay connection may take several seconds. You must call TapToPayReaderInstaller.prepareConnection before starting a transaction so that this process does not delay checkout. It's recommended to prepare the connection right after app startup whenever Tap to Pay is available.

important

Keep and reuse the same TapToPayReaderInstaller instance that you registered on SDK initialization.

If prepareConnection is called while a transaction is in progress the callback will return .fail(.busy, ...).

Clear the connection

Use clearConnection to end a prepared Tap to Pay connection. After it succeeds, you can call prepareConnection again. The SDK then requests a new token from your token provider. Calling clearConnection when no connection is prepared, or on a device that does not support Tap to Pay, immediately reports .success. If preparation, clearing, or a transaction is already in progress, the callback reports .fail(.busy, ...). Wait for the current operation to finish, then retry.

important

The token used to prepare the connection determines the terminal configuration for subsequent transactions. In markets that operate across multiple countries or currencies, you should clearConnection and then call prepareConnection to supply a new token for current country/currency configuration.

Provide the token

The SDK requests a token when the connection with Apple begins:

public typealias TokenProvider = @Sendable (TokenRequestReason) async -> TokenRequest

TokenRequestReason is .setup for the initial connection. If Apple rejects a supplied token, the SDK requests another token with .rejected(.expired) or .rejected(.invalid(_)). It keeps requesting a token until a valid one is supplied or the connection is canceled.

Return one of the following values:

public enum TokenRequest {
case provide(String, requiresRelink: Bool)
case cancel
}

Use requiresRelink: false for most cases. A merchant must accept Apple terms only once. If you need to force the merchant to re-link the account use requiresRelink: true.

Return .cancel when the app cannot provide a token. The connection flow will stop and the SDK will emit a connection error.

Handle account linking

After validating the token, Apple may require the merchant account to be linked. The SDK calls:

public typealias AccountLinkingHandler =
@Sendable (AccountLinkingReason) async -> AccountLinkingDecision

Apple requires the app to inform the merchant that the Apple Tap to Pay terms and conditions will be shown. Show your own alert or informative message first:

func handleAccountLinking(
reason: AccountLinkingReason
) async -> AccountLinkingDecision {
switch reason {
case .setup:
// Explain that Apple terms will be displayed.
return .confirm
case .deniedByUser:
// Explain that the merchant rejected Apple's terms.
// Return .confirm to show the terms again, or .cancel to stop.
return .confirm
}
}

Return .confirm only after the merchant agrees to view the terms. The SDK then presents the Apple terms and conditions. Return .cancel if the merchant declines; the connection flow stops and the SDK emits a connection error.

AccountLinkingReason.deniedByUser means the merchant rejected the Apple terms and conditions in the Apple sheet. The integrator can explain that accepting the terms is required and return .confirm to retry the Apple flow. Return .cancel when the merchant does not want to retry.

Present merchant-education content

Apple's Tap to Pay on iPhone Human Interface Guidelines recommend showing merchants a "How to Tap" overlay that explains how contactless payments work. Geopagos's SDK does not present this automatically. After the Apple terms and conditions acceptance, call presentTapToPayDiscovery whenever your app wants to show it. Please note that showing this into you app is mandatory.

tapToPayInstaller.presentTapToPayDiscovery(on: viewController) { result in
switch result {
case .success:
break
case let .fail(error, message):
// Handle the failure.
}
}
important

viewController must be the topmost presented view controller at the time of the call. If not then the presentation fails.

public enum TapToPayDiscoveryResult: Sendable {
case success
case fail(TapToPayDiscoveryError, message: String?)
}

public enum TapToPayDiscoveryError: Sendable, Equatable {
case osUpdateRequired
case unknown
}
  • osUpdateRequired: the device is running an unsupported iOS version. Prompt the merchant to update iOS.
  • unknown: an unexpected error, message carries a diagnostic description sent by Apple, log it.

Receive reader notices

onReaderNotice is informative:

public enum TapToPayReaderNotice {
case merchantBlocked
case deviceBanned
case connectivityIssue
case nfcDisabled
case osUpdateRequired
case entitlementMissing
}

Use it to notify the merchant or record diagnostics:

  • merchantBlocked: Apple has blocked the merchant account.
  • deviceBanned: Apple has blocked the device.
  • connectivityIssue: Apple detected a connectivity problem.
  • nfcDisabled: NFC is disabled while attempting to read.
  • osUpdateRequired: the device needs an iOS update. Prompt the merchant to update iOS before retrying.
  • entitlementMissing: the app is missing the Tap to Pay on iPhone entitlement.

Transaction flow

After the reader is connected, follow the standard transaction flow. When your listener provides ReadConfig with the amount to collect, Apple displays its native, non-customizable card-reading screen. Apple manages this screen completely, including PIN entry when required.

The card-reading screen expires 40 seconds after Apple shows it to prompt the customer to hold their card near the reader. If the customer doesn't tap in time, Apple shows a Payment Timeout alert at the bottom of the screen; the customer can tap Try Again to reactivate the reader without restarting the transaction.

important

For performance reasons, the SDK delivers the card read notice as soon as Apple returns it, without waiting for its native card-reading screen to finish closing. This means SDKTransactionState.confirmPayment, .confirmRefund, or .confirmCancel (depending on the transaction type) may be called while that screen is still dismissing in the background.

If your confirmCallback/rejectCallback handling only confirms or rejects the transaction, no special handling is needed. If it also drives your own UI (e.g. installments selection), account for the possibility that Apple's screen has not fully dismissed yet, to avoid overlapping UI or navigation issues.

warning

Apple's encrypted card payload remains valid for up to 120 seconds after it leaves the device's Secure Element. No more than 120 seconds should elapse between the card read and sending the transaction to Geopagos — for example, while showing an installments selection screen in confirmCallback/rejectCallback. If this time window is exceeded, the transaction expires and a new card read is required.

After Apple's screen closes, the SDK continues the normal transaction processing flow.