Search

Are you looking for test card numbers?

Would you like to contact support?

Marketpay icon

Point of sale for platforms

Process point-of-sale payments on behalf of your account holders.

Adyen for Platforms enables you to onboard account holders with point-of-sale (POS) integrations to accept in-store payments. To make this happen, you need to:

Before you begin

Before you start processing point-of-sale payments for your account holders, you need to:

Subscribe to store notifications

When you add a store for an account holder, we send a notification to let you know the store has been set up. To ensure you receive this notification:

  1. Make a POST request to the /createNotificationConfiguration endpoint, specifying:

    • active: true.
    • description: Your text to recognize the notification subscription by.
    • An eventConfigs array with eventType: ACCOUNT_HOLDER_STORE_STATUS_CHANGE and includeMode: INCLUDE.
    • notifyURL: Endpoint on your server where you'll receive the notifications.
    • hmacSignatureKey: Use this parameter if you want us to protect notifications with HMAC signatures. It specifies the HMAC key that we'll use to calculate the HMAC signatures of the notifications we send to the notifyURL. On your end you'll need to verify the signatures of the incoming notifications.
    • notifyUsername and notifyPassword: Username and password that we need for basic authentication with the server where you'll receive the notifications.
    • sslProtocol: Protocol used to secure the communication, such as TLS or SSL.
    /createNotificationConfiguration request
    curl https://cal-test.adyen.com/cal/services/Notification/v6/createNotificationConfiguration \
    -H "x-API-key: YOUR_X-API-KEY" \
    -H "content-type: application/json" \
    -d '{
      "configurationDetails":{
        "active": true,
        "description":"YOUR_UNIQUE_DESCRIPTION",
        "eventConfigs":[
          {
            "eventType":"ACCOUNT_HOLDER_STORE_STATUS_CHANGE",
            "includeMode":"INCLUDE"
          }
        ],
        "notifyURL":"https://www.merchant-domain.com/notification-handler",
        "hmacSignatureKey":"c6c608e9d2ec95889cb9757e6beb4956c187f44cf7bb7297b16880c0310af7ff",
        "notifyUsername":"YOUR_USERNAME",
        "notifyPassword":"YOUR_PASSWORD",
        "sslProtocol":"SSL"
      }
    }'
    Response
    {
      "pspReference": "8515681150749298",
      "configurationDetails": {
        "active": true,
        "description": "YOUR_UNIQUE_DESCRIPTION",
        "eventConfigs": [
          {
            "eventType": "ACCOUNT_HOLDER_STORE_STATUS_CHANGE",
            "includeMode": "INCLUDE"
          }
        ],
        "notificationId": 20337,
        "notifyURL": "https://www.merchant-domain.com/notification-handler",
        "sslProtocol": "SSLInsecureCiphers"
      }
    }
  2. Make sure your server accepts the notifications.
  3. Use the notificationId you receive in the response to test the notification.

Add a store to the account holder

If you're onboarding an account holder with a point-of-sale integration, you need to provide us with the details of their store. We will then set up the store to accept the configured payment methods. When we finish configuring the store, we send you a notification.

