Appearance
How a checkout works
A checkout is one order. Your server sets the amount. The payment goes to your main pocket. The checkout expires. It can complete once.
A payment link is a standing page. A checkout is not.
Status
| Status | Meaning |
|---|---|
| open | Payable, or a payment is in flight |
| completed | The money landed. payment has the receipt |
| expired | Time passed, and no attempt is in flight |
status is computed on each read. The payment attempt says whether money landed. The clock says whether time passed. Your GET and the payer page use the same rule.
There is no failed status and no canceled status. A declined prompt leaves the checkout open. The payer can try again until it expires. payment.status is the last attempt.
Fulfill only when status is completed.
Expiry
expires_at is set at create. The default life is 30 minutes. Read expires_at from the response.
An attempt still in flight keeps status at open after expires_at.
On Kenya, an exact M-Pesa Pay Bill payment can set status to completed after expires_at. See below.
payment
payment is null until someone tries to pay. After that it is the latest attempt.
| Field | Meaning |
|---|---|
intent_id | Attempt id (pi_…) |
status | created, reserved, submitted, pending, completed, failed, expired, or reversed |
amount_settled | Minor units received, as a string. null until it settles |
provider_receipt | Mobile-money receipt. null until it settles |
phone | Payer phone number |
A new attempt replaces the previous payment object.
One attempt at a time
While an attempt is in flight, a second prompt is not sent. POST /checkouts/:id/pay returns 200 and the current attempt.
A completed checkout returns 409 and code 2785.
A card payment already in progress returns 409 and code 2786.
The payer page
url is the hub page /checkout/<id>.
The page shows your name, the amount, and description. It does not show reference, the receipt, or a previous payer's phone number.
If you send phone at create, the page fills that number. The payer can change it.
The country is in the id: cs_ke_… or cs_ug_…. The page calls that country's host. The payer does not sign in.
M-Pesa menu (Kenya)
When Pay Bill is on, the payer page shows two numbers:
| Field on the payer checkout | Meaning |
|---|---|
paybill.business_number | Your M-Pesa Pay Bill number |
paybill.account_number | CS plus 8 digits, for this checkout |
The payer can pay the checkout amount from the M-Pesa menu with those numbers.
| Pay Bill amount | Result |
|---|---|
| Equal to the checkout amount | status becomes completed, even after expires_at |
| Any other amount | The money goes to your main pocket. status stays open or expired |
The merchant checkout object does not include paybill. Confirm with GET /checkouts/:id.
After payment
When status is completed and you set return_url, the page shows the paid state for 3 seconds. It then opens return_url and sets these query parameters:
| Parameter | Value |
|---|---|
checkout | Checkout id |
reference | Your reference, when you set one |
status | completed |
Parameters already on return_url stay. checkout, reference, and status are replaced when those names already exist.
The redirect runs only after completion. Do not fulfill from the query string. Read the checkout.
return_url must use https. Off production, http://localhost and http://127.0.0.1 are allowed.
Fields
| You send | MOOKHPay sets |
|---|---|
amount | id, url, currency, status, expires_at |
reference, description | payment |
return_url, phone | Payee: your main pocket |
There is no currency field. The host sets it. Kenya is KES. Uganda is UGX.
The amount is checked against the mobile-money rail at create. See Limits.