# How to Set Up POS Terminal Payments with Adyen in Salesforce Commerce Cloud

> Learn to set up POS terminal payments with Adyen in Salesforce Commerce Cloud. Integrate in-person payments via the Terminal API by configuring store IDs endpoints and credentials.

- Repository: [Adyen/adyen-salesforce-commerce-cloud](https://github.com/adyen/adyen-salesforce-commerce-cloud)
- Tags: how-to-guide
- Published: 2026-02-23

---

**Adyen’s Salesforce Commerce Cloud (SFCC) cartridge enables in-person payments through a service-driven flow that connects your storefront checkout to physical terminals via the Terminal API, requiring configuration of store IDs, regional endpoints, and service credentials before implementing the handle-authorize hook pattern.**

The `adyen/adyen-salesforce-commerce-cloud` repository provides a complete Point of Sale (POS) integration that bridges web checkout flows with Adyen-managed physical terminals. This implementation allows merchants to process **POS terminal payments with Adyen** directly from their SFCC storefront, creating a unified commerce experience across online and in-person channels. The integration relies on three architectural layers: configuration management, front-end terminal discovery, and back-end payment authorization.

## Understanding the POS Integration Architecture

The cartridge implements a layered architecture that separates concerns across configuration, API exposure, and payment processing. According to the source code in `adyen/adyen-salesforce-commerce-cloud`, the system organizes functionality into distinct components:

- **Configuration Layer**: Manages store identification and regional service endpoints via [`constants.js`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/constants.js) and [`services.xml`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/services.xml)
- **Front-End/API Layer**: Exposes terminal discovery through [`getConnectedTerminals.js`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/getConnectedTerminals.js) 
- **Back-End Payment Flow**: Handles instrument creation and authorization through [`posHandle.js`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/posHandle.js) and [`posAuthorize.js`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/posAuthorize.js)

This architecture ensures that terminal selection occurs at checkout, while sensitive payment processing happens server-side through the **AdyenPosPayment** service.

## Step 1: Enable and Configure the POS Payment Method

### Activate AdyenPOS in Business Manager

The cartridge registers the payment method using the constant `METHOD_ADYEN_POS` defined in [`src/cartridges/int_adyen_SFRA/cartridge/adyen/config/constants.js`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/src/cartridges/int_adyen_SFRA/cartridge/adyen/config/constants.js). To enable POS terminal payments, activate the `AdyenPOS` payment method in Business Manager. The system checks availability via `PaymentMgr.getPaymentMethod(constants.METHOD_ADYEN_POS).isActive()` before exposing the flow during checkout.

### Configure Store ID and Regional Endpoints

In the Business Manager interface (rendered via `posSettings.isml`), merchants must configure two critical parameters:

- **Store ID**: Matches the store entity configured in your Adyen Customer Area for Terminal API access
- **Terminal API Region**: Select from `US`, `EU`, `AU`, or `APSE` to determine the live endpoint target

These values persist through the `AdyenConfigs` helper and determine the service URL at runtime. The region selection maps to specific endpoints via constants like `POS_ENVIRONMENT_EU` or `POS_ENVIRONMENT_US` in [`constants.js`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/constants.js).

## Step 2: Retrieve Connected Terminals

When the checkout page loads, the client fetches available terminals through the `getConnectedTerminals` controller. Located at [`src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/pos/getConnectedTerminals.js`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/pos/getConnectedTerminals.js), this script validates the active store ID and queries Adyen's Connected Terminals endpoint:

```javascript
// src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/pos/getConnectedTerminals.js
function getConnectedTerminals(req, res, next) {
  const requestObject = {
    merchantAccount: AdyenConfigs.getAdyenMerchantAccount(),
    store: AdyenConfigs.getAdyenActiveStoreId()
  };
  
  const response = adyenTerminalApi.executeCall(
    constants.SERVICE.CONNECTEDTERMINALS,
    requestObject
  );
  
  res.json({ response: response.text });
}

```

The front-end populates a dropdown element (typically `select id="terminalDropdown"`) with the returned terminal IDs and models. Only terminals linked to the configured Store ID appear in this list.

## Step 3: Implement the Payment Flow

The payment execution follows SFCC's standard hook pattern with two middleware functions handling instrument creation and authorization.

### Creating the Payment Instrument

The [`posHandle.js`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/posHandle.js) middleware prepares the basket by removing existing payment instruments and creating a fresh one bound to the AdyenPOS method:

```javascript
// src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/hooks/payment/processor/middlewares/posHandle.js
function posHandle(basket) {
  Transaction.wrap(() => {
    collections.forEach(basket.getPaymentInstruments(), item => {
      basket.removePaymentInstrument(item);
    });
    
    const paymentInstrument = basket.createPaymentInstrument(
      constants.METHOD_ADYEN_POS,
      basket.totalGrossPrice
    );
    
    paymentInstrument.custom.adyenPaymentMethod = 'POS Terminal';
  });
  
  return { error: false };
}

```

This ensures a clean state before the authorization step and marks the instrument with the custom payment method identifier.

### Authorizing the Terminal Payment

When the shopper confirms the order, [`posAuthorize.js`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/posAuthorize.js) extracts the selected terminal ID from the form data and initiates the Terminal API call:

```javascript
// src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/hooks/payment/processor/middlewares/posAuthorize.js
function posAuthorize(order, paymentInstrument, paymentProcessor) {
  paymentInstrument.paymentProcessor = paymentProcessor;
  
  const form = server.forms.getForm('billing');
  const terminalId = form.adyenPaymentFields?.terminalId.value;
  
  if (!terminalId) {
    throw new AdyenError('No terminal selected');
  }
  
  return adyenTerminalApi.createTerminalPayment(
    order, 
    paymentInstrument, 
    terminalId
  );
}

```

### Building the Terminal API Request

The `createTerminalPayment` function in [`adyenTerminalApi.js`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/adyenTerminalApi.js) constructs a **SaleToPOIRequest** message conforming to the POS protocol specification:

```javascript
// src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/pos/adyenTerminalApi.js
function createTerminalPayment(order, paymentInstrument, terminalId) {
  const service = adyenService.getService(constants.SERVICE.POSPAYMENT);
  
  // Dynamic URL rewriting for live regions
  if (AdyenConfigs.getAdyenEnvironment() === constants.MODE.LIVE) {
    const regionEndpoint = AdyenHelper.getTerminalApiEnvironment();
    const serviceUrl = service.getURL().replace('[ADYEN-REGION]', regionEndpoint);
    service.setURL(serviceUrl);
  }
  
  const request = buildTerminalRequest(order, terminalId);
  const result = service.call(JSON.stringify(request));
  
  if (result.error) {
    sendAbortRequest(terminalId, service);
    return { error: true };
  }
  
  return parsePaymentResponse(result.object, order, paymentInstrument);
}

```

The request structure includes:
- **MessageHeader**: Protocol version, message class (`SERVICE`), category (`PAYMENT`), and unique `ServiceID`
- **PaymentRequest**: `SaleData` containing `SaleTransactionID` (order number), `SaleReferenceID`, and `SaleToAcquirerData`
- **AmountsReq**: Currency and amount extracted from the payment instrument

## Step 4: Configure Service Credentials

The **AdyenPosPayment** service requires credential configuration in Business Manager. Defined in [`metadata/site_import/services.xml`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/metadata/site_import/services.xml), the service uses placeholder URLs with the `[ADYEN-REGION]` token that gets replaced at runtime based on your region settings:

```xml
<service service-id="AdyenPosPayment">
  <credential-id>AdyenPosPayment</credential-id>
  <url>https://[ADYEN-REGION].terminal-api.adyen.com/.../sync</url>
  ...
</service>

```

Administrators must upload API keys and merchant account credentials for both test and live environments. The cartridge automatically switches between these based on the `AdyenEnvironment` configuration.

## Summary

- **Enable the payment method**: Activate `AdyenPOS` (`METHOD_ADYEN_POS`) in Business Manager to expose the terminal flow at checkout.
- **Configure store parameters**: Set the Store ID and select the appropriate Terminal API region (`US`, `EU`, `AU`, `APSE`) in the Adyen Settings UI.
- **Implement terminal discovery**: Use the `getConnectedTerminals` controller to populate the terminal selection dropdown with devices linked to your store.
- **Execute the payment flow**: Implement [`posHandle.js`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/posHandle.js) to create payment instruments and [`posAuthorize.js`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/posAuthorize.js) to send **SaleToPOIRequest** messages via the **AdyenPosPayment** service.
- **Handle service credentials**: Configure test and live credentials in [`services.xml`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/services.xml), ensuring the `[ADYEN-REGION]` placeholder aligns with your selected region endpoint.
- **Manage failures**: The system automatically sends abort requests to terminals when HTTP-level failures occur, preventing orphaned transactions.

## Frequently Asked Questions

### What is the Terminal API and how does it differ from online payments?

The **Terminal API** is Adyen's protocol for communicating with physical POS terminals using JSON-based **SaleToPOIRequest** messages. Unlike online payments that redirect to Adyen-hosted pages or use encrypted card data, Terminal API sends payment instructions directly to a specific terminal ID, and the terminal handles card presentment, PIN entry, and receipt printing locally. The SFCC integration wraps these messages in the [`adyenTerminalApi.js`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/adyenTerminalApi.js) service layer while maintaining the same order management flow as e-commerce transactions.

### How do I handle failed POS transactions or timeouts?

The cartridge implements automatic error handling in [`posAuthorize.js`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/posAuthorize.js) and [`adyenTerminalApi.js`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/adyenTerminalApi.js). When a service call fails or returns an error response, the `sendAbortRequest` function immediately cancels the pending terminal transaction to prevent double-charging. The order status is set to `NotPaid`, and detailed error logs are written for debugging. For timeouts, ensure your service timeout settings in Business Manager accommodate the typical duration of card-present interactions (usually 30-60 seconds).

### Can I use multiple store IDs with different terminal fleets?

Yes, but the current implementation in `posSettings.isml` and `AdyenConfigs` supports a single active Store ID per site configuration. To support multiple stores, you would need to extend the `getConnectedTerminals` controller to accept dynamic store ID parameters from the front-end rather than relying solely on `AdyenConfigs.getAdyenActiveStoreId()`. Each store ID must be properly configured in your Adyen Customer Area with its associated terminal fleet.

### What regions are supported for Adyen POS terminal payments?

The integration supports four regional endpoints defined in [`constants.js`](https://github.com/adyen/adyen-salesforce-commerce-cloud/blob/main/constants.js): `US` (United States), `EU` (Europe), `AU` (Australia), and `APSE` (Asia-Pacific South East). The `AdyenHelper.getTerminalApiEnvironment()` method maps these selections to live endpoints like `live-eu.terminal-api.adyen.com` or `live-us.terminal-api.adyen.com`. Always select the region closest to your terminal's physical location to minimize latency and ensure compliance with local processing requirements.