Terminal-2 icon

Tap to Pay on iPhone

Integrate your POS app with the iOS Mobile SDK to make Tap to Pay on iPhone payments.

With our Tap to Pay on iPhone solution you can accept contactless in-person payments using an iPhone as the payment interface, and process these payments on the Adyen payments platform.

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:
  • To get the SDK, you must have either an API credential with a basic authentication password and the Allow SDK download for POS developers role, or a GitHub access token.
  • To establish a communication session, you must have an API credential with an API key, a client key, and the Checkout webservice role.
Webhooks To learn the outcome of refunds, set up Standard webhooks (if this hasn't been done already).
Hardware An iPhone. See iOS system requirements for the full hardware and software requirements.
Limitations Check the countries/regions, payment methods, and functionality that we support for Tap to Pay on iPhone.
Setup steps Before you begin:
  • Ask our Support Team to configure your account for Tap to Pay on iPhone payments.
  • Order a test card.

How it works

Tutorial and app
Hit the ground running with our integration tutorial and iOS sample app.

To build a Tap to Pay on iPhone solution:

  1. Get an entitlement from Apple for Tap to Pay on iPhone and configure your Xcode project accordingly.

  2. 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.

  3. Implement a server-to-server API request to establish a secure communication session.

  4. In your POS app, enable the transaction functionality of the SDK.

  5. From your POS app, call the warmup function to speed up initiating transactions.

  6. In your POS app, implement handling payments using the SDK.

    This creates the following flow:

    1. Your iOS POS app creates a Terminal API payment request, or receives a Terminal API payment request from your backend.
    2. The POS app passes the payment request to the iOS Mobile SDK.
    3. The SDK passes the request to the Tap to Pay on iPhone component.
    4. 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.

  7. In your POS app, implement handling refunds using the SDK.

  8. If the same device will be used at multiple locations, implement clearing the communication session.

1. Get an entitlement from Apple

To add Tap to Pay functionality to your iOS POS app through the iOS Mobile SDK, you need to apply for a specific entitlement and then configure your Xcode project to use the entitlement.

  1. Make sure that you meet the requirements for submitting an entitlement request:

    • An Organization level Apple Developer account.
    • The Account Holder role for that Apple Developer account.

  2. Fill out the form to get the entitlement.

  3. After you receive the entitlement to support Tap to Pay on iPhone, follow Apple's instructions to configure your Xcode project to use it.

2. 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.

This flow only works for iOS Mobile SDK version 3.5.0 or later. For earlier versions, choose the tab Use a GitHub access token to add the SDK.

You can share the basic authentication password with anybody in your team who needs to get the SDK or create multiple passwords for internal use.

Do not share this password in any publicly accessible code or area where unauthorized users can find it.

  1. In your Customer Area create a basic authentication credential with a password:

    1. Go to Developers > API credentials, and select Create new credential.
    2. Under Payments > Credential type select Web service user and then select Create credential.
    3. Under Server settings > Authentication select the Basic auth tab and then select Generate password.
    4. Select the copy icon and save your basic authentication password in a secure location.
    5. Go to Permissions > Roles > POS, select Allow SDK download for POS developers, and deselect any other permissions and roles.

      Contact Support Team if you don't see the Allow SDK download for POS developers role.

    6. Select Save changes.
  2. Save the basic authentication password in a .netrc file:

    1. Check if you already have a .netrc file in your home directory. If you do not have it, create a plain text file with that name in your home directory (~/.netrc).

    2. Add the following content to your .netrc file, where login is the API credential username, for example ws_12345@Company.[YourCompanyAccount], and password is the basic authentication password.

    .netrc content for TEST integration
    Expand view
    Copy link to code block
    Copy code
    Copy code
    machine pos-mobile-test.cdn.adyen.com
    login YOUR_BASIC_AUTH_USERNAME
    password YOUR_BASIC_AUTH_PASSWORD
    1. Make sure that the .netrc file has the following file system permission: 0600.
  3. Add the POS Mobile SDK as a package dependency to your Xcode project:

    1. In your Xcode project or workspace, go to File > Add Package Dependencies.

    2. Enter the URL https://github.com/Adyen/adyen-pos-mobile-ios-test and select your preferred Dependency Rule.

    3. Select Add Package, and after the SDK is loaded add AdyenPOSTEST to your app target that connects to the Adyen test environment.

      It is not possible to use TEST and LIVE environments from a single app target. Each app target must connect to a specific environment.

    4. Select Add Package.

  4. In your code, add import AdyenPOS, or for Objective-C compatibility add #import "ADYPOS/ADYPOS.h".

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 of AdyenPOSTEST.
  • Link the ADYPOSLIVE package product to your app target instead of AdyenPOSLIVE.

    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.

3. Establish a session

The Mobile SDK has to communicate in a secure way with the Adyen payments platform. For more information, see Communication session.

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:

  1. Log in to your Customer Area.
  2. Go to Developers > API credentials, and select the credential username for your integration, for example ws@Company.[YourCompanyAccount].
  3. Under Client settings > Authentication select the Client key tab.
  4. Select Generate client key.
  5. 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:

  1. From your backend, make a POST /checkout/possdk/v68/sessions request, specifying:

    Parameter Required Description
    merchantAccount -white_check_mark- The unique identifier of your merchant account.
    setupToken -white_check_mark- The setup token provided by the Mobile SDK through the PaymentServiceDelegate.register(with:) callback of PaymentServiceDelegate.
    store The unique identifier of the store that you want to process payments for.
    /sessions request
    Expand view
    Copy link to code block
    Copy code
    Copy code
    curl https://checkout-test.adyen.com/checkout/possdk/v68/sessions \
    -H 'content-type: application/json' \
    -H 'x-API-key: ADYEN_API_KEY' \
    -X POST \
    -d '{
    "merchantAccount": "YOUR_MERCHANT_ACCOUNT",
    "setupToken": "SETUP_TOKEN",
    "store": "YOUR_STORE_ID"
    }'
  2. 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 the MessageHeader of the payment request.
    /sessions response
    Expand view
    Copy link to code block
    Copy code
    Copy code
    {
    "id": "APP_SESSION_ID",
    "installationId": "INSTALLATION_ID",
    "merchantAccount": "YOUR_MERCHANT_ACCOUNT",
    "store": "YOUR_STORE_ID",
    "sdkData": "SDK_DATA_BLOB"
    }