To provide the store details, include a storeDetails array when you create a new account holder or when you update an existing one:

  1. Make a POST request to one of the following endpoints:

    Situation Endpoint Specify
    There is no account holder for the sub-merchant yet. /createAccountHolder The usual parameters to create a business account holder, as well as businessDetails.taxId.
    You want to add a store to an existing account holder. /updateAccountHolder accountHolderCode: Your unique reference for the account holder. Include businessDetails.taxId, unless it is already present as part of businessDetails.
  2. In the request body, include an accountHolderDetails.storeDetails array, specifying:

    • address: The city, country, houseNumberOrName, postalCode, stateOrProvince, and street of the physical store where the account holder will process payments from.
    • fullPhoneNumber: The phone number of the store.
    • merchantAccount: The merchant account used for accepting payments.
    • merchantCategoryCode: The merchant category code (MCC) that classifies the business of the account holder.
    • storeName: The name of the account holder's store. This will be included in the shopper statement.
    • storeReference: Your unique reference for the account holder's store.

    The next example shows a /createAccountHolder request to create a new account holder with a store.

    /createAccountHolder request
    curl https://cal-test.adyen.com/cal/services/Account/v6/createAccountHolder \
    -H "x-API-key: YOUR_X-API-KEY" \
    -H "content-type: application/json" \
    -d '{
      "accountHolderCode": "YOUR_UNIQUE_ACCOUNT_HOLDER_CODE",
      "accountHolderDetails": {
        "address": {
            "country": "US"
        },
        "businessDetails": {
          "legalBusinessName": "Real Good Restaurant Inc.",
           "taxId": "444455555",
          "shareholders": [
            {
                "name": {
                  "firstName": "Maria",
                  "gender": "FEMALE",
                  "lastName": "Green"
                },
                "address": {
                  "country": "US"
                }
            }
          ]
        },
        "email": "maria@green.com",
        "storeDetails" : [
          {
            "storeReference": "YOUR_SUBMERCHANT_STORE_ID",
            "storeName": "Store Name",
            "merchantAccount": "YOUR_MERCHANT_ACCOUNT",
            "merchantCategoryCode": "7999",
            "address" : {
              "city": "Store City",
              "country": "US",
              "houseNumberOrName": "1",
              "postalCode": "123",
              "stateOrProvince": "CA",
              "street": "Store Street"
            },
            "fullPhoneNumber": "+31201234567"
          }
        ]
      },
      "legalEntity": "Business"
    }'

    This example shows how you would add a store to an existing account holder using an /updateAccountHolder call.

    /updateAccountHolder request
    curl https://cal-test.adyen.com/cal/services/Account/v6/updateAccountHolder \
    -H "x-API-key: YOUR_X-API-KEY" \
    -H "content-type: application/json" \
    -d '{
      "accountHolderCode": "YOUR_UNIQUE_ACCOUNT_HOLDER_CODE",
      "accountHolderDetails": {
        "storeDetails" : [
          {
            "storeReference": "YOUR_SUBMERCHANT_STORE_ID",
            "storeName": "Store Name",
            "merchantAccount": "YOUR_MERCHANT_ACCOUNT",
            "merchantCategoryCode": "7999",
            "address": {
              "city": "Store City",
              "country": "US",
              "houseNumberOrName": "1",
              "postalCode": "123",
              "stateOrProvince": "CA",
              "street": "Store Street"
             },
            "fullPhoneNumber": "+31201234567"
          }
        ]
      }
    }'

    The response contains various account holder details, including the storeDetails showing that the status of the store is Pending.

    Response
    {
       "invalidFields": [],
       ...
       "accountHolderDetails":{
          ...
          "storeDetails" : [
             {
                "address": {
                   "city": "Store City",
                   "country": "US",
                   "houseNumberOrName": "1",
                   "postalCode": "123",
                   "stateOrProvince": "CA",
                   "street": "Store Street"
                   },
                "fullPhoneNumber": "+31201234567",
                "merchantAccount": "YOUR_MERCHANT_ACCOUNT",
                "merchantCategoryCode": "7999",
                "status": "Pending",
                "store": "6db2bdbc-5e77-4388-9a6e-9e653905d3e5",
                "storeReference": "YOUR_SUBMERCHANT_STORE_ID",
                "storeName": "Store Name",
             }
          ],
          ...
       },
       ...
    }
  3. Before processing point-of-sale payments, wait until you receive an ACCOUNT_HOLDER_STORE_STATUS_CHANGE notification showing that the new status of the store is Active.

    Notification
    {
       "eventType":"ACCOUNT_HOLDER_STORE_STATUS_CHANGE",
       "eventDate":"2018-04-23T13:13:44+02:00",
       "executingUserKey":"ws",
       "live":true,
       "pspReference":"12312312313",
       "content":{
          "accountHolderCode":"YOUR_UNIQUE_ACCOUNT_HOLDER_CODE",
          "store": "6db2bdbc-5e77-4388-9a6e-9e653905d3e5",
          "storeReference":"YOUR_SUBMERCHANT_STORE_ID",
          "newStatus":"Active",
          "oldStatus":"Pending",
          "reason":"Test Reason"
       }
    }

Assign a terminal to the store

To process in-store payments, the account holder's store needs to have one or more payment terminals. The process is as follows:

  1. Select a terminal and inform the account holder of the communication requirements.
  2. Order a terminal if you don't have the required terminal in your inventory.
  3. Assign the terminal from your inventory to the store of the account holder in one of the following ways:

    • Assign terminals using your Customer Area
      or
    • Assign terminals in bulk using the Terminal Management API

      The advantage of using the Terminal Management API over the Customer Area, is that API calls enable you to automate assigning terminals and retrieving an overview of terminal assignments.

  4. Configure the terminals in the Customer Area, under Point of Sale > Terminal settings.
  5. Ship the terminal to the physical store address and instruct the account holder to board the terminal.

