Payment status
Understand payment lifecycle states, status polling patterns, terminal resolutions, and how to verify incoming funds.
Polling Architecture
Ziba Pay uses status API polling for merchant payment verification. Outbound merchant notifications are not implemented for collection routes.
Important: POST/payment/mtn/callback is an internal provider callback used by MTN's servers only. Do not call it from your merchant server or wait for an outbound notification.
transactionReferenceId until COMPLETED or FAILED. Do not invent merchant notification endpoints.Status Polling Endpoints
Depending on the payment channel used during collection, query status using the corresponding endpoint below:
| Payment Channel | Status Endpoint | HTTP Method | Authentication | Input Parameter |
|---|---|---|---|---|
| MTN Mobile Money | /payment/mtn/status/:reference | GET | Authorization: Bearer <token> | transactionReferenceId in URL path |
| TransactPay Orders (Status) | /payment/zibapay/order/status/:reference | POST (No body) | Authorization: Bearer <token> | order reference in URL path |
| TransactPay Orders (Verify) | /payment/zibapay/order/verify/:reference | POST (No body) | Authorization: Bearer <token> | order reference in URL path |
Recommended Polling Algorithm
When implementing your polling worker, follow these best practice intervals to balance prompt resolution with rate limit compliance:
- Initial Delay: Wait 3 to 5 seconds after initiating collection before issuing the first status request.
- Polling Frequency: Poll every 3 to 5 seconds while status remains PENDING or PROCESSING.
- Exponential Backoff: If status remains PENDING after 30 seconds, increase the polling interval to 10 seconds.
- Timeout Threshold: After 180 seconds (3 minutes), conclude polling and treat the session as EXPIRED if no terminal status has been received.
async function pollPaymentStatus(reference, token, maxTimeoutMs = 180000) {
const startTime = Date.now();
let intervalMs = 3000;
// Initial wait for customer USSD prompt
await new Promise((r) => setTimeout(r, 3000));
while (Date.now() - startTime < maxTimeoutMs) {
const res = await fetch(`https://payment-service-mh2d.onrender.com/api/payment/mtn/status/${reference}`, {
method: "GET",
headers: { "Authorization": `Bearer ${token}` }
});
if (res.ok) {
const { status } = await res.json();
console.log(`Current status for ${reference}: ${status}`);
if (status === "COMPLETED") return { success: true, status };
if (["FAILED", "CANCELLED", "EXPIRED", "REVERSED"].includes(status)) {
return { success: false, status };
}
}
// Exponential backoff after 30 seconds
if (Date.now() - startTime > 30000) {
intervalMs = Math.min(intervalMs * 1.5, 10000);
}
await new Promise((r) => setTimeout(r, intervalMs));
}
throw new Error("Payment polling timed out after 3 minutes");
}Special Edge Case: MTN TIMEOUT Behavior
In cases where the upstream mobile network experiences temporary network latency, MTN may return a TIMEOUT event. Ziba Pay intentionally keeps the local order status as PENDING rather than marking it FAILED.
This ensures that if the customer subsequently enters their PIN and the network confirms settlement, the payment successfully completes instead of being prematurely abandoned.
Order Status & Lifecycle State Machine
7 Lifecycle StatesState Transition Flow
| Status | Type | Description | Merchant Action | Next States |
|---|---|---|---|---|
| PENDING | In-Flight | Collection initiated. The USSD prompt or push authorization has been dispatched to the customer's phone. | Continue polling with exponential backoff (every 3 to 5 seconds). | PROCESSING, COMPLETED, FAILED, EXPIRED, CANCELLED |
| PROCESSING | In-Flight | The customer entered their PIN and the transaction is being verified on the telecommunication or bank network. | Continue polling. State will resolve to COMPLETED or FAILED within seconds. | COMPLETED, FAILED, CANCELLED |
| COMPLETED | Terminal (Success) | Funds successfully debited from customer and credited to merchant account. Final successful state. | Fulfill order, deliver digital service, and display success receipt to customer. | REVERSED (only if subsequently refunded) |
| FAILED | Terminal (Failure) | Transaction declined by upstream provider due to insufficient balance, invalid PIN, or telecom outage. | Stop polling. Inform customer of payment failure and prompt to re-try or change channel. | Terminal (no further transitions) |
| CANCELLED | Terminal (Failure) | Transaction rejected explicitly by the customer dismissing the USSD prompt on their handset. | Stop polling. Update cart status to cancelled. | Terminal (no further transitions) |
| EXPIRED | Terminal (Failure) | Customer failed to approve the prompt before the telecom carrier session timeout window elapsed (120–180s). | Prompt user to re-initiate a fresh collection request. | Terminal (no further transitions) |
| REVERSED | Post-Terminal | A previously COMPLETED order was refunded or reversed by provider intervention or dispute settlement. | Revoke goods/license and update your local books. | Terminal (no further transitions) |