MagicPayments Integration Guides

Togo Mobile Money Gate

Pay-inPay-outXOFMoov / Yas

Accept Moov Money (Flooz) and Mixx by Yas pay-ins on a hosted page where the payer approves a push on their phone, and send mobile-money payouts in West African CFA Franc (XOF).

When to use it

The Togo Mobile Money Gate accepts mobile-money pay-ins (Moov Money, branded Flooz, and Mixx by Yas) on a MagicPayments-hosted page, and sends mobile-money payouts to a phone number. All amounts are in XOF (West African CFA Franc).

OperationRailHow the integration looks
Pay-inMobile money (push to payer)Create an invoice, redirect the payer to the hosted page.
Pay-outMobile money (phone)Server-to-server call to the phone payout endpoint.
XOF has no minor unit

The CFA Franc has 0 decimal places, so the minor unit equals the major one: "amount": 5000 means 5,000 F CFA — not 50.00. Sending 500000 for a 5,000 F CFA order overcharges the payer by 100×. This is the single most common mistake on this corridor.

Prerequisites

You need your signing credentials from Getting started, one pay-in gate_id per operator (Moov and Yas are separate gates), and one pay-out cascade_id per operator. Your account manager provides all of them.

Pay-in — hosted mobile-money page

The payer never touches your servers with their payment details. You create an invoice; we host the page where they enter their own mobile-money number and approve the charge on their handset.

Each operator is its own gate, because the operator is fixed before the page opens — the payer is not asked to pick one. Choose the gate_id by the payer's number. Togolese mobile numbers are 8 digits with no leading zero; the country code is 228.

OperatorNumber starts withGate
Moov Money (Flooz)78, 79, 96, 97, 98, 99your Moov gate_id
Mixx by Yas70, 71, 72, 73, 90, 91, 92, 93your Yas gate_id
  1. Create the invoice Call the gate endpoint with the operator's gate_id and the amount in XOF. If you already know the payer's number, pass it in customer.phone_number — the page pre-fills it.
  2. Redirect the payer Send the customer's browser to the invoice_url we return. The page is branded for that operator, accepts only that operator's numbers, and shows that operator's approval steps in French (English is selectable). The payer confirms the number and presses Pay.
  3. The payer approves on their phone The operator pushes a confirmation request to the handset and the payer enters their mobile-money PIN. If nothing arrives, the page tells them to dial the operator's USSD code (*155# for Moov, *145# for Yas) and approve the pending request there.
  4. Receive the result We call your callback_url on every status change and you can poll invoice status as a fallback.
POST/api/invoice
Request body
{
  "gate_id": "tg-moov-gate",
  "invoice": {
    "invoice_id": "order-tg-2026-000123",
    "currency": "XOF",
    "amount": 5000,
    "description": "Wallet top-up",
    "ttl_minutes": 30
  },
  "customer": {
    "id": "cust-77120",
    "full_name": "Kossi Mensah",
    "email": "[email protected]",
    "phone_number": "+22896123456"
  },
  "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": "tg-moov-gate",           # the Moov gate — +228 96… number below
    "invoice": {
        "invoice_id": "order-tg-2026-000123",
        "currency": "XOF",
        "amount": 5_000,             # 5,000 F CFA — XOF has 0 decimal places
        "description": "Wallet top-up",
        "ttl_minutes": 30,
    },
    "customer": {
        "id": "cust-77120",
        "full_name": "Kossi Mensah",
        "email": "[email protected]",
        "phone_number": "+22896123456",
    },
    "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-tg-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."
}
A stable customer id helps

The operator sees the charge under customer.id; use the same id for the same payer across orders so repeat payments are recognised as one customer. full_name and email are optional on this corridor but are forwarded when present. The payer's IP address is captured from the hosted page — you don't need to send it.

Tracking the pay-in

Look up the invoice by your own id (or our invoice_uid). It reaches paid on success.

POST/api/invoice/status
resp = signed_post("/api/invoice/status", {
    "merchant_invoice_id": "order-tg-2026-000123"
})
invoice = resp.json()
# invoice["status"] -> "paid" | "payment_failed" | "expired" | "canceled" | ...
# invoice["actual_payment"]["processing_info"]["amount_acquired"] -> settled XOF
The payer decides how fast this moves

The charge sits in processing until the payer approves the push on their handset, which can take a couple of minutes. Set a ttl_minutes that gives them room, and treat processing as "not finished yet" rather than as a failure.

Pay-out — mobile money to a phone

Payouts go server-to-server. The destination is a phone number plus the operator that serves it. Like pay-ins, payouts are split per operator: the cascade_id decides which operator the payout is sent through, and phone.provider must name that same operator. Always pass cascade_id explicitly — relying on your wallet's default cascade sends every payout through one operator regardless of the number.

Operatorphone.providerCascade
Moov Money (Flooz)moovyour Moov payout cascade_id
Mixx by Yasyasyour Yas payout cascade_id
POST/api/payment/payout/phone
Request body
{
  "cascade_id": "tg-yas-payout",
  "payment": {
    "payment_id": "payout-tg-2026-000045",
    "currency": "XOF",
    "amount": 30000,
    "description": "Affiliate payout"
  },
  "phone": {
    "provider": "yas",
    "phone_number": "+22890123456"
  },
  "customer": {
    "id": "partner-2391",
    "full_name": "Afi Lawson"
  },
  "workflow_hooks": {
    "callback_url": "https://merchant.example.com/mp/callbacks"
  }
}
Python
resp = signed_post("/api/payment/payout/phone", {
    "cascade_id": "tg-yas-payout",       # the Yas cascade — +228 90… number below
    "payment": {
        "payment_id": "payout-tg-2026-000045",
        "currency": "XOF",
        "amount": 30_000,            # 30,000 F CFA
        "description": "Affiliate payout",
    },
    "phone": {"provider": "yas", "phone_number": "+22890123456"},
    "customer": {"id": "partner-2391", "full_name": "Afi Lawson"},
    "workflow_hooks": {"callback_url": "https://merchant.example.com/mp/callbacks"},
})
payout = resp.json()
payout_uid = payout["payment_id"]   # our uid; status starts at "initiated"
Route by the recipient's operator, not by default

A payout for a Yas number sent through the Moov cascade is forwarded to Moov as a Moov payout and fails at the operator. Decide the cascade from the number's prefix (table above) before you call, and keep phone.provider in step with it.

Tracking the pay-out

Payouts use the payment status endpoint (not the invoice one).

POST/api/payment/status
resp = signed_post("/api/payment/status", {
    "merchant_payment_id": "payout-tg-2026-000045"
})
# resp.json()["status"] -> "success" | "processing" | "decline" | "error"

Phone number format

Send the destination as the local 8-digit 9XXXXXXX / 7XXXXXXX form, the full 2289XXXXXXX form, or in international format (+228 96 12 34 56) — spaces, dashes and parentheses are accepted and stripped, and the 228 country code is added when missing. There is no trunk zero in Togo; a leading 0 is dropped if you send one.

Testing & go-live