Make a split payment

To initiate an in-store payment, you make a Terminal API call. In the PaymentRequest you provide the split payment data in the SaleToAcquirerData, in URL-escaped or Base64-encoded format. This credits payments directly to an account holder's account.

To make the payment:

  1. Make a POST request to a Terminal API endpoint, specifying:

    • MessageHeader: The standard SaleToPOIRequest.MessageHeader object, explained in Terminal API fundamentals:
      Parameter Required Description
      ProtocolVersion -white_check_mark- 3.0
      MessageClass -white_check_mark- Service
      MessageCategory -white_check_mark- Payment
      MessageType -white_check_mark- Request
      SaleID -white_check_mark- Your unique ID for the cash register.
      ServiceID -white_check_mark- Your unique ID for this request, consisting of 1-10 alphanumeric characters. Must be unique within the last 48 hours.
      POIID -white_check_mark- Unique ID of the terminal that you want to route this request to. Format: [device model]-[serial number].
    • PaymentRequest: The request body. This must include:

      Parameter Required Description
      SaleData.SaleTransactionID -white_check_mark- An object with:
      • TransactionID: Your unique reference for this request. In your Customer Area and Adyen reports, this will show as the merchant reference for the transaction.
      • TimeStamp: Date and time of the request in UTC format.
      SaleData.SaleToAcquirerData Provides the split payment data as concatenated key-value pairs separated by an ampersand (&) character, or in Base64-encoded format. See the tables below for details.
      PaymentTransaction.AmountsReq -white_check_mark- An object with:
      • Currency: The transaction currency.
      • RequestedAmount: The transaction amount.
    • To provide the split payment data in PaymentRequest.SaleData.SaleToAcquirerData, use the following fields:

      Field Description Example
      split.api Version of the Split API: 1. split.api=1
      split.totalAmount Amount to be split, in minor units. Must be equal to the transaction amount and to the sum of the split amounts. split.totalAmount=62000
      split.currencyCode Currency of the amount to be split. split.currencyCode=EUR
      split.nrOfItems Number of times you will split the payment. Must match the number of split items in the request. split.nrOfItems=2

      Specify each split as a split.item{item#} starting at 1split.item1split.item2, and so on. Each split item includes:

      Field Description Example
      split.item{item#}.amount Amount of the split, in minor units. split.item1.amount=60000 and split.item2.amount=2000
      split.item{item#}.type Split type. A type of MarketPlace or VAT sends the amount to the account specified. A type of Commission or PaymentFee sends the amount to your liable account. split.item1.type=MarketPlace and split.item2.type=Commission
      split.item{item#}.account Account that will receive the split. This is the accountCode of one of your account holder's accounts or your own liable account. You don't need to specify an account when split.type is Commission.  split.item1.account=8815628270908491
      split.item{item#}.reference The reference for that specific transaction split, which is returned in our reporting. Required if the split.type is MarketPlace.  

    The next example shows how you would split a 620.00 EUR payment into 600.00 EUR to be paid into the account holder's account with accountCode 8815628270908491 and 20.00 EUR commission to be paid to the platform.

    Payment request with split data
    {
      "SaleToPOIRequest":{
        "MessageHeader":{
          "ProtocolVersion":"3.0",
          "MessageClass":"Service",
          "MessageCategory":"Payment",
          "MessageType":"Request",
          "SaleID":"POSSystemID12345",
          "ServiceID":"0207111104",
          "POIID":"V400m-324688179"
        },
        "PaymentRequest":{
          "SaleData":{
            "SaleTransactionID":{
              "TransactionID":"27908",
              "TimeStamp":"2019-03-07T10:11:04+00:00"
            },
            "SaleToAcquirerData": "split.api=1&split.nrOfItems=2&split.totalAmount=62000&split.currencyCode=EUR&split.item1.amount=60000&split.item1.type=MarketPlace&split.item1.account=8815628270908491&split.item1.reference=test1&split.item2.amount=2000&split.item2.type=Commission&split.item2.reference=TestCommission"
          },
          "PaymentTransaction":{
            "AmountsReq":{
              "Currency":"EUR",
              "RequestedAmount":620.00
            }
          }
        }
      }
    }

    The payment request is routed to the terminal, for the shopper to present their card and verify the payment. The payment is then sent to the Adyen payments platform for processing.

  2. Receive the payment result.

    If your integration uses asynchronous cloud communications, you receive the result in a TENDER_FINAL display notification.

    If the payment is successful:

    • Approved is displayed on the terminal screen.
    • You receive a payment response containing:
      • POIData.POITransactionID.TransactionID: Transaction identifier for the payment in the format tenderReference.pspReference. For example, oLkO0012498220087000.981517998282382C.
      • PaymentResponse.Response.Result: Success
      • PaymentResponse.Response.AdditionalResponse: A base64 string. When decoded, this is a JSON object with additional transaction data.
      • PaymentReceipt: Object containing data you can use to generate a receipt.

You can also view the details of a payment in your Customer Area, under Transactions > Payments.

Splitting at capture

Some platforms do not know the final split amounts at the moment the payment request is made. If this applies to your account holder's in-store payments, you need to enable manual capture for in-store payments and follow up each authorised Terminal API payment with a /capture API call. Splitting at capture is also the procedure to use for tipping (gratuity) and partial payments.

Enabling manual capture for in-store payments applies to all your point-of-sale account holders.

Proceed as follows:

  1. Enable manual capture for point-of-sale payments:

    1. Log in to your Customer Area.
    2. Switch to your merchant account.
    3. Go to Account > Settings.
    4. For the POS Capture Delay, select manual.
    5. Select Submit.
  2. Make a payment request to a Terminal API endpoint. You can include split data as described above, but these will be overridden by the capture request you'll do in the next step.

  3. When the payment is authorised, make a POST request to the /capture endpoint, specifying:

    • originalReference: The pspReference returned as part of the transaction identifier in the response to the Terminal API payment request.
    • reference: Optional reference for this capture. This will be shown in your Customer Area and reports.
    • merchantAccount: Your merchant account used for accepting payments.
    • modificationAmount: The value in minor units and currency of the amount being split. Must be the same amount as the original transaction.
    • splits: An array of split items, each including:

      • amount.value: Amount of the split, in minor units.
      • type: Split type. A type of MarketPlace or VAT sends the amount to the account specified. A type of Commission or PaymentFee sends the amount to your liable account.
      • account: Account that will receive the split. This is the accountCode of one of your account holder's accounts or your own liable account. You don't need to specify an account when split.type is Commission.
      • reference: An optional reference which is returned in our reporting. You can use this to refer to that specific transaction split.

    The next example shows how you would capture a 620.00 EUR authorisation with pspReference 981517998282382C, and split it into 600.00 EUR to be paid into the account holder's account with accountCode 8815628270908491 and 20.00 EUR commission to be paid to the platform.

    Capture request with split data
    curl https://pal-test.adyen.com/pal/servlet/Payment/v64/capture \
    -H "x-API-key: YOUR_X-API-KEY" \
    -H "content-type: application/json" \
    -d '{
      "originalReference":"981517998282382C",
      "reference":"YOUR_REFERENCE_TO_THIS_CAPTURE",
      "merchantAccount":"YOUR_MERCHANT_ACCOUNT",
      "modificationAmount":{
        "value":62000,
        "currency":"EUR"
      },
      "splits":[
        {
          "amount":{
            "value":60000
          },
          "type":"MarketPlace",
          "account":"8815628270908491",
          "reference":"YOUR_REFERENCE_TO_SPLIT_#1"
        },
        {
          "amount":{
            "value":200
          },
          "type":"Commission",
          "reference":"YOUR_REFERENCE_TO_SPLIT_#2"
        }
      ]
    }'

    If the capture request is successfully received, the response contains:

    • response: [capture-received]
    • pspReference: Our unique identifier for this capture request.

    Payment captures are performed asynchronously, and we inform you whether a capture request is sent for processing with a webhook notification. If the capture is successfully sent for processing, this notification contains:

    • eventCode: CAPTURE
    • originalReference: The pspReference of the authorisation.
    • pspReference: Our unique identifier for this capture request.
    • success: true

    If success is false then your capture request failed. Review the reason you received in the notification, fix the issue, and submit the capture request again.

See also