NewStore self-checkout is headless for payments: the platform records the payment on the order but does not process it. Your self-checkout app takes payment through a payment terminal and payment service provider (PSP) of your choice, and then reports the completed payment to NewStore when placing the order. This article covers where payment sits in the transaction flow, the architecture it requires, terminal integration with Adyen and with NewStore Payments (Stripe), how to record the payment on the order correctly, and the payment-specific errors you may encounter.
Note
The payments contract on the Order Placement API is still being refined. The typed instrument fields described here may change before general availability. Validate against the latest integration guide before going to production.
How does payments work in the self-checkout flow
Payment happens between the tax quotation and order placement. The sequence is fixed:
Get the final total — the
totals.grand_totalfrom the last tax quotation is the exact amount to charge. Never compute it client-side.Charge the terminal — your app sends the amount to the payment terminal via your PSP. The shopper taps or inserts their card.
Place the order — only after the terminal reports success, call
POST /orderswith aCAPTUREDorAUTHORIZEDpayment instrument carrying the PSP reference. ForAUTHORIZEDpayments, they convert toCAPTUREDwith a delay. Reconcile such payments with your payment service provider and in Omnichannel Manager.Create the receipt — continue with the receipt flow as described in the integration guide.
Note
If the terminal declines or fails the payment transaction, no order is placed. The shopper retries payment or abandons the cart, but nothing is recorded on the platform. Pay first, place second is the only way to keep the payment logs and ensure payment does not silently fail on the platform.
Architecture requirements
The rest of the self-checkout integration can run entirely in the browser or on the device, but the payment step needs a thin server-side component that you own as the retailer. PSP API keys (Adyen, Stripe) are secrets and must never be shipped to or stored in the browser or in the app. Route all PSP calls through this thin server-side component.
The kiosk frontend calls your backend with the amount to charge.
Your backend holds the PSP API key and calls the PSP's cloud API, which pushes the payment to the physical terminal.
The terminal result (success/failure plus the PSP reference) flows back to the frontend, which then places the order.
Currency and amounts
One currency per store. NewStore supports a single currency per store location, set at the order level. All instrument amounts on an order use that currency.
NewStore uses integer minor units; some PSP APIs do not. A NewStore amount of
77519(USD) corresponds to775.19in the Adyen Terminal API'sRequestedAmount, which takes decimal major units. Convert exactly once, at the PSP boundary, and keep everything else in minor units.Charge exactly
totals.grand_total. The platform validates that instrument amounts sum to the order total; any mismatch rejects the order. The quotation already reflects the promotions calculation, so the amount you charge is the discounted total. See Promotions in the integration guide.Dynamic currency conversion (DCC). If the retailer has DCC enabled on their Adyen account and the shopper chooses to pay in their home currency at the terminal, NewStore still records the payment in the store's currency. The DCC details are logged on the PSP side only.
Integrating an Adyen terminal
Use the Adyen cloud Terminal API (synchronous mode), which fits a web-based or app-based kiosk. The backend of the app sends a SaleToPOIRequest to Adyen's cloud endpoint, Adyen pushes it to the terminal, and the response returns when the shopper completes or cancels the payment.
Note
Terminal provisioning and boarding steps live in Adyen's documentation and the retailer's Adyen Customer Area, not in the self-checkout pack. For how NewStore tenants are set up with Adyen generally, see Integrating Adyen.
Prerequisites
Before the first payment can succeed on the device:
The terminal (for example a
Verifone e285orAMS1) is assigned to the store and boarded on the retailer's Adyen merchant account.Terminal API is enabled in the
Adyen Customer Areafor that merchant account.You have an Adyen API key with the Cloud Device API role, created under API credentials in the Customer Area. Use a test key against the Adyen test environment first.
The terminal is connected to Wi-Fi, and on the device, Settings > Diagnostics displays both
PSPandTerminal APIasSuccessful.You know the terminal's POIID:
<Model>-<Serial>(for examplee285-XXXXXXXX), displayed on the device underSettings>Device info.
Configuration
Variable | Value |
|---|---|
| API key with the Cloud Device API role. Server-side only — never in the browser. |
| The Adyen merchant account the terminal is boarded to (for example |
| The terminal identifier, |
|
|
If you use a company-level API key rather than a merchant-level one, name the merchant account on every payment request via SaleData.SaleToAcquirerData (merchantAccount=<name>).
Important
When you place the order, the card instrument must carry the Adyen merchant account in
provider_account_id. Without it, the order is still accepted with201, but asynchronous payment processing fails withmissing_merchant_accountand the order lands in the failed payment queue in Omnichannel Manager. Treatprovider_account_idas required for every Adyen payment.
{"error_code": "missing_merchant_account",
"message": "Could not infer merchant account from request",
"request_accepted": false
}provider_account_id is a typed field of the card instrument, but the order request does not enforce it, so nothing catches a missing value at request time. Ensure that this is a mandatory step for every Adyen payment. See the card payment model for the instrument fields and the metadata keys that the NewStore platform understands.
Integrating NewStore Payments via Stripe
With NewStore Payments, the payment infrastructure is powered by Stripe and the merchant account is managed by NewStore, which makes setup simpler than a direct PSP contract. Your backend uses the Stripe Terminal API to drive the reader (for example a Stripe S700).
Important
NewStore Payments uses Stripe connected accounts for each store for your business. Your app must create the
PaymentIntenton the store's connected account, not on the platform account. If the intent is created on the platform account, the terminal payment appears to succeed and the order reaches NOM, but the platform cannot complete the financial instrument and the order fails with a failed authorization error.
Validating that you are connected to a physical reader
Stripe's SDKs default to a simulated reader in development. If your app displays failed to fetch or the physical terminal never lights up, check that the integration is registered against the real reader and not a simulated reader.
Recording the payment on the order
The full instrument contract is documented in the integration guide. Mentioned below are the payment-provider specifics that the integration needs.
{
"payment": {
"instruments": [{
"id": "payment-1",
"type": "CARD",
"amount": 77519,
"provider": "adyen",
"status": "CAPTURED",
"processed_at": "2026-08-13T09:39:15Z",
"reference": "GDQVVVSCRVC5VP75",
"origin": "terminal",
"provider_account_id": "NewStorePOS",
"brand": "mc",
"last_digits": "9999"
}]
}
}provider_account_idis required for Adyen payments (see above). For NewStore Payments the account linkage comes from the connected account used at payment time.statusisCAPTUREDfor self-checkout: the terminal has already taken the money before the order is placed.AUTHORIZEDis also valid to use, but ensure that payment has been captured later by reconciling with your payment service provider and in Omnichannel Manager.processed_atmust be RFC 3339 with a timezone offset.referenceis the PSP's transaction reference — the tender reference returned by the Adyen terminal response, or the PaymentIntent id from Stripe. It is required for card payments and is what links the NewStore order to the PSP transaction for capture, refunds, and reconciliation.brandandlast_digitscarry the card brand and last four digits; the receipt renders them.
Payment processing is asynchronous
A 201 from POST /orders means the order is accepted — not that payment processing has completed. The platform validates and processes the payment instrument asynchronously. A misconfigured instrument (missing merchant account, wrong connected account, missing reference) therefore surfaces after order placement, as an entry in the failed payment queue in NOM with an error such as:
Error communicating with EXTERNAL payment provider. This might indicate a payment provider CONFIGURATION or an INTEGRATION ERROR. Error: Could not infer merchant account from requestAfter your first end-to-end transaction against a real environment, always open the order in NOM (Omnichannel Manager) and confirm the payment shows as processed — a green kiosk screen is not proof that the payment side is correctly wired.
Cash and other tenders
Cash instruments (provider: "CASH" with given_amount, rounded_amount, change_amount) are supported by the order contract but a self-checkout kiosk may not have a cash drawer. You can choose how to handle cash at the kiosk yourself. For example, you can have cash handled via handoff to an associate or use an associate-attended cash counting machine near the kiosk. Split payments and gift cards are not supported in the current self-checkout scope. See the limitations.
Local development
There is currently no payment terminal stub in the local stub server pack. Options for building before hardware is available:
Stub the terminal in your app — a fake payment port that returns Success or Failure lets you build and test the full order path against the local stub servers.
PSP test environments — Adyen's test environment and Stripe's test mode work with real boarded test terminals and test cards, without moving real money. This is the recommended step between local stubs and staging.
Troubleshooting
Symptom | Likely cause | Fix |
|---|---|---|
Order accepted, but fails in NOM with |
| Add the Adyen merchant account name as |
Terminal payment succeeds, order fails with failed authorization (NewStore Payments) | PaymentIntent created on the platform account instead of the store's connected account | Create the PaymentIntent on the connected account. |
Failed to fetch; physical terminal never reacts (Stripe) | App is talking to a simulated reader | Register and connect the physical reader; disable the simulator. |
Adyen cloud request rejected or terminal unreachable | Terminal API not enabled, terminal not boarded to the store, or API key lacks the Cloud Device API role | Work through the prerequisites checklist above; check Settings → Diagnostics on the device. |
| Instrument amounts do not sum to | Copy |
Amount on the terminal is 100× too large or too small | Minor-unit / major-unit mismatch at the PSP boundary | Convert minor units to the PSP's expected format exactly once, in the backend. |
Further reading
Self-checkout integration guide — Full order and instrument contract, idempotency, and error handling
Integrating Adyen — How Adyen is set up for a NewStore tenant
Set up payments in NewStore POS — NewStore Payments and supported payment service providers
Hardware and software support — Payment terminals supported by NewStore