Madagascar P2P Gate
Accept mobile-money pay-ins on a MagicPayments-hosted page and send mobile-money payouts in Malagasy Ariary (MGA).
When to use it
The Madagascar P2P Gate accepts mobile-money pay-ins (MVola and other Malagasy operators)
on a MagicPayments-hosted page, and sends mobile-money payouts to a phone number. All amounts
are in MGA (Malagasy Ariary, 2 decimal places — so 5000000 means 50,000.00 MGA).
| Operation | Rail | How the integration looks |
|---|---|---|
| Pay-in | Mobile money (P2P) | Create an invoice, redirect the payer to the hosted page. |
| Pay-out | Mobile money (phone) | Server-to-server call to the phone payout endpoint. |
You need your signing credentials from Getting started, a
pay-in gate_id for Madagascar mobile money, and a pay-out
cascade_id enabled for MGA phone payouts. Your account manager provides both.
Pay-in — hosted P2P page
The payer never touches your servers with their payment details. You create an invoice; we host the page.
-
Create the invoice
Call the gate endpoint with your
gate_idand the amount in MGA minor units. -
Redirect the payer
Send the customer's browser to the
invoice_urlwe return. They complete the MVola flow on our page. -
Receive the result
We call your
callback_urlon every status change and you can poll invoice status as a fallback.
/api/invoice{
"gate_id": "mg-mobile-money-gate",
"invoice": {
"invoice_id": "order-mg-2026-000123",
"currency": "MGA",
"amount": 5000000,
"description": "Wallet top-up",
"ttl_minutes": 30
},
"customer": {
"id": "cust-77120",
"full_name": "Rakoto Andrianina",
"phone_number": "+261340000000"
},
"workflow_hooks": {
"callback_url": "https://merchant.example.com/mp/callbacks",
"return_success_url": "https://merchant.example.com/orders/123/done",
"return_decline_url": "https://merchant.example.com/orders/123/retry"
}
}
Python
resp = signed_post("/api/invoice", {
"gate_id": "mg-mobile-money-gate",
"invoice": {
"invoice_id": "order-mg-2026-000123",
"currency": "MGA",
"amount": 5_000_000, # 50,000.00 MGA
"description": "Wallet top-up",
"ttl_minutes": 30,
},
"customer": {
"id": "cust-77120",
"full_name": "Rakoto Andrianina",
"phone_number": "+261340000000",
},
"workflow_hooks": {
"callback_url": "https://merchant.example.com/mp/callbacks",
"return_success_url": "https://merchant.example.com/orders/123/done",
"return_decline_url": "https://merchant.example.com/orders/123/retry",
},
})
invoice = resp.json()
redirect_url = invoice["invoice_url"] # send the payer here
Response
{
"request_id": "0f0b1d2e-...",
"request_status": "success",
"invoice_id": "62051f64-9a79-4edb-8a9e-95c86c55ee4e",
"merchant_invoice_id": "order-mg-2026-000123",
"invoice_status": "unpaid",
"invoice_url": "https://stage.example-mp.com/public/invoice/62051f64-.../gate",
"message": "Invoice (Gate PayIn) with internal uid=62051f64-... has been created."
}
If you'd rather have us redirect straight to the hosted page from the create call, set
"redirect_to_gate_payment_page": true at the top level of the request body and follow the
303 redirect instead of reading invoice_url.
Tracking the pay-in
Look up the invoice by your own id (or our invoice_uid). It reaches paid on success.
/api/invoice/statusresp = signed_post("/api/invoice/status", {
"merchant_invoice_id": "order-mg-2026-000123"
})
invoice = resp.json()
# invoice["status"] -> "paid" | "payment_failed" | "expired" | "canceled" | ...
# invoice["actual_payment"]["processing_info"]["amount_acquired"] -> settled MGA minor units
Pay-out — mobile money to a phone
Payouts go server-to-server. For Madagascar mobile money the destination is a phone number
plus the operator. Use the phone payout endpoint with a cascade_id enabled for MGA payouts.
/api/payment/payout/phone{
"cascade_id": "mg-mobile-payout",
"payment": {
"payment_id": "payout-mg-2026-000045",
"currency": "MGA",
"amount": 3000000,
"description": "Affiliate payout"
},
"phone": {
"provider": "mvola",
"phone_number": "+261340000000"
},
"customer": {
"id": "partner-2391",
"full_name": "Rasoa Hery"
},
"workflow_hooks": {
"callback_url": "https://merchant.example.com/mp/callbacks"
}
}
The phone.provider names the Malagasy operator. Supported here:
| Operator | provider |
|---|---|
| MVola (Telma) | mvola |
| Orange Money | orange |
| Airtel Money | airtel |
resp = signed_post("/api/payment/payout/phone", {
"cascade_id": "mg-mobile-payout",
"payment": {
"payment_id": "payout-mg-2026-000045",
"currency": "MGA",
"amount": 3_000_000,
"description": "Affiliate payout",
},
"phone": {"provider": "mvola", "phone_number": "+261340000000"},
"customer": {"id": "partner-2391", "full_name": "Rasoa Hery"},
"workflow_hooks": {"callback_url": "https://merchant.example.com/mp/callbacks"},
})
payout = resp.json()
payout_uid = payout["payment_id"] # our uid; status starts at "initiated"
Tracking the pay-out
Payouts use the payment status endpoint (not the invoice one).
/api/payment/statusresp = signed_post("/api/payment/status", {
"merchant_payment_id": "payout-mg-2026-000045"
})
# resp.json()["status"] -> "success" | "processing" | "decline" | "error"
Send the destination in international format (+261…). A mismatch between the operator
and the number's prefix is the most common cause of a decline on payout.
Testing & go-live
- On stage, use the test
gate_id/cascade_idfrom your account manager; no real funds move. - Verify you can drive a pay-in to
paidand a payout tosuccess, and that your callback handler verifies the signature and is idempotent. - Confirm your MGA amount conversion:
amountis minor units with 2 decimal places. - Swap base URL and credentials to production; the request shapes are identical.