4. Enable transactions

To enable the payments functionality of the Mobile SDK:

  1. In your POS app, implement the PaymentServiceDelegate protocol. Below is an example of how you could do that.

    Copy code
    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.

  2. Create an instance of PaymentService with the PaymentService(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 your delegate is strongly referenced, because the PaymentService keeps a weak reference to the delegate.

    Copy code
    let paymentService = PaymentService(delegate: myPaymentServiceDelegate)
  3. Make sure that the PaymentServiceDelegate can provide new sdkData at any time.
    If there is no session or the session has expired, the delegate is called using the PaymentServiceDelegate.register(with:) callback. Using the provided setupToken you need to get the sdkData through your backend and return it. For instructions, see Establish a session.

  4. 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.

    Copy code
    try await paymentService.warmUp()

5. 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, and prepares the proximity reader on the iPhone. In addition, the warm-up function shows the Terms and Conditions for Tap to Pay if those haven't been accepted yet for the merchant account or store that uses the Mobile SDK.

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:

Copy code
try await paymentService.warmUp()

6. 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 iPhone as the payment interface to use.
  • The presentation mode you want to use.

To enable the iOS Mobile SDK to handle transactions:

  1. If you create the Terminal API payment request in your POS app, use PaymentService.installationId as the POIID in the MessageHeader of the payment request.
    Note that if you create the Terminal API payment request in the backend, this uses the installationId from the /checkout/possdk/v68/sessions response.

    For the structure of a Terminal API payment request see PaymentRequest.

  2. Create an instance of Payment.Request using Payment.Request(data:), and pass the Terminal API payment request from your POS app or backend.

    Copy code
    let transaction = try Payment.Request(data: requestData)
  3. Get a PaymentInterface from an instance of PaymentService, using PaymentService.getPaymentInterface(with: .tapToPay).

    Copy code
    let paymentService = PaymentService(...)
    let paymentInterface = try paymentService
    .getPaymentInterface(with: .tapToPay)
  4. 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 a ViewModifier.
    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 with viewModifier:

    1. Set presentationMode as follows.

      Copy code
      let presentationMode: TransactionPresentationMode = .viewModifier
    2. Apply the presentation mode on your SwiftUI view.

      Copy code
      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 with presentingViewController(_:logo:parameters:), set presentationMode as follows.

    Copy code
    let presentationMode: TransactionPresentationMode = .presentingViewController(
    rootViewController,
    logo: logo,
    parameters: .init(successScreenTimeout: 2)
    )
  5. Invoke PaymentService.performTransaction(with:paymentInterface:presentationMode:) on your instance of PaymentService.

    Copy code
    let transactionResponse = await paymentService.performTransaction(
    with: transaction,
    paymentInterface: paymentInterface,
    presentationMode: presentationMode
    )

    The Mobile SDK checks for a session and shows the transaction screen on your iPhone.

  6. 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.

  7. Pass the paymentResponse to your POS app.

7. 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:

  1. If you create the Terminal API reversal request in your POS app, use PaymentService.installationId as the POIID in the MessageHeader of the reversal request.
    Note that if you create the Terminal API reversal request in the backend, this uses the installationId from the /checkout/possdk/v68/sessions response.

    For the structure of a Terminal API referenced refund request see ReversalRequest.

  2. Create an instance of Reversal.Request using Reversal.Request(data:), and pass the Terminal API reversal request from your POS app or backend.

    Copy code
    let request = try Reversal.Request(data: requestData)
  3. Invoke PaymentService.performReversal(with:) on your instance of PaymentService.

    Copy code
    let reversalResponse = await paymentService.performReversal(with: request)

    The Mobile SDK now checks for a session, and starts the transaction.

  4. 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.

  5. 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.

8. (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:

  1. Explicitly clear the communication session using PaymentService.resetSession() .
  2. Establish a new communication session.

Other payment features

In addition to payments and refunds, 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 Tap to Pay on iPhone
Partial authorization -white_check_mark-
Payment -white_check_mark-
Pre-authorization -white_check_mark-
Refund, referenced -white_check_mark-
Refund, unreferenced -white_check_mark-
Store and forward offline payments -x-
Surcharge -white_check_mark-
Tax-free shopping -white_check_mark-

Follow UI guidelines

When using Tap to Pay on iPhone, the users of your POS app as well as the customers will interact with your app. There are some recommendations to follow for these interactions.

POS app user screens

It needs to be clear to users operating your POS app (like store staff) how they can activate Tap to Pay on iPhone.

  • Make the users aware they need to set up a device passcode to unlock the iPhone they use for Tap to Pay on iPhone.
  • Make sure that the user interface for Tap to Pay on iPhone is in accordance with Apple's best practices and presentation guidelines.
    For example:
    • For a button, use the label Tap to Pay on iPhone, or Tap to Pay if there is little space.
    • Apple Pay and Tap to Pay on iPhone are two different things. So if you use an icon, use the Tap to Pay icon, not the Apple Pay icon.

Approval screen

After reading a card, the iPhone automatically shows a Done screen. When the transaction is authorized, an Approved screen appears automatically. There is no need to show another approval screen. Instead move to the next step in the flow, which would be to get the receipt.

Test your solution

To make test transactions:

  1. Make sure you are using the test version of the Mobile SDK.

  2. Create a Sandbox Apple ID. This is needed to test Tap to Pay on iPhone in our test environment.

  3. Initiate a test transaction using the following Adyen point-of-sale test cards to complete the payment:

    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:

  1. 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.

  2. Get the live SDK. You need to generate a new, live basic authentication credential exclusively for downloading the SDK.

  3. 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.

  4. For Tap to Pay on iPhone, accept the Apple Terms of Use.

  5. Optionally set a custom transaction screen text.

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:

  1. Generate a new basic authentication credential in your live Customer Area and add it to your .netrc file.
  2. In your .netrc file, change the value for machine to pos-mobile.cdn.adyen.com.
  3. 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.
  4. 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 of AdyenPOSLIVE.

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.

Next steps