Passer au contenu principal
POST
/
v1
/
payments
Create a payment
curl --request POST \
  --url https://api.sandpay.dev/v1/payments \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "amount": 25000,
  "currency": "FCFA",
  "operator": "orange",
  "country": "CI",
  "msisdn": "+22507123456",
  "reference": "ORDER-2026-A1",
  "scenario": "success"
}
'
{
  "id": "TX_8K3M9F",
  "parentTxId": null,
  "orderRef": "ORDER-2026-A1",
  "orderUrl": null,
  "isUrlProtected": false,
  "amount": 25000,
  "commission": 250,
  "netAmount": 25000,
  "customerTotal": 25250,
  "merchantAbsorptionPct": 100,
  "merchantShare": 250,
  "customerShare": 0,
  "commissionMode": "customer",
  "currency": "FCFA",
  "country": "CI",
  "msisdn": "+22507123456",
  "reference": "ZANA-PAY-20260531221015-8E2884A47B1C",
  "latencyMs": 1240,
  "createdAt": "2023-11-07T05:31:56Z",
  "raw": {
    "_simulated": true,
    "amount": "25000",
    "currency": "FCFA",
    "externalId": "ORDER-2026-A1",
    "payer": {
      "partyIdType": "MSISDN",
      "partyId": "22507123456"
    },
    "payerMessage": "Payment",
    "payeeNote": "ORDER-2026-A1",
    "status": "SUCCESSFUL",
    "financialTransactionId": "SIM_A1B2C3D4"
  }
}

Autorisations

Authorization
string
header
requis

API key in format sp_sk_test_... or sp_sk_live_....

Corps

application/json
amount
integer
requis
Plage requise: x >= 1
Exemple:

25000

currency
string
requis
Required string length: 3 - 4
Exemple:

"FCFA"

operator
enum<string>
requis

Mobile Money operator code.

Options disponibles:
mtn,
orange,
moov,
airtel
country
string
requis
Required string length: 2
Exemple:

"CI"

msisdn
string
requis
Pattern: ^\+[1-9][0-9]{6,14}$
Exemple:

"+22507123456"

reference
string
requis
Required string length: 1 - 80
Exemple:

"ORDER-2026-A1"

application
string
requis

Your store's slug (Settings → Stores). Required — every transaction is tied to a store.

Required string length: 1 - 80
Exemple:

"my-store"

description
string

Optional free-form description (max 200 chars). Stored verbatim on the transaction.

Maximum string length: 200
Exemple:

"Premium plan upgrade"

order_ref
string

Strongly recommended — your order / commande id. Groups this payment with its refunds and any recurring charges. Falls back to reference when omitted (B126).

Required string length: 1 - 120
Exemple:

"ORDER-2026-A1"

order_url
string<uri>

Optional public URL to the order in your app (B126).

Maximum string length: 500
is_url_protected
boolean

Set true when order_url requires a specific app permission to open (admin-only, not public). Defaults to false (B130).

scenario
enum<string>
défaut:success

Optional. Defaults to success.

Options disponibles:
success,
pin_invalid,
low_balance,
timeout,
blocked,
cancelled,
unknown_msisdn,
limit_exceeded,
maintenance,
duplicate

Réponse

Payment created.

id
string
requis

Transaction id.

Exemple:

"TX_8K3M9F"

type
enum<string>
requis

Transaction direction (B119): collection (pay-in, customer → merchant), refund (payout reversing a collection), or disbursement (free-form payout, merchant → an msisdn).

Options disponibles:
collection,
refund,
disbursement
parentTxId
string | null
requis

For a refund, the id of the original collection it reverses. null for collections and disbursements (B119).

Exemple:

null

orderRef
string
requis

App order id grouping related transactions (collection + refunds + recurring charges). Falls back to reference when not supplied (B126).

Exemple:

"ORDER-2026-A1"

orderUrl
string<uri> | null
requis

Public URL to the order in the consuming app; null when not set (B126).

Exemple:

null

isUrlProtected
boolean
requis

Whether orderUrl requires a specific permission on the consuming app to open (admin-only, not public). The dashboard shows a "protected URL" hint accordingly. Defaults to false (B130).

Exemple:

false

amount
integer
requis

Gross amount in minor units the customer was debited (e.g. CFA centimes are not used; FCFA uses whole units).

Plage requise: x >= 1
Exemple:

25000

commission
number
requis

