Payment Intents
The core C2B charge object. One shape across Waafipay, CAC, SabaPay, D-Money, EAB, Cybersource card — and any future rail. State-machine driven, idempotent by contract.
Endpoints
/v1/payment_intentsCreate a new PaymentIntent.
/v1/payment_intentsList recent intents (limit ≤ 100, default 25).
/v1/payment_intents/{id}Retrieve a specific intent.
/v1/payment_intents/{id}/confirmFinalise a requires_action intent (OTP, redirect return).
/v1/payment_intents/{id}/cancelCancel an intent before settlement.
Create
| Name | Type | Description |
|---|---|---|
amountrequired | integer | Minor units. DJF is zero-decimal; pass integer DJF. |
currencyrequired | ISO 4217 | "DJF", "USD", "ETB". |
providerrequired | enum | waafi · cac · sabapay · dmoney · eab · santimpay · mock |
metadata | object | Provider flow selectors: waafi_mode (hpp wallet · card Visa/MC), santim_mode (direct) + santim_payment_method, auth_mode (SabaPay APP/OTP). |
customer_msisdn | E.164 | Required for mobile-money providers. |
customer_account | string | Required for bank providers (IBAN or other identifier). |
customer_id | cus_ | Optional — attach to a Customer for tokenised re-use. |
description | string | ≤140 chars, appears on receipts. |
callback_url | url | Where the provider sends async status updates (we proxy to your webhook). |
return_url | url | Where the customer is sent after a redirect-based confirm. |
metadata | map | Free-form string-string map; round-trips on every response. |
Idempotency-Key: order_42 on every create. Same key in the next 24h returns the same intent — never a double-charge.Confirm
For OTP-based rails, call confirm with the OTP after the customer enters it. For redirect-based rails the intent transitions on the provider callback; confirm is a no-op.
| Name | Type | Description |
|---|---|---|
otp | string | For OTP-based rails (Waafi, D-Money). Required when status was requires_action. |
client_secret | string | Required when authed with pk_* (browser). |
provider | enum | Optional override; defaults to the provider used on create. |
metadata | map | Additional fields to merge into the intent. |
Sample request + response
curl -X POST https://api.merashub.com/v1/payment_intents \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: order_42" \
-H "Content-Type: application/json" \
-d '{
"amount": 50000,
"currency": "DJF",
"provider": "waafi",
"customer_msisdn": "+25377xxxxxxx",
"description": "Order #42"
}'{
"id": "pi_01HZF8AYJX3D8M7QK0E5V4WJ9K",
"client_secret": "pi_01HZF8AYJX3D8M7QK0E5V4WJ9K_secret_xyz",
"amount_minor": 50000,
"currency": "DJF",
"status": "requires_action",
"next_action": {
"type": "collect_otp",
"otp_length": 6
},
"metadata": {},
"created_at": "2026-05-25T10:15:30Z"
}Status state machine
requires_payment_methodNew intent — caller has not picked a provider yet (rare; orchestrator usually moves past this immediately).
requires_actionAwaiting OTP, redirect return, or external confirmation.
processingProvider has accepted; settlement in progress.
succeededTerminal success. Ledger booked, webhook fired.
canceledCaller cancelled, OR provider declined.
Webhook is the source of truth
payment_intent.succeeded on your webhook endpoint. The synchronous response may say processing while the rail is still settling asynchronously.