Collect a payment (payin)
A payin debits a customer's mobile money account and credits your MoyaPay balance.
POST /payin
Requires a Bearer token.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
refercence_cl | string | yes | Your merchant reference. Must match the account of the token |
business_code | string | yes | Business code provided by MoyaPay. Must belong to your account |
country_code | string | yes | Country of the transaction: CI, SN, BF, CM, BN, RDC, CB, GMB |
payment_method | string | yes | Payment method code, see Payment methods |
merchant_transaction_id | string | yes | Your transaction identifier, unique |
amount | number | yes | Amount, as a JSON number (not a string). See Limits |
currency | string | yes | Currency of the transaction's country, for example XOF. Choose it from the Currencies by country table |
telephone | string | yes | Customer phone number, as a string. See Limits |
successUrl | string | yes | Redirect URL after a successful payment (redirect-based methods, such as Wave) |
failedUrl | string | yes | Redirect URL after a failed payment |
notifyUrl | string | yes | Your notification URL. See the note below |
otp_code | string | depends on the method | Required for OM_CI (Orange Money Côte d'Ivoire): the one-time code generated by the customer |
notifyUrlThe field is required, but callbacks are currently sent to the callback URL registered on your merchant account by MoyaPay (one URL for payins, another for payouts), not to the value sent in notifyUrl.
Example
- cURL
- Node.js
- PHP
- Python
curl -X POST https://services.moya-pay.com/service/api/v1/payin \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"refercence_cl": "CL-123456",
"business_code": "MYP-000000-ABCDEF",
"country_code": "CI",
"payment_method": "WAVE_CI",
"merchant_transaction_id": "ORDER-2026-000123",
"amount": 1000,
"currency": "XOF",
"telephone": "0505000000",
"successUrl": "https://shop.example.com/payment/success",
"failedUrl": "https://shop.example.com/payment/failed",
"notifyUrl": "https://shop.example.com/api/moyapay/notify"
}'
const res = await fetch('https://services.moya-pay.com/service/api/v1/payin', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
refercence_cl: 'CL-123456',
business_code: 'MYP-000000-ABCDEF',
country_code: 'CI',
payment_method: 'WAVE_CI',
merchant_transaction_id: 'ORDER-2026-000123',
amount: 1000,
currency: 'XOF',
telephone: '0505000000',
successUrl: 'https://shop.example.com/payment/success',
failedUrl: 'https://shop.example.com/payment/failed',
notifyUrl: 'https://shop.example.com/api/moyapay/notify',
}),
});
const body = await res.json();
if (!body.success) throw new Error(body.description || body.message);
console.log(body.data.status, body.data.payment_url);
<?php
$payload = [
'refercence_cl' => 'CL-123456',
'business_code' => 'MYP-000000-ABCDEF',
'country_code' => 'CI',
'payment_method' => 'WAVE_CI',
'merchant_transaction_id' => 'ORDER-2026-000123',
'amount' => 1000,
'currency' => 'XOF',
'telephone' => '0505000000',
'successUrl' => 'https://shop.example.com/payment/success',
'failedUrl' => 'https://shop.example.com/payment/failed',
'notifyUrl' => 'https://shop.example.com/api/moyapay/notify',
];
$ch = curl_init('https://services.moya-pay.com/service/api/v1/payin');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);
if (empty($body['success'])) {
throw new Exception($body['description'] ?? $body['message'] ?? 'MoyaPay error');
}
import requests
resp = requests.post(
"https://services.moya-pay.com/service/api/v1/payin",
headers={"Authorization": f"Bearer {token}"},
json={
"refercence_cl": "CL-123456",
"business_code": "MYP-000000-ABCDEF",
"country_code": "CI",
"payment_method": "WAVE_CI",
"merchant_transaction_id": "ORDER-2026-000123",
"amount": 1000,
"currency": "XOF",
"telephone": "0505000000",
"successUrl": "https://shop.example.com/payment/success",
"failedUrl": "https://shop.example.com/payment/failed",
"notifyUrl": "https://shop.example.com/api/moyapay/notify",
},
timeout=30,
)
body = resp.json()
if not body.get("success"):
raise RuntimeError(body.get("description") or body.get("message"))
Response
{
"status": 201,
"success": true,
"code": 200,
"message": "TRANSACTION IS INITIATED SUSSESSFULLY",
"data": {
"status": "PENDING",
"transaction_id": "ORDER-2026-000123",
"payment_url": "https://pay.wave.com/c/..."
}
}
| Field | Description |
|---|---|
success | true if the request is accepted. This is the field to test |
data.status | Initial status of the transaction, usually PENDING |
data.transaction_id | Your merchant_transaction_id, keep it to follow the transaction |
data.payment_url | Depends on the payment method. URL to redirect the customer to so they can approve the payment (Wave in particular) |
data.deepLinks, data.validFor | Depends on the payment method. Link that opens the operator's app, and its validity period |
data.msg | Depends on the payment method. Instruction to show to the customer |
The top-level status is 201 while the HTTP status is 200: do not rely on that value. The text of message can also vary by operator: do not parse it in your code. Test success, then read data.status.
After the response
- Depending on the payment method, redirect the customer to
data.payment_urlor show them the instruction (data.msg) so they approve on their phone. - Wait for the callback, or query the transaction status.
Validation rules
merchant_transaction_idmust be new. An identifier that was already used returns a400error. Uniqueness is enforced across the whole platform: choose distinctive identifiers.- An identical transaction (same amount, same number, same type) still in progress for less than a minute is rejected with
An identical transaction is already in progress. Please wait.Wait for the first one to finish, or change the amount. - The payment method must be enabled on your account, otherwise the response is
Marchand not have provider.
Possible errors are listed in Errors.
If initiation fails at the operator (Impossible to make the transaction, HTTP 500), the transaction is still recorded. Do not send the same merchant_transaction_id again: check its status first, then create a new transaction with a new identifier if needed.