Operator commission kept from the gross amount (B112). Defaults to 1% (1.5% for Moov). 0 when no settlement / commission applies.

Plage requise: x >= 0
Exemple:

250

netAmount
number
requis

Net the merchant received. Reconcile your merchant ledger against this, not amount. In customer mode this equals amount (the merchant gets the full amount); in merchant mode it is amount − commission.

Plage requise: x >= 0
Exemple:

25000

customerTotal
number
requis

Total the client was charged (netAmount + commission). In customer mode this is amount + commission; in merchant mode it equals amount (B113).

Plage requise: x >= 0
Exemple:

25250

merchantAbsorptionPct
integer | null
requis

Percentage (0–100) of the operator commission the merchant absorbed for this payment (B114). 100 = merchant absorbs the full fee (default); 0 = customer absorbs all. null for legacy rows.

Plage requise: 0 <= x <= 100
Exemple:

100

merchantShare
number
requis

Portion of the commission borne by the merchant (amount − netAmount). B114.

Plage requise: x >= 0
Exemple:

250

customerShare
number
requis

Portion of the commission borne by the customer (commission − merchantShare). B114.

Plage requise: x >= 0
Exemple:

0

commissionMode
enum<string> | null
requis

Who absorbed the operator commission for this payment (B113): customer (default) — client pays amount + commission, merchant receives the full amount; merchant — client pays amount, merchant receives amount − commission. null for legacy rows.

Options disponibles:
customer,
merchant
Exemple:

"customer"

currency
string
requis

ISO-4217-style currency code (FCFA, XOF, XAF).

Required string length: 3 - 4
Exemple:

"FCFA"

operator
enum<string>
requis

Mobile Money operator code.

Options disponibles:
mtn,
orange,
moov,
airtel
country
string
requis

ISO-3166 2-letter country code.

Required string length: 2
Exemple:

"CI"

msisdn
string
requis

E.164-formatted phone number.

Pattern: ^\+[1-9][0-9]{6,14}$
Exemple:

"+22507123456"

reference
string
requis

Caller-supplied idempotency reference (unique per org). Recommended canonical shape ORIGIN-OP-TS-CODE (e.g. ZANA-PAY-20260531221015-8E2884A47B1C): ORIGIN = your app slug (SP for SandPay), OP ∈ PAY/REF/DIS/ABO, TS = compact UTC datetime YYYYMMDDHHMMSS, CODE = 12-char uppercase hex. Any string is accepted — this is a recommended convention, not enforced. Distinct from order_ref.

Required string length: 1 - 80
Exemple:

"ZANA-PAY-20260531221015-8E2884A47B1C"

status
enum<string>
requis

Final settlement status.

Options disponibles:
SUCCESS,
PIN_INVALID,
INSUFFICIENT_FUNDS,
TIMEOUT,
ACCOUNT_BLOCKED,
USER_CANCELLED,
UNKNOWN_MSISDN,
LIMIT_EXCEEDED,
SERVICE_UNAVAILABLE,
DUPLICATE_REFERENCE,
PENDING
latencyMs
integer
requis

Total time spent processing the payment, in milliseconds.

Plage requise: x >= 0
Exemple:

1240

createdAt
string<date-time>
requis

ISO-8601 timestamp when the payment was created.

raw
object
requis

Native operator response payload. In sandbox this is synthesized to mirror the real operator shape (MTN MoMo, Orange Money, Moov Africa, Airtel Money) — with _simulated: true set on the top level so consumers can detect sandbox payloads. In production this is the verbatim payload returned by the upstream operator API.

This field lets you write integration code (e.g. tx.raw.financialTransactionId for MTN, tx.raw.responseCode for Moov) against sandbox transactions and have the same code work in production without changes.

null for legacy rows persisted before raw passthrough shipped.

Exemple:
{
"_simulated": true,
"amount": "25000",
"currency": "FCFA",
"externalId": "ORDER-2026-A1",
"payer": {
"partyIdType": "MSISDN",
"partyId": "22507123456"
},
"payerMessage": "Payment",
"payeeNote": "ORDER-2026-A1",
"status": "SUCCESSFUL",
"financialTransactionId": "SIM_A1B2C3D4"
}
scenario
enum<string>

Scenario used to drive the outcome, if explicit.

Options disponibles:
success,
pin_invalid,
low_balance,
timeout,
blocked,
cancelled,
unknown_msisdn,
limit_exceeded,
maintenance,
duplicate