Home›Documentation›Cashier & POS API
Partner integration

Cashier & POS API

A stable server-to-server contract for cash registers, kiosks, order-management systems, and multi-merchant commerce platforms.

Keep credentials off the register

A cashier client calls your backend; your backend calls XRPay with a secret key or Connect access token. Never embed a secret credential in browser, mobile, or POS-extension code.

Integration flow

1

Create

Send an integer minor-unit amount, external order ID, register context, and a durable idempotency key.

2

Present

Display the returned QR image or open the hosted action while the order remains unpaid.

3

Confirm

Verify the signed webhook, deduplicate its stable event ID, then retrieve the intent if needed.

4

Reconcile

Record XRPay as an external tender only after succeeded, preserving the intent and provider references.

Create a payment intent

Amounts are exact integer minor units: 1250 USD means USD 12.50, while 1250 JPY means JPY 1,250. Reuse an idempotency key only for the same logical request.

BASH
curl https://api.xrpay.it/api/v1/payment-intents \
  -H 'Authorization: Bearer sk_test_...' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: store-42-order-1008-attempt-1' \
  -d '{
    "amount_minor": 1250,
    "currency": "USD",
    "external_order_id": "order-1008",
    "integration_type": "pos",
    "location_id": "store-42",
    "terminal_id": "register-3",
    "operator_id": "employee-91"
  }'

Persist the returned id, external_order_id, status, amount, currency, location, terminal, and idempotency key with the cashier order. The complete schemas and error responses are in the OpenAPI 3.1 contract.

Direct Mobile Money — no checkout page

For an API-owned customer journey, set collection_mode to direct and provide exactly one Mobile Money method. XRPay validates the country, currency, network, number, and merchant route before submitting one approval request to the customer's phone.

BASH
curl https://api.xrpay.it/api/v1/payment-intents \
  -H 'Authorization: Bearer sk_live_...' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: order-1009-direct-momo-1' \
  -d '{
    "amount_minor": 10000,
    "currency": "GHS",
    "external_order_id": "order-1009",
    "integration_type": "api",
    "customer_email": "customer@example.com",
    "collection_mode": "direct",
    "payment_method_types": ["mobile_money"],
    "payment_method_data": {
      "type": "mobile_money",
      "mobile_money": {
        "country": "GH",
        "network": "mtn",
        "phone": "+233599233665"
      }
    }
  }'

The response never returns the full phone number or an underlying provider name. It returns only mobile_money.phone_last4 and a truthful next_action: approve_on_phone, submit_otp, or wait_for_confirmation. If an OTP is requested, POST {"otp":"123456"} to /api/v1/payment-intents/{id}/confirm.

Do not retry an uncertain submission with a new key

A timeout can mean the phone prompt was already sent. When the intent is processing or asks you to wait, retrieve the same intent and wait for a signed webhook. Creating another intent may charge the customer twice.

Cashier status behavior

StatusRequired behavior
requires_customer_actionHosted: display the QR or open hosted_url. Direct Mobile Money: follow next_action and tell the customer to approve on their phone. Keep the order unpaid.
processingLock the tender attempt and continue reconciliation.
succeededRecord an external tender and close the order.
failed / expiredRelease the attempt and let the operator retry with a new idempotency key.
canceledThe unpaid attempt was canceled. Still accept a later authoritative event at the payment boundary.

Deterministic sandbox

Test credentials do not submit external payments, send phone prompts, or create canonical cashier intents on a ledger. Create either a hosted or direct intent, then POST /api/v1/payment-intents/{id}/simulate with {"outcome":"succeeded"}, failed, or expired.

A successful local-currency simulation credits that business's isolated virtual payout balance exactly once. The response reports the credit directly:

JSON
{
  "status": "succeeded",
  "livemode": false,
  "sandbox_simulated": true,
  "test_balance_credit": {
    "credited": true,
    "idempotent_replay": false,
    "amount": "100.00",
    "currency": "GHS",
    "livemode": false,
    "simulated": true,
    "external_money_moved": false
  }
}

Repeating the successful simulation returns idempotent_replay: true and does not add the amount twice. A payment configured to settle as XRP, RLUSD, or another crypto asset does not create a local-currency payout balance.

Test the complete payout flow without sending money

Read the virtual balance, list the simulated bank or Mobile Money destination for the selected country and currency, create a destination, request a quote, and execute it. A test payout returns livemode: false, simulated: true, and external_transfer_sent: false. XRPay changes only virtual balances and contacts no bank, Mobile Money operator, or external payout provider. Live and test balances never mix.

Exercise cancellation, retry, duplicate delivery, network loss, and full/partial refund paths before certification.

Signed, durable webhooks

Register a public HTTPS URL with POST /api/v1/webhooks using the test or live credential mode it should receive. Verify X-XRPay-Signature over the exact raw request bytes, durably record the body before returning 2xx, and deduplicate on the stable event id. The required top-level livemode flag provides an additional environment check.

JAVASCRIPT
import crypto from "node:crypto";

export function verifyXRPay(rawBody, signature, secret) {
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  const left = Buffer.from(expected);
  const right = Buffer.from(signature || "");
  return left.length === right.length &&
    crypto.timingSafeEqual(left, right);
}

Inspect failures with GET /api/v1/webhook-deliveries?status=failed and replay a delivery with POST /api/v1/webhook-deliveries. Payment and refund failures are retried with exponential backoff.

Refunds and platforms

Create a full or partial refund with POST /api/v1/refunds and an idempotency key. Pending refunds reserve the refundable balance, preventing concurrent over-refunds. Live non-custodial refunds require merchant approval and become final only at completed.

Multi-merchant platforms use OAuth 2.0 Authorization Code with PKCE. Request only the required checkout, transaction, refund, and webhook scopes; every resource remains isolated to its connected merchant and live/test mode.

Related