Payments
A payment is one customer paying you one amount through an InnBucks code. Every other product โ checkout, webhooks, balance, payouts โ hangs off this object.
Lifecycle
Code active
Customer has 10 minutes to pay via app, QR, deep link or USSD *569#.
Money received
Ledger credited (amount โ fee). payment.paid webhook fires. Terminal.
Window closed
10 minutes elapsed unpaid. No money moved. payment.expired fires. Terminal โ create a fresh payment to retry.
Gateway failure
Rare terminal state when InnBucks reports the code unusable. payment.failed fires.
An InnBucks code lives 10 minutes and pays exactly one payment. Don't cache or reuse
codes; create a new payment per purchase attempt. expires_at is on every payment object.
Create a payment
curl https://omniway.online/v1/payments \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"amount_cents": 2500,
"currency": "USD",
"narration": "Term 2 fees โ J. Moyo",
"merchant_ref": "invoice-2026-0042",
"metadata": {"student_id": "S1042", "term": "2"},
"success_url": "https://portal.school.example/fees/paid",
"cancel_url": "https://portal.school.example/fees"
}'
| Field | Required | Notes |
|---|---|---|
amount_cents | yes | Integer cents > 0. 2500 = USD 25.00. |
currency | no | USD (default) or ZWG. |
narration | no | Human text shown to the customer and on statements (โค100 chars). |
merchant_ref | no | Your order/invoice id. Indexed โ filter listings by it. |
metadata | no | Any JSON object, stored and echoed back. Yours entirely. |
success_url / cancel_url | no | Where hosted checkout sends the customer after paid / expired. |
The response includes four ways for the customer to pay, all for the same code:
codeโ the 9-digit InnBucks code, for manual entry in the app or at USSD*569#.qr_png_data_urlโ a ready-to-render QR (drop it straight into an<img src>).deep_linkโ opens the InnBucks app with the payment pre-loaded (mobile).checkout_urlโ the hosted page that presents all of the above with InnBucks branding.
Fees
Your platform fee (default 1.5%, set per merchant) is deducted when a payment settles, never before.
fee_cents is on every payment object, and the ledger shows it as
its own line โ your balance grows by amount_cents โ fee_cents. See your exact rate via
GET /v1/me (fee_bps, fee_fixed_cents).
Retrieve & track
curl https://omniway.online/v1/payments/7303405f-โฆ \
-H "Authorization: Bearer sk_test_..."
Reads are live: while a payment is pending the platform checks InnBucks for you (within their inquiry rules โ you can poll this endpoint as often as you like, we throttle the upstream correctly). For event-driven confirmation use webhooks instead of polling.
List & filter
curl "https://omniway.online/v1/payments?status=paid&from=2026-06-01&limit=50" \
-H "Authorization: Bearer sk_test_..."
| Query param | Meaning |
|---|---|
status | pending ยท paid ยท expired ยท failed |
merchant_ref | Exact match on your reference |
from / to | ISO dates bounding created_at |
limit / cursor | Page size (โค100) and keyset cursor (last id of the previous page); has_more tells you when to stop |
Counter & kiosk pattern (no redirect)
At a physical till there is no browser redirect โ render the payment on your own screen:
// 1. Your till backend creates the payment (server-side, with your secret key)
const p = await createPayment({ amount_cents: total, narration: `Till ${tillNo}` });
// 2. Render: big code + QR + countdown
codeEl.textContent = p.code; // "820614925"
qrEl.src = p.qr_png_data_url; // ready-made QR
startCountdown(new Date(p.expires_at)); // 10-minute window
// 3. Poll until paid (the platform throttles upstream correctly)
const timer = setInterval(async () => {
const fresh = await getPayment(p.id); // GET /v1/payments/:id via your backend
if (fresh.status === "paid") { clearInterval(timer); showSuccess(); }
if (fresh.status === "expired") { clearInterval(timer); showRetry(); }
}, 4000);
This is exactly how the school-till and tuck-shop recipes work.