POS charges
Scope: charges:write. Live only.
POST /v1/pos/charges
For a till system. You have rung up an amount at a counter (a six-digit till number printed on the sticker); you want a code the customer scans. No phone number is asked for — nothing is sent to anybody until the customer scans.
The code lives five minutes, deliberately short: a code left on screen after the customer leaves is one the next person in the queue could pay.
Headers: Idempotency-Key required. Use your sale number.
Body
| Field | ||
|---|---|---|
till | required | The six digits on the counter, e.g. "400291". |
amount_minor | required | Whole shillings, TZS 100 – 20,000,000. |
reference, description, metadata, currency | optional | As on a charge. |
Response 201
json
{
"id": "ch_01HZX…",
"object": "charge",
"state": "requires_payment",
"till": "400291",
"token": "7Kf2…",
"qr": "https://pay.pamopay.co/c/1/7Kf2…",
"amount_minor": 4650000,
"mdr_minor": 0,
"net_minor": 4650000,
"currency": "TZS",
"reference": "SALE-3391",
"description": "",
"metadata": {},
"expires_at": "2026-09-16T09:19:02.118Z",
"created": "2026-09-16T09:14:02.118Z"
}Render qr as the QR code, verbatim. Do not build the URL yourself from token — the scheme, the /c/, and the version segment are ours to keep right, and a hand-built one will be refused by every handset. A customer with the PamoPay app installed opens the app directly; one without it opens a page saying which shop and how much.
Then poll GET /v1/charges/{id} or wait for charge.succeeded. The payment is made from the customer's PamoPay wallet, so the event's rail is wallet and it carries till so nine lanes can be told apart.
Refusals: BAD_TILL, UNKNOWN_TILL (not a till on this business), TILL_CLOSED, MERCHANT_NOT_LIVE, plus the charge ones.
The fee field is spelled differently here
The POS response spells the fee mdr_minor where /v1/charges spells it fee_minor. Read both if you share code between the two.
Opening further tills is done from the dashboard's Checkout screen, not the API.