Skip to main content
Work through this before the first real customer stands in front of the till.

Correctness

₮1,500 is 1500. Check every path that builds an amount — especially any code ported from an integration that used minor units. A ×100 error here is a customer charged a hundred times over.
Uppercase MNT. amountMnt is rejected with 400 invalid request body. Verify against a live call, not against your own type definitions.
Written to your own order record before you show the cashier a success screen. Without it a refund becomes a manual search.
No trimming, no prefix stripping, no case changes. If your scanner appends a newline or a configured prefix, strip that in your code before sending.

Safety

Generated once per sale attempt and reused across every retry of that attempt. Confirm by forcing a timeout and checking the customer was charged once.
The charge may have committed. Retry idempotently or reconcile against GET /v1/transactions — never silently fail the sale, and never silently recharge.
qr token already used → verify before recharging. transaction already reversed → mark it refunded and show success.
Only 422 returns JSON. Calling .json() on a 409 throws and turns a handled decline into an unhandled crash at the till.
Not in a shipped mobile binary, not in a browser bundle, not in git. One terminal per till, so a lost device is one rotation rather than a store-wide re-credential.

Cashier experience

All ten of them, in Mongolian. Do not show the raw English error text and do not show a generic “declined” — the difference between “not enough balance” and “can’t be used today” decides what the customer does next.
TOKEN_EXPIRED and TOKEN_INVALID are recoverable at the counter. Give the cashier a button.
A dead credential cannot be retried into working.
It does not exist at the moment of sale and never will. If your receipt template has a slot for it, leave it blank or fill it in later.

Operations

Never log the QR string or the terminal password. The id is what support will ask for.
A tight retry loop against a shared rate limit turns one slow request into an outage.
Skewed clocks produce confusing history windows and spurious token-age complaints.
Spend rules and daily limits reset at Mongolian midnight; the API returns UTC timestamps. Convert, do not assume.

The final rehearsal

Run this end to end on the real hardware, with a real card, before you open:
1

Charge ₮100

201, vatStatus: "pending". Save the id.
2

Rescan the same code

409 qr token already used. Your POS should offer to verify, not to charge again.
3

Replay the original request with the same Idempotency-Key

201 with the same id. The customer was charged once.
4

Pull the balance up in the customer's pass

₮100 gone, once.
5

Wait a few minutes, then list transactions

vatStatus: "issued" with a receipt number in vatReceiptID.
6

Reverse it

204. Then reverse it again: 409, handled as success.
7

Check the pass and the status

₮100 returned. vatStatus moves to voided.
If all seven behave as described, your integration is sound.

Getting help

Email info@vibepay.mn with the transaction id and the approximate time. That is enough for us to trace any payment end to end.