With our card reader solution you can accept mobile in-person payments using a card reader as the payment interface, and process these payments on the Adyen payments platform.
The card reader is paired with an iOS mobile device through Bluetooth. On the iOS mobile device, payment requests are initiated from a POS app. On the card reader, the customer can tap, insert, or swipe their card, or use a digital wallet like Apple Pay.
Requirements
Before you begin, take into account the following requirements, limitations, and preparations.
Requirement | Description |
---|---|
Integration type | Your POS app must be integrated with Terminal API. |
API credentials | You need the following API credentials:
|
Webhooks | To learn the outcome of refunds, set up Standard webhooks (if this hasn't been done already). |
Hardware | You need the following hardware:
|
Limitations | Check the countries/regions, payment methods, and functionality that we support for card reader on iPhone. |
Setup steps | Before you begin: |
How it works
Tutorial and app
Hit the ground running with our integration tutorial and iOS sample app.
To build a card reader solution:
- Add the iOS Mobile SDK to your project, either using basic authentication or using a GitHub access token.
Note that for compatibility with Objective C, you need to prefix the public symbols with
ADY
. - Implement a server-to-server API request to establish a secure communication session.
- In your POS app, enable the transaction functionality of the SDK.
- From your POS app, call the warm-up function to speed up initiating transactions.
- In your POS app, add permissions for pairing the mobile device with the card reader.
- Get an entitlement from Apple to suppress Apple Pay in your iOS POS app when your app is in the foreground. Configure your Xcode project accordingly.
- Optionally, you can configure the Mobile SDK to avoid reconnection delays between the card reader and the mobile device.
- You enable the device management screens built into the Mobile SDK or build a custom UI for device management.
-
In your POS app, implement handling payments using the SDK.
This creates the following flow:- Your iOS POS app creates a Terminal API payment request, or receives a Terminal API payment request from your backend.
- The POS app passes the payment request to the iOS Mobile SDK.
- The SDK passes the request to the Tap to Pay on iPhone component.
- When the customer completes the payment by tapping their card or mobile device on the iPhone, the SDK passes the Terminal API payment response to the POS app.
- In your POS app, implement handling refunds and diagnosing the state of the SDK.
- If the same device will be used at multiple locations, implement clearing the communication session.
1. Add the SDK to your project
You can add the iOS Mobile SDK to your POS app using a Swift Package Manager remote package. To get access you need to have a basic authentication credential if using SDK version 3.5.0 or later, or a GitHub access token for earlier versions.
Compatibility with Objective-C
If your POS app requires the iOS Mobile SDK to be compatible with Objective-C:
- Link the
ADYPOSTEST
package product to your app target instead ofAdyenPOSTEST
. - Link the
ADYPOSLIVE
package product to your app target instead ofAdyenPOSLIVE
.It is not possible to use TEST and LIVE environments from a single app target. Each app target must connect to a specific environment.
The integration process is the same. The only difference is that the public symbols are prefixed with ADY
. For example, PaymentService
is called ADYPaymentService
.
2. Establish a session
The Mobile SDK has to communicate in a secure way with the Adyen payments platform. For more information, see
To authenticate your server-to-server API request for establishing a communication session, you need to have an Adyen API credential in your test Customer Area. This credential must have a client key and an API key with the following role:
- Checkout webservice role. This role is assigned by default when the API key is created.
To add a client key to an existing API credential, create a client key as follows:
- Log in to your Customer Area.
- Go to Developers > API credentials, and select the credential username for your integration, for example ws@Company.[YourCompanyAccount].
- Under Client settings > Authentication select the Client key tab.
- Select Generate client key.
-
Select Save changes.
The client key is part of the setup but is not used later on. Therefore, you do not need to specify allowed origins, and you do not need to save the client key in your system.
To let your backend establish a session:
-
From your backend, make a POST /checkout/possdk/v68/sessions request, specifying:
Parameter Required Description merchantAccount The unique identifier of your merchant account. setupToken The setup token provided by the Mobile SDK through the . store The unique identifier of the store that you want to process payments for. -
When you receive the response:
- Check that you get a 201 Created HTTP status code.
- Return the sdkData to your POS app.
- If you create the Terminal API request on your backend, save the installationId and use this as the
POIID
in theMessageHeader
of the payment request.
3. Enable transactions
To enable the payments functionality of the Mobile SDK:
-
In your POS app, implement the
PaymentServiceDelegate
protocol. Below is an example of how you could do that.struct SessionsResponse: Decodable { let sdkData: String } class MyPaymentServiceDelegate: PaymentServiceDelegate { internal func register( with setupToken: String ) async throws -> String { /// Make a call to your backend to trigger a `/checkout/possdk/v68/sessions` request, specifying the `setupToken` provided by the SDK. let request = URLRequest(url: URL(string: "{ADDRESS_OF_YOUR_BACKEND_API}")!) let (data, _) = try await URLSession.shared.data(for: request) let response = try JSONDecoder().decode(SessionsResponse.self, from: data) return response.sdkData } }
The actual structure of the
SessionsResponse
depends on your backend implementation. -
Create an instance of
PaymentService
with thePaymentService(delegate:)
initializer and pass the delegate object. Make sure that you keep a strong reference to the payment service instance so that it is retained for the duration of the transaction. Also make sure yourdelegate
is strongly referenced, because thePaymentService
keeps a weak reference to thedelegate
.let paymentService = PaymentService(delegate: myPaymentServiceDelegate)
-
Make sure that the
PaymentServiceDelegate
can provide newsdkData
at any time.
If there is no session or the session has expired, the delegate is called using thePaymentServiceDelegate.register(with:)
callback. Using the providedsetupToken
you need to get thesdkData
through your backend and return it. For instructions, see Establish a session. -
Optional. Verify that the callback works, by calling the warm-up function.
The warm-up function checks for a session and any configuration changes, and prepares the proximity reader on the iPhone.
try await paymentService.warmUp()
4. Use the warm-up function
To speed up initiating transactions, you can use the warm-up function. This function checks for a session and any configuration changes
As a best practice, call the warm-up function:
- When the POS app starts. In other words, as soon as the app has the active state.
- When the POS app returns to the active state after running in the background.
To call the warm-up function:
try await paymentService.warmUp()
Similar to the warm-up function, you can optionally prepare the card reader to perform hardware configuration updates and security checks outside the transaction flow.
5. Configure your POS app
For your card reader solution you need to configure your POS app to:
Manage pairing permissions
Your iOS POS app needs to have certain permissions for pairing the mobile device with the card reader.
- In the
Info.plist
of your POS app, add the following keys with an appropriate explanation:-
NSBluetoothAlwaysUsageDescription
: this enables setting up a Bluetooth pairing between the mobile device and the card reader. -
NSCameraUsageDescription
: this enables using the camera of the mobile device to scan the barcode of the card reader. Barcode scanning is one of the ways to select a card reader for pairing.
-
Suppress Apple Pay
To accept payments from customers, you may need to suppress Apple Pay on the device that runs your mobile POS app.
It is possible that Apple Pay is enabled on the Apple device that runs your iOS POS app. For example, if staff use their personal iPhone. When a card reader is near, the device running the POS app tries to make the payment using the Apple Pay passes that are on the device.
To prevent this from happening, you must suppress Apple Pay in your iOS POS app when your app is in the foreground.
- Contact Apple Pay at [email protected] and ask for an entitlement to suppress Apple Pay.
- When you receive confirmation that the entitlement was granted, add the entitlement to your provisioning profile on the Apple developer website.
- In Xcode, in the Signing & Capabilities settings, add the
com.apple.developer.passkit.pass-presentation-suppression
key to the .entitlements file for your POS app. -
In your POS app, call requestAutomaticPassPresentationSuppression.
This method automatically switches between disabling Apple Pay when the POS app is in the foreground, and enabling Apple Pay when the POS app is in the background.
6. (Optional) Avoid delays
You can avoid delays and speed up the transaction by:
- Waking up the card reader after it has gone to sleep.
- Preparing the card reader outside a transaction flow with configuration and security checks.
Wake up the card reader
The card reader goes to sleep after about five minutes. When you start a transaction after the reader has gone to sleep, there is a slight delay while the mobile device reconnects to the last connected reader. To avoid this delay, you can take either of the following measures:
- Before starting the transaction, wake up the card reader by calling the DeviceManager
connect
function. - Regularly refresh the connection between the mobile device and the card reader by implementing a timer that calls the
connect
function every couple of minutes.Be aware that implementing a timer can significantly decrease the battery life of the card reader.
Call the connect
method as follows, where viewModel
is a class that contains an instance of the SDK's PaymentService
(for an example, see our sample app):
Button("Connect to last known reader") {
if let device = viewModel.paymentService.deviceManager.knownDevices.first {
viewModel.paymentService.deviceManager.connect(to: device)
}
}
Prepare the card reader for transaction
When you start a transaction, the Mobile SDK connects with the card reader through Bluetooth to check the reader's configuration. If the configuration is not up to date, the Mobile SDK sends the latest configuration files to the reader. This can take up to to 30 seconds. Additionally, the Mobile SDK performs a security check every 24 hours. To avoid a delay during the transaction flow, you can prepare the card reader by running these checks outside the transaction flow.
It is possible to start a transaction while the card reader preparation is running. There can be a short delay during the transaction while any remaining checks and configuration updates are completed.
We recommend preparing the device for transaction every 24 hours or when the configuration has changed.
To prepare the device outside a transaction flow:
-
Call the DeviceManager
prepareDeviceForTransaction
function as follows, where theconnectedDevice
is the card reader to prepare.// Register delegate paymentService.deviceManager.delegate = self // Start device preparation if let connectedDevice = paymentService.deviceManager.connectedDevice { paymentService.deviceManager.prepareDeviceForTransaction(connectedDevice) }
- When the preparation of the card reader is finished, the
deviceManager
delegate is called withonDevicePreparationFinished
or anerror
message.// Called when preparation finished func onDevicePreparationFinished(with error: Error?, by manager: DeviceManager) { // Handle result of preparation }
7. Manage the UI
To use the card reader, store staff needs to:
- Pair the mobile device running the Mobile SDK with the card reader.
- See an overview of card readers. For example, to switch to a different card reader.
- View details of the card reader they are using. For example, to check the battery charge level.
- Update the firmware of the card reader they are using.
To handle the device pairing and viewing of device details, you can:
- Use the built-in UI trough SwiftUI
- Use the built-in UI through UIKit
- Build a custom UI
Choose the tab that fits your preferred UI option.
8. Handle a payment
In this step you add code to start a transaction with:
-
A Terminal API payment request.
Tip
To help you create Terminal API requests, we provide a TerminalAPIKit for iOS on GitHub. Installation and usage instructions are in the repository's README. - The card reader as the payment interface to use.
-
The presentation mode you want to use.
To enable the iOS Mobile SDK to handle transactions:
-
In your POS app, create a Terminal API payment request with:
-
MessageHeader.POIID
: the installation ID of the SDK.- If you create the Terminal API diagnosis request in your POS app, use
PaymentService.installationId
as thePOIID
in the MessageHeader of the request. - If you create the Terminal API diagnosis request in the backend, this uses the installationId from the
/checkout/possdk/v68/sessions
response.
- If you create the Terminal API diagnosis request in your POS app, use
-
The remaining
MessageHeader
parameters and the request body.For details, see Make a payment and PaymentRequest.
-
-
Create an instance of
Payment.Request
usingPayment.Request(data:)
, and pass the Terminal API payment request from your POS app or backend.let transaction = try Payment.Request(data: requestData)
-
Get a
PaymentInterface
from an instance ofPaymentService
, usingPaymentService.getPaymentInterface(with: .cardReader)
.let paymentService = PaymentService(...) let paymentInterface = try paymentService .getPaymentInterface(with: .cardReader)
-
Specify a
TransactionPresentationMode
value that matches the UI framework, SwiftUI or UIKit, of the POS app.Value Description viewModifier
For use with a SwiftUI application. The UI is embedded in a View
as aViewModifier
.presentingViewController
For use with a UIKit application. The UI is presented on top of the provided UIViewController
.Optionally use parameters to customize the user interface.
Parameter Description logo
A bitmap image to show on your mobile device during the transaction flow. To ensure visibility in both dark mode and light mode, the bitmap image must have a transparent background. The logo is placed in a frame with a vertical height of 40 points and scaled to aspect ratio to fit in that frame. successScreenTimeout
Indicates how long the SDK shows the screen that indicates the transaction succeeded. If not specified, this success screen is dismissed after four seconds. You can set a time in seconds as a Double with a minimum of 0.5 seconds and a maximum of 4 seconds. Using
viewModifier
(SwiftUI)If you use
TransactionPresentationMode
withviewModifier
:-
Set
presentationMode
as follows.let presentationMode: TransactionPresentationMode = .viewModifier
-
Apply the presentation mode on your SwiftUI view.
Button(...) { // code to start the transaction }) .transactionModal( with: {YOUR_INSTANCE_OF_PAYMENT_SERVICE} logo: logo, parameters: .init(successScreenTimeout: 2) )
Using
presentingViewController
(UIKit)If you use
TransactionPresentationMode
withpresentingViewController(_:logo:parameters:)
, setpresentationMode
as follows.let presentationMode: TransactionPresentationMode = .presentingViewController( rootViewController, logo: logo, parameters: .init(successScreenTimeout: 2) )
-
-
Invoke
PaymentService.performTransaction(with:paymentInterface:presentationMode:)
on your instance ofPaymentService
.let transactionResponse = await paymentService.performTransaction( with: transaction, paymentInterface: paymentInterface, presentationMode: presentationMode )
The Mobile SDK checks for a session, starts the transaction, and shows screens on your mobile device to help the customer.
-
Check the
paymentResponse
. This is the Terminal API response with the transaction result and the data you can use to generate a receipt, or with any errors. -
Pass the
paymentResponse
to your POS app.
9. Handle a refund
There are two types of refund: referenced and unreferenced. The main difference is that a referenced refund is connected to the original payment, and an unreferenced refund isn't. That makes unreferenced refunds a bit riskier. For an overview of the differences, see Refund a payment.
Refunds are usually not processed synchronously. When you send a request for a referenced or unreferenced refund, the Terminal API response only confirms we received the request.
We inform you about the outcome of the refund asynchronously, through a webhook.
- For a referenced refund, we return a CANCEL_OR_REFUND webhook.
- For an unreferenced refund, we return a REFUND_WITH_DATA webhook.
Depending on the card scheme and country/region where the card is used, unreferenced refunds are sometimes processed synchronously. In that case the Terminal API response includes an acquirerResponseCode
to indicate the outcome.
To learn the outcome of a refund, you need to set up webhooks.
Handle a referenced refund
The Terminal API request for a referenced refund is a reversal request. The SDK contains a dedicated function for this.
In your iOS POS app, add code for the following steps:
-
Create a Terminal API reversal request with:
-
MessageHeader.POIID
: the installation ID of the SDK.- If you create the Terminal API diagnosis request in your POS app, use
PaymentService.installationId
as thePOIID
in the MessageHeader of the request. - If you create the Terminal API diagnosis request in the backend, this uses the installationId from the
/checkout/possdk/v68/sessions
response.
- If you create the Terminal API diagnosis request in your POS app, use
-
The remaining
MessageHeader
parameters and the request body.For details, see Referenced refund and ReversalRequest.
-
-
Create an instance of
Reversal.Request
usingReversal.Request(data:)
, and pass the Terminal API reversal request from your POS app or backend.let request = try Reversal.Request(data: requestData)
-
Invoke
PaymentService.performReversal(with:)
on your instance ofPaymentService
.let reversalResponse = await paymentService.performReversal(with: request)
The Mobile SDK now checks for a session, and starts the transaction.
-
Check the
reversalResponse
. This is the Terminal API response with the transaction result and the data you can use to generate a receipt, or with any errors. -
Pass the
reversalResponse
to your POS app.
Handle an unreferenced refund
The Terminal API request for an unreferenced refund is a payment request with an additional parameter:
PaymentData.PaymentType
: Refund
This means you can use the same code as for handling a payment. The only difference is the structure of the Terminal API payment request that you pass as the requestData
to the Payment.Request
.
For the structure of the Terminal API request, see Unreferenced refund.
10. Diagnose the device
We recommend implementing the Terminal API diagnosis request. This enables you to do the following:
- Check if there are stored offline payments that have not been forwarded to Adyen yet. The SDK will try to go online and forward these transactions. Note that you do not need to send a diagnosis request to forward offline transactions, because forwarding happens automatically whenever the internet connection is restored after it had dropped.
-
Check for security threats that would block transactions. If threats are detected that an operator can solve, the response includes information about the cause and solution of the problem.
-
Check the expiry date of the SDK that is used on the mobile device. Transactions will be blocked if mandatory updates are not carried out.
In your iOS POS app, add code for the following steps:
-
Create a Terminal API diagnosis request with:
-
MessageHeader.POIID
: the installation ID of the SDK.- If you create the Terminal API diagnosis request in your POS app, use
PaymentService.installationId
as thePOIID
in the MessageHeader of the request. - If you create the Terminal API diagnosis request in the backend, this uses the installationId from the
/checkout/possdk/v68/sessions
response.
- If you create the Terminal API diagnosis request in your POS app, use
-
The remaining
MessageHeader
parameters. -
The request body consisting of
DiagnosisRequest.HostDiagnosisFlag
: set to true, so that the SDK will try to forward any stored offline transactions and perform a security scan.
For details, see Diagnose a Mobile SDK solution and DiagnosisRequest.
-
-
Call the
performDiagnosis
function, using therequest
parameter to pass the Terminal API diagnosis request.public func performDiagnosis( with request: Diagnosis.Request ) async -> Diagnosis.Response { await diagnosisPerformer.performDiagnosis(with: request) }
-
When you get the Terminal API
DiagnosisResponse
, check theResponse.AdditionalResponse
:-
See the
unconfirmedBatchCount
for the number of stored offline transactions that have not been forwarded to theAdyen payments platform yet. -
Base64-decode the
storeAndForwardStatus
value for more information about offline transactions.
-
Base64-decode the
attestationStatus
value for information about any security issues. If issues are detected that can be resolved by the end user, the resulting JSON object includes messages with the details. These are the same error messages that we show automatically on the end user's mobile device when these issues are detected during a transaction. -
See the
sdkExpiry
for the date when the installed SDK version expires.
For a detailed explanation of the response, see Diagnose a Mobile SDK solution.
-
11. (Optional) Clear the session token
If the same device is used at multiple locations, you need to ensure that transactions are logged for the correct location.
The request to create a secure communication sessions with Adyen is made from the backend, for example for store A. If the device is moved to store B and a transaction is started there, on the Adyen side the transaction will appear to belong to store A instead of store B. You can prevent this by clearing the existing session and establishing a new one.
When the device is moved to a different location:
- Explicitly clear the communication session using
PaymentService.resetSession()
. - Establish a new communication session.
Other supported features
In addition to payments, refunds, and diagnosis, the Mobile solutions support other (payment) features. These are the same features that are supported in Terminal API integrations using Adyen-provided payment terminals.
For some features you need to add parameters to your Terminal API payment request, similar to unreferenced refunds described above. Other features only require enabling the feature for your Adyen account. You can find the details on the pages dedicated to those features. Where the details differ between an integration using payment terminals and a mobile solution, this is clearly indicated.
Feature | Supported with Card reader iOS |
---|---|
Diagnosis | ![]() |
Partial authorization | ![]() |
Payment | ![]() |
Pre-authorization | ![]() |
Refund, referenced | ![]() |
Refund, unreferenced | ![]() |
Store and forward offline payments | ![]() |
Surcharge | ![]() |
Tax-free shopping | ![]() |
Test your solution
To make test transactions:
-
Make sure you are using the test version of the Mobile SDK.
-
Initiate a test transaction using the following Adyen point-of-sale test cards to complete the payment:
- White-green test card
- Blue-green test card version 2.4 or later
The instructions are the same for both cards; see either of the pages mentioned above.
Go live
When you have finished testing your integration and are ready to go live:
-
If new to Adyen, get a live account. You need to have access to your organization's live Customer Area to generate API credentials for the live environment.
-
Get the live SDK. You need to generate a new, live basic authentication credential exclusively for downloading the SDK.
-
Use the live endpoint for establishing a session. To access the live endpoint, you need to generate a new, live API key that is different from the API key used for downloading the SDK.
Get the live SDK
This step is only necessary if you used basic authentication to add the SDK to your test integration. If you used a GitHub access token, you do not need to change anything because you have already added the relevant dependency to your app target that connects to the live environment.
To pull in the live version of the SDK:
- Generate a new basic authentication credential in your live Customer Area and add it to your
.netrc
file. - In your
.netrc
file, change the value formachine
topos-mobile.cdn.adyen.com
. - In your Xcode project or workspace, remove the TEST dependency and add the LIVE dependency using the URL
https://github.com/Adyen/adyen-pos-mobile-ios
. - Add
AdyenPOSLIVE
to your app target that connects to the Adyen Live environment.
If your POS app requires the iOS Mobile SDK to be compatible with Objective-C:
- Link the
ADYPOSLIVE
package product to your app target instead ofAdyenPOSLIVE
.
Establish a live session
When going live, you must change the /sessions
endpoint as well as the API key that you use to authenticate /sessions
requests.
- To access the live endpoint, generate a new API key from your live Customer Area.
-
The live endpoint URL contains a prefix which is unique to your company account, for example:
https://{PREFIX}-checkout-live.adyenpayments.com/checkout/possdk/v68/sessions
Get your
{PREFIX}
from your live Customer Area under Developers > API URLs > Prefix.