Tous les fournisseurs
Bictorys
paiement·🇸🇳🇨🇮🇲🇱🇹🇬🇧🇯🇧🇫·Sandbox disponible
Bictorys est un fournisseur d’infrastructure de paiement pour l’Afrique de l’Ouest et centrale, qui prend en charge le mobile money et les paiements par carte dans plusieurs pays via une API unifiée.
À utiliser avec des agents IA
Après avoir installé le plugin ou ajouté le serveur MCP, donnez cette consigne à votre agent :
« Initie un paiement mobile money ou par carte et redirige le client vers la page de paiement pour qu’il le finalise. »
Installer le plugin →Capacités
| Capacité | Type | StatutiNon vérifiée — générée à partir de la doc publique, pas encore relue.Disponible — écrite à partir de la doc du fournisseur, pas encore testée : peut contenir des erreurs.Vérifiée — a correspondu une fois à l’API réelle (test en direct, app qui fonctionne ou rapport accepté), en sandbox ou en production. | Méthode | Exemple |
|---|---|---|---|---|
| create_charge | synchrone | Vérifiée | POST | Exemple |
| create_payout | synchrone | Vérifiée | POST | Exemple |
| verify_transaction | synchrone | Disponible | GET | Exemple |
| webhook_payment_completed | webhook | Vérifiée | POST | Exemple |
Gotchas
Les gotchas sont affichés tels qu’ils figurent dans les specs, dans leur langue d’origine.
create_charge
- ⚠payment_type is a query parameter, not a request body field. Append it to the URL: POST /pay/v1/charges?payment_type=wave_money. Omitting it returns HTTP 202 with a hosted-checkout link instead of a direct 201 response.
- ⚠The response HTTP status determines the flow: 201 means the direct payment was initiated (use response.transactionId and response.redirectUrl); 202 means the customer must be sent to response.link on the Bictorys-hosted checkout page.
- ⚠Store transactionId (201 flow) or chargeId (202 flow) before redirecting the customer — you'll need it to match the webhook_payment_completed event back to this order, and optionally to call verify_transaction if you need an immediate status for the redirect UX.
- ⚠Nothing is fulfillable right after create_charge returns — the customer hasn't paid yet, they're only about to be redirected to complete payment. Never trust the browser redirect alone to mark an order paid (it's fully spoofable — a user can visit successRedirectUrl without paying). The source of truth for fulfillment is the signature-verified webhook_payment_completed event, not this response and not the redirect.
- ⚠successRedirectUrl and errorRedirectUrl must start with https:// — HTTP URLs are rejected with E400-0: redirectUrl must start with https://.
- ⚠Sandbox base URL is https://api.test.bictorys.com — the production base URL is https://api.bictorys.com. Sandbox and production use separate API keys obtained from the Bictorys dashboard.
- ⚠Card payments via the direct API (payment_type=card with a cardObject in the body) require PCI-DSS certification. Mobile money payments have no certification requirement.
- ⚠Card payments return E401-7: Card payment is not activated yet in production until Bictorys manually activates card payments for your merchant account — reproduces both with an explicit payment_type=card and via the hosted-checkout flow (no payment_type, customer picks card from Bictorys' page). Sandbox does not reproduce this — test cards work there with no activation step, so this will not surface until you go live. Contact Bictorys support to enable card payments on your production account before launch; mobile money has no such gate.
- ⚠create_charge uses the PUBLIC API key (env BICTORYS_API_KEY, header X-Api-Key), and so does verify_transaction; create_payout uses the PRIVATE key (env BICTORYS_PRIVATE_KEY, header X-API-Key). The integration guide: "Status check (GET /pay/v1/transactions/{id}/status) | Clé publique (BICTORYS_API_KEY)", "Payouts (retraits) | Clé privée (BICTORYS_PRIVATE_KEY)", and "Utiliser la clé privée pour une charge fonctionne, mais ce n'est pas recommandé." A sandbox call measured 2026-09-14 also accepted the private key on verify_transaction.
- ⚠Orange Money Côte d'Ivoire requires an OTP: the customer must dial #144*82# on their phone to generate a 6-8 digit code, then pass it as the 'otp' field in the request body. The OTP expires quickly — if the charge fails with an OTP error, ask the user to redial.
- ⚠The request body field is 'errorRedirectUrl' (lowercase e) — Bictorys' own documentation example shows 'ErrorRedirectUrl' (capital E), which appears to be a doc typo; lowercase is confirmed working end-to-end in production (charge creation + successful webhook).
- ⚠HTTP 403 with an HTML body (not JSON) means AWS WAF is rate-limiting your requests. Implement exponential backoff with at least 5-second spacing between retries.
create_payout
- ⚠payment_type goes in the query string: POST /pay/v1/payouts?payment_type=wave_money. The API reference does not mark it required, but the integration guide always sends it (POST {BICTORYS_API_URL}/pay/v1/payouts?payment_type={type}). The reference enum has 10 values: wave_money, orange_money, maxit, mtn_money, free_money, moov, mobicash, togocell, bictorys, card.
- ⚠transactionType is required — omitting it causes a 500. Use "payment" for standard disbursements.
- ⚠idempotency-key header is effectively required — a missing idempotency-key is a known cause of 500 errors. Always send a UUID per request.
- ⚠merchant.secretCode is the operator-issued merchant PIN (e.g. your Orange Money or MTN merchant code) registered in Bictorys Dashboard → Entreprise → Préférences. Required when configured on your account — omitting it returns E400-37: Secret code is missing.
- ⚠customerObject.phone is a string with a + and country code prefix, no spaces. Example: "+221771234567" for Senegal. Using a numeric type causes a 400 phone error.
- ⚠The response amount is a negative integer — money leaving the merchant account. Use Math.abs() if displaying to the user.
- ⚠status is a string, not an integer: one of succeeded, failed, cancelled, pending, processing, reversed or authorized (PaymentStatus in the transactions reference). A completed sandbox payout returns "succeeded".
- ⚠Money transfer must be explicitly enabled for your merchant account. If not, Bictorys returns E400-28: Money transfer is not authorized for this merchant. Contact Bictorys support.
- ⚠A 400 error mentioning "balance" means Bictorys' own float is insufficient; a 400 error mentioning "plafond" or "limit" means the recipient's mobile money account hit its own operator-side ceiling — these are distinct failure modes and neither is retryable without merchant/recipient action.
- ⚠Set your HTTP client timeout to at least 30 seconds — payout processing is slower than charge creation.
- ⚠Sandbox base URL is https://api.test.bictorys.com — the production URL is https://api.bictorys.com. Sandbox payouts do not send real money.
verify_transaction
- ⚠Redirect query parameters are fully spoofable — never fulfill an order from them alone; call this endpoint server-side (or wait for the webhook) before trusting a redirect. A signature-verified webhook_payment_completed event, on the other hand, does not need this endpoint to be trusted — its HMAC/secret verification already proves authenticity, and gating fulfillment on a live call here risks losing an already-successful payment if this endpoint is briefly down (see the 500 gotcha below).
- ⚠Use the PUBLIC key here (env BICTORYS_API_KEY, same key as create_charge, header X-API-Key). Bictorys' integration guide (docs.bictorys.com/docs/integration) lists "Status check (GET /pay/v1/transactions/{id}/status) | Clé publique (BICTORYS_API_KEY)" and "BICTORYS_API_KEY (publique) — Header X-Api-Key pour créer des charges et vérifier des transactions." The sandbox also accepts the PRIVATE key (BICTORYS_PRIVATE_KEY) on this endpoint — a live sandbox call measured 2026-09-14 (afrotools PR #67): both keys return 200 — so a sandbox-only test will not catch a wrong key. Do not rely on that permissiveness in production, and never send the private key from a frontend or mobile app.
- ⚠Bictorys' integration guide ("Statuts possibles" table) says: `succeeded` — "Paiement confirmé" — "✅ Valider la commande"; `authorized` — "Paiement autorisé (pré-capture carte)" — "✅ Traiter comme `succeeded`"; `pending` and `processing` — "⏳ En attente — le webhook confirmera le statut final"; `failed`, `cancelled`, `reversed` — "❌ Marquer FAILED". "authorized" means funds are reserved but not yet captured: the API reference describes create_charge's `authorization: true` as a request to "reserve an amount on the holder card to pay later the transaction", and has a separate endpoint to "capture totally or partially a transaction previously authorized". Whether fulfilling on "authorized" without a capture is safe is not stated — see open_questions.
- ⚠The transactionId to pass here is the id field from the webhook payload, or the transactionId / chargeId field from the create_charge response — they refer to the same transaction UUID.
- ⚠id can be null: on 2026-10-29, in the sandbox, a status check on an unpaid hosted-checkout charge (create_charge HTTP 202, chargeId), made right after creation, returned HTTP 200 {"id":null,"status":"pending"} (integration report, afrotools issue #175). Do not take the transaction id from this response: use the id you put in the path.
- ⚠Sandbox, to reach succeeded: Bictorys documents test cards (docs.bictorys.com/docs/cartes-bancaires-de-test): enter a first and last name of your choice, a future expiry date and a CVV; 2303779999000275 (frictionless) and 2303779999000408 (challenge, the OTP to enter on the challenge page is 1234) succeed; 2303779999000291, 2303779999000317, 2303779999000424 and 2303779999000432 fail (not authenticated, or unavailable). Mobile money in the sandbox runs on a simulator (docs.bictorys.com/docs/test-mobile-money): a QR code is shown, to scan with the phone's camera only, never with a real Wave, Orange Money or MTN app. In the sandbox, the success and error redirect URLs are not called after a mobile money payment: test a redirect with a card.
- ⚠A transactionId taken from the redirect comes from the browser, so validate it server-side before putting it in the path (a visitor can send "../…" and make this call, with your private key, reach another endpoint): the docs type it as a uuid — create_charge: "transactionId": { "description": "Unique Id of the initiated transaction", "type": "string", "format": "uuid" } — so the example rejects anything else.
- ⚠Sandbox base URL is https://api.test.bictorys.com — the production URL is https://api.bictorys.com.
- ⚠On status 'succeeded', this endpoint returns NO amount and NO timestamp — observed live: {"id": "…", "status": "succeeded"} and nothing else. Reading status and amount from the same response therefore yields 0 for the amount ON THE SUCCESS PATH, which in a billing system means crediting nothing while everything reports success. Fall back to GET /pay/v1/transactions and match on paymentReference to obtain amount and timestamp.
- ⚠The list endpoint GET /pay/v1/transactions (undocumented) only becomes non-empty once a transaction COMPLETES. It stays [] while a charge is merely pending, which makes reference-based lookup look untestable in sandbox until the first successful payment.
- ⚠In the Bictorys sandbox, GET /pay/v1/transactions/{id}/status is known to return HTTP 500 intermittently. For sandbox testing, rely on webhook events instead of polling this endpoint.
- ⚠Bictorys recommends polling this endpoint for backend-less apps (e.g. static/mobile-only clients that can't expose a webhook endpoint). Apps with a backend server should treat webhook_payment_completed as the source of truth for fulfillment, and use this endpoint for the redirect-triggered UX check and for reconciliation (e.g. a periodic sweep of orders whose webhook never arrived) — not as a mandatory gate after every webhook.
webhook_payment_completed
- ⚠Bictorys sends two validation headers: X-Secret-Key (raw shared secret, always present) and optionally X-Webhook-Signature (HMAC-SHA256 hex) + X-Webhook-Timestamp (Unix milliseconds). When X-Webhook-Signature is present, verify with HMAC-SHA256(BICTORYS_WEBHOOK_SECRET, '${timestamp}.${rawBody}') and reject payloads where Math.abs(Date.now() - timestamp) > 300_000.
- ⚠The HMAC pair is ACCOUNT-GATED and off by default: docs.bictorys.com/docs/intégration-en marks both headers 'optional (if HMAC enabled)'. A live sandbox webhook captured 2026-09-14 carried only accept, content-length, content-type, host, user-agent and x-secret-key — no signature, no timestamp. Write both paths, but do not assume the HMAC will be there: on an account where it is not enabled, your fallback is what runs in production.
- ⚠X-Secret-Key carries the secret in CLEAR on every delivery. Never point a Bictorys webhook at a public capture bin (webhook.site, requestbin) using your real secret — anyone holding the bin URL can read it. Use a throwaway secret for capture testing, then rotate.
- ⚠WHEN THE HMAC IS NOT ENABLED on your account, X-Secret-Key proves only that the sender knows the secret — it says nothing about the body, and a tampered payload passes (measured: an amount rewritten from 100 to 100000 is accepted). In that configuration the amount/currency/status/paymentReference cross-check below is not a belt-and-braces extra: it is the only thing standing between you and a forged payload.
- ⚠Always use crypto.timingSafeEqual() when comparing X-Secret-Key or HMAC values — never use === or ==. String equality comparison is vulnerable to timing attacks.
- ⚠Bictorys retries the webhook up to 3 times if your endpoint returns a non-2xx status. Always respond HTTP 200 immediately, even if your internal processing fails — then handle errors asynchronously.
- ⚠Express users: mount express.raw({ type: 'application/json' }) before express.json() on the webhook route, or keep req.rawBody. The raw body is required to compute the HMAC if it is enabled on your account — JSON.parse() loses it — and it is what you log for audit either way.
- ⚠Don't gate fulfillment on an extra synchronous call to verify_transaction — a signature-verified webhook (HMAC or X-Secret-Key) combined with the anti-fraud amount/currency match below is already sufficient proof. verify_transaction's /status endpoint is known to be intermittently unavailable (see its own gotchas), so calling it here risks losing an already-successful payment to a transient outage on Bictorys' side. Reserve verify_transaction for the client-side redirect flow (which IS spoofable and needs it) and for reconciliation if a webhook never arrives.
- ⚠Anti-fraud check: before fulfilling, verify that the webhook's amount and currency match your own order records — a forged or replayed payload could otherwise pass signature checks with tampered amounts.
- ⚠Log the raw webhook payload to a persistent store (DB table) immediately after signature verification and before any business logic runs — this gives you an audit trail even if downstream processing fails, and is the natural place to implement the idempotency check above.
- ⚠Implement idempotency using the transaction id field. Bictorys may deliver the same webhook event more than once; processing the same id twice must not double-fulfill an order.
- ⚠Your webhook URL must be publicly reachable over HTTPS. Webhook configuration is separate for test vs. production environments in the Bictorys dashboard.
- ⚠Bictorys recommends this webhook for apps with a backend server. For backend-less apps (e.g. static/mobile-only clients that can't expose a webhook endpoint), poll verify_transaction instead to check payment status.
- ⚠The timestamp field is not strict ISO 8601 (space instead of 'T', no timezone, non-standard fractional-second precision — e.g. "2026-08-08 18:45:44.10254"). Don't rely on a strict ISO parser; use a lenient date library or normalize the string first.
Détails
- Catégorie
- paiement
- Sandbox
- Oui