PaymonetraDocs

Checkout

We host it. Send the payer to `checkout_url` and you are done.

#The seven states

statusthe screenwhat the payer does next
awaiting_detailsthe name and email formfills it in, then the account appears
pendingthe waiting screen with the account and countdownsends the transfer
paidconfirmeddone. paid.late true means it arrived after the timer and still counts
underpaidits own screenshortfall says what is still owed, to the same account
overpaidits own screenoverpaid_by says the excess; the seller returns it
expiredits own screenstart again for a fresh amount
cancelledits own screenthe seller called it off

Underpaid, overpaid and expired each get a screen and a next step, not an error. The message field already carries the right sentence for each.

A late payment still counts. The account is permanent, so a transfer that arrives after the countdown still lands and is still attributed. The session goes to paid with paid.late true, and the message says it arrived after the timer and counts anyway. Never tell a payer their money is lost because a clock ran out.

#Reading a session

GEThttps://api.paymonetra.com/v1/checkout/{reference}Public, guarded by the reference

Public. Poll it while waiting; the page updates itself rather than asking the payer to refresh.

JSON
{
  "reference": "ef8e7ff7078b893d03",
  "status": "pending",
  "amount": 45000.70,
  "description": "Ankara bundle",
  "expires_in": 885,
  "redirect_url": null,
  "customer": { "name": "Ngozi Nwosu", "email": "" },
  "account": { "number": "8021234701", "name": "MONETRA - Ngozi Nwosu", "bank": "Payrep MFB" },
  "instruction": "Send exactly NGN 45,000.70 to the account below from your banking app.",
  "note": "The exact kobo matters. It is how we know the payment is yours.",
  "message": "Waiting for your transfer. Bank transfers can take a minute, so this page will update on its own."
}

instruction, note and message are written on the server so every checkout says the same thing. Render message as given. It is the sentence that stops the waiting state looking broken, which is the single thing that screen has to get right.

expires_in is seconds, for the countdown. It is 0 on any state but pending.

account is only present while status is pending. On an expired or paid session it is deliberately absent, because showing an account nobody is watching invites a second transfer.

#Collecting a name and email

POSThttps://api.paymonetra.com/v1/checkout/{reference}/detailsPublic, guarded by the reference

Public. Only for a session in awaiting_details.

JSON
{ "customer_name": "Bolu Adeyemi", "customer_email": "bolu@example.com" }

Answers the same body as the GET, now pending with the account showing. The countdown restarts here, so the fifteen minutes is spent on the transfer rather than on typing.

Calling it on a session that is already past that point returns the current state rather than an error, so the page can simply re-render.

#What your server hears

When the money lands, the collection.success webhook gains a field:

JSON
"checkout_reference": "ef8e7ff7078b893d03"

Null when the payment was a plain transfer into a customer's permanent account with no checkout behind it. Present when it belonged to a session, so the merchant settles that order without matching on amounts themselves.

#Why the amount is not always round

An anonymous checkout now asks for a round amount. It borrows its own account number for the payment, so nothing needs to distinguish it from somebody else paying the same merchant. Render amount and account as given and say nothing about kobo.

A checkout carrying a customer_reference still has the suffix. That payer is shown their own permanent account, which is the point of having issued it, and on a permanent account the suffix is what separates two of their payments.

So the screen cannot assume either. Read instruction and note from the response rather than composing them: the server says whether an exact figure matters, because only the server knows which mechanism a session got. A page that always says "send exactly" is wrong for most payers now, and one that never says it is wrong for the rest.

The suffix also reappears as a fallback. If the account pool is at its limit or the provider is unreachable, a checkout is created the old way rather than refused, so a session with no customer_reference can still come back with a kobo amount. Nothing about the response shape changes; the numbers just differ.

#Why the suffix exists at all

A bank transfer carries no memo we can rely on, and a merchant's collection account may have twenty people paying into it at once. So every session is quoted the naira asked for plus a kobo suffix nobody else currently has pending on that account: NGN 45,000 becomes NGN 45,000.70.

That turns "whose payment was that" from a guess about timing into an exact match. It is the same trick the consumer payment links have used in production since August.

Two consequences for the page:

Show the exact figure, kobo included, and make it copyable. A payer who rounds to 45,000.00 is underpaid, and that is a worse experience than a slightly odd number.

Say why the odd figure is there. The server sends the sentence: "The exact kobo matters. It is how we know the payment is yours." Render it. Without it the number looks like a mistake.

#Why checkout has its own host

Checkout gets its own host for a reason that is not tidiness. That page is shown to complete strangers, most of whom are not Paymonetra customers. On the apex, every visitor's browser would send any Paymonetra session cookies to a page built to be embedded and linked from anywhere. A separate host means the checkout can never see them. It is also the page most worth cloning for phishing, and a short distinct address is one you can teach people to check.

#One honest limit

When a payer sends a figure nobody is expecting and more than one session is open on that account, we do not attribute it. The merchant is still credited in full, because the money genuinely arrived; only the link to a particular order is missing, and it is logged for someone to look at.

Guessing would be worse: it would tell one payer their order is settled while another waits forever.