Quickstart

Make a successful sandbox payment, end to end, in under 15 minutes.

🎯

Goal: create a QR code, simulate a payment in the sandbox, and verify the result. End to end in under 15 minutes.

This quickstart covers the merchant integration path: your backend creates a code, your customer pays it on the Scan to Pay app, you verify the payment. If you're integrating as a wallet provider, a bank, or via the In-App SDK, start with Choose your integration instead.


Before you start

You'll need four things:

  1. Sandbox credentials. Email [email protected] to request access. You'll receive an API username in the form merchant-{id} (e.g. merchant-25) and a password.
  2. A terminal with curl available.
  3. The Scan to Pay app installed on a test device, switched to test mode. See Sandbox and test cards for the QR that toggles the app between live and test.
  4. A test card. Any 18-digit card starting with 50010001000105 returns success in the sandbox — we'll use 500100010001050000 below.

Step 1 — Authenticate

Every request to the Scan to Pay API uses HTTP Basic Auth.

export USERNAME='merchant-25'
export PASSWORD='your-sandbox-password'
export BASE='https://qa.scantopay.io/pluto'

Sanity check that your credentials work:

curl -i -u "$USERNAME:$PASSWORD" "$BASE/public/monitor"

You should see HTTP/1.1 200 OK. If you get 401, double-check the username format and password.


Step 2 — Create a payment code

A code is a 10-digit string that represents an intent to pay. You'll render it as a QR (next step), and the customer scans it from the Scan to Pay app.

curl -X POST "$BASE/code/create" \
  -u "$USERNAME:$PASSWORD" \
  -H 'Content-Type: application/json' \
  -d '{
    "merchantReference": "demo-order-001",
    "amount": 10.00,
    "useOnce": true,
    "shortDescription": "Quickstart demo basket"
  }'

You'll receive a response like:

{
  "code": "0123456789",
  "expiryDate": 1747235400000,
  "codeUrl": null
}

Hang on to the code value — that's what the customer will scan. The default expiry is 30 minutes from creation; expiryDate is the exact expiry in epoch milliseconds.

📘

Field notes

  • merchantReference is your reference for this transaction. Make it unique per attempt.
  • amount is in your merchant's configured currency (ZAR by default for South African merchants). Set to 0 for variable-amount codes the customer types in.
  • useOnce: true invalidates the code after one successful payment. Set to false for static QRs (poster / counter signage).
  • Omit notificationUrl for this quickstart — we'll poll for the result. See Webhooks to register a webhook instead.

Step 3 — Render the code as a QR

Two options. Pick whichever fits your flow.

Option A — let Scan to Pay render the QR for you. Embed a <img> tag pointing at the QR endpoint:

<img src="https://qa.scantopay.io/pluto/public/stpqr/0123456789" alt="Scan to Pay" />

You can tint the template with ?colorCode=N (0=Orange, 1=B&W, 2=Grey, 3=Black). Add ?plain for an unstyled QR with no template chrome.

Option B — render the QR yourself. Encode the 10-digit code as a QR using any QR library. The payload is just the digits.


Step 4 — Pay it on the test app

On your test device:

  1. Open the Scan to Pay app (already switched to test mode).
  2. Tap Scan and point at the QR — or tap Enter manually and type the 10-digit code.
  3. Confirm the amount and tap Pay.
  4. Choose to pay by card and enter the test card number 500100010001050000, expiry 12/30, CVV 123.
  5. When prompted, enter PIN 0000.
  6. Confirm.

The app will show "Payment successful."


Step 5 — Verify the payment

Query the transaction state to confirm.

curl -X POST "$BASE/code/queryRef" \
  -u "$USERNAME:$PASSWORD" \
  -H 'Content-Type: application/json' \
  -d '{
    "code": "0123456789",
    "merchantReference": "demo-order-001"
  }'

Response:

{
  "transactionId": 81234,
  "reference": "demo-order-001",
  "amount": 10.00,
  "currencyCode": "ZAR",
  "code": "0123456789",
  "status": "SUCCESS",
  "clientMsisdn": "27831234567",
  "retrievalReferenceNumber": 6321400012,
  "authCode": "123456",
  "bankResponse": "00",
  "date": "2026-05-14T11:42:03Z"
}

The status field carries the transaction state. SUCCESS is the only positive terminal value. A response of status: "N/A" means the transaction hasn't reached the system yet — wait a couple of seconds and retry. See Transaction states for every state the system can return and what each means.

⚠️

Polling vs webhooks

queryRef only works while no webhook URL is configured for the merchant. If you set up a webhook (recommended for production), queryRef returns HTTP 406 and you receive the result via webhook instead. See Webhooks.


What just happened

You created an intent to pay (the code), the customer authenticated against their card, the platform routed the authorisation through the acquiring bank, and you queried the outcome. The same five-step pattern works for every Scan to Pay merchant flow — printed in-store QR, dynamic POS QR, e-commerce checkout, bill payment, USSD. What changes is how the customer reaches the code (printed, on a screen, deep-linked from a merchant app).

What's next

Pick the integration that matches your business: