SoftPay: native payment, with your design
SoftPay displays the operator selection, the phone number, the confirmation code, the app link (Wave, Djamo) and the payment tracking directly inside your page, without going through the Cartflox page. You keep control of the entire style, or you design every screen yourself. Only your public key is used in the browser.
The footer of the form always carries the “Powered by Cartflox” mention with a link to cartflox.com: it is part of the SoftPay terms of use and cannot be removed. You can change its color, not hide it.
Try it right now: SoftPay demo with your public key and your colors.
Option A: ready-made form, in your colors
Section titled “Option A: ready-made form, in your colors”<div id="paiement"></div><script src="https://cartflox.com/cartflox-softpay.js"></script><script> Cartflox.softpay.configure({ publicKey: "af_live_pub_YOUR_KEY" }); Cartflox.softpay.mount({ container: "#paiement", amount: 5000, currency: "XOF", country: "ci", // payment methods offered (ci, sn, bj, ml, bf, tg, cm...) description: "Order #1042", customer_email: "awa@example.com", metadata: { order_id: "1042" }, merchant_name: "My Store", // optional: name shown on receipts and on the equivalent Cartflox page merchant_logo: "https://maboutique.com/logo.png", theme: { accent: "#1a1a1c", "accent-text": "#ffffff", radius: "0px", font: "inherit" }, onSuccess: function (r) { /* show a thank-you message; validate server-side */ }, onError: function (e) { console.error(e); } });</script>mount options
Section titled “mount options”| Option | Type | Required | Description |
|---|---|---|---|
container |
string or element | Yes | CSS selector or element that receives the form |
amount |
number | Yes | Amount, integer for CFA francs |
currency |
string | No | XOF by default |
country |
string | No | Country of the offered methods (ci, sn, bj, ml, bf, tg, cm, gn…). Without a value: all active operators |
description, customer_name, customer_email, customer_phone, metadata, success_url, cancel_url |
No | Same fields as POST /v1/checkout/sessions |
|
merchant_name, merchant_logo |
string | No | Name and logo (https address) shown on the equivalent Cartflox page and on receipts, instead of your workspace’s |
title |
string | No | Form title (“Payment” by default) |
theme |
object | No | CSS variables without the prefix: { accent, "accent-text", bg, text, muted, border, field, radius, font } |
labels |
object | No | Labels to replace (see Texts) |
autoRedirect |
boolean | No | false so that the customer is not sent to a hosted page automatically: you receive the URL in onRedirect and handle it yourself |
timeoutMs |
number | No | Maximum time to track a pending payment (180,000 ms by default) |
onSession, onStatus, onRedirect, onSuccess, onError |
function | No | See Events |
configure accepts publicKey (required), country (default country), css: false (no injected styles) and baseUrl (only if you serve the script from another domain).
Customize the style
Section titled “Customize the style”The form is built with CSS variables and stable classes. Override them in your own stylesheet, or pass { css: false } to configure to inject no styles at all and write everything yourself.
| Variable | Role |
|---|---|
--cf-accent |
Color of the Pay button and of the selection |
--cf-accent-text |
Button text color |
--cf-bg |
Form background |
--cf-text |
Main text |
--cf-muted |
Secondary text |
--cf-border |
Borders |
--cf-field |
Field background |
--cf-radius |
Overall border radius |
--cf-font |
Font (inherit by default: your page’s font) |
.cf-sp { --cf-accent: #c01826; --cf-radius: 0; --cf-font: "Questrial", sans-serif; }.cf-sp-bouton { text-transform: uppercase; letter-spacing: .06em; }.cf-sp-methode.cf-sp-actif { box-shadow: none; background: #fff3f3; }Available classes: .cf-sp, .cf-sp-entete, .cf-sp-titre, .cf-sp-montant, .cf-sp-methodes, .cf-sp-methode, .cf-sp-actif, .cf-sp-telephone, .cf-sp-indicatif, .cf-sp-input, .cf-sp-otp, .cf-sp-ussd, .cf-sp-bouton, .cf-sp-lien, .cf-sp-erreur, .cf-sp-attente, .cf-sp-spinner, .cf-sp-qr, .cf-sp-pied, .cf-sp-securise, .cf-sp-propulse (the Cartflox mention: free color, mandatory display). Each screen also carries a .cf-sp-vue-<name> class, where <name> is one of formulaire (form), attente (waiting), otp, application, redirection, succes (success), echec (failure).
All labels can be replaced with the labels option, for example labels: { payer: "Pay now", telephone: "Your Mobile Money number", succes: "Payment received!" }.
Events
Section titled “Events”| Event | When |
|---|---|
onSession(s) |
Session created: s.id, s.orderId, s.url (the equivalent Cartflox page) |
onStatus(r) |
On each payment or tracking response: r.status |
onRedirect(url) |
Hosted page (cards, some aggregators): the URL the customer is leaving for, autoRedirect: false to handle it yourself. Also called for an app link (Wave, Djamo): the widget then stays displayed with the “Open” button and tracks the payment. |
onSuccess(r) |
Payment confirmed: r.sessionId, r.orderId, r.amount, r.currency |
onError(e) |
Payment declined, timeout or network error: e.message |
Option B: headless, you design everything
Section titled “Option B: headless, you design everything”Same engine, no interface: you call the session and display whatever you want.
In headless mode, you draw the footer yourself: the “Powered by Cartflox” mention with a link to https://cartflox.com is still required, just as in the ready-made form.
Cartflox.softpay.configure({ publicKey: "af_live_pub_YOUR_KEY" });
const session = await Cartflox.softpay.createSession({ amount: 5000, currency: "XOF", metadata: { order_id: "1042" },});
const methods = await session.methods("ci");// [{ code, gatewayId, name, provider, country, type, logo, requiresPhone, dialCode }]
const r = await session.pay({ method: methods[0], phone: "+2250700000000", name: "Awa Koné" });switch (r.status) { case "SUCCESS": /* paid */ break; case "PENDING": /* request pushed to the phone: show r.instructions, then session.waitForResult() */ break; case "REQUIRE_OTP": /* confirmation code (Orange Money, Magma OnePay, Paystack...): show r.ussdCode or r.instructions, then session.pay({ ..., otp }) */ break; case "APP_LINK": /* Wave, Djamo: button to r.redirectUrl (target _blank), r.qr as an image on desktop, then session.waitForResult() */ break; case "REDIRECT": window.location.href = r.redirectUrl; break; // provider's hosted page case "INLINE_CARD": window.location.href = session.url; break; // cards: secure Cartflox page default: /* r.message */}
const fin = await session.waitForResult({ timeoutMs: 180000, onTick: (d) => console.log(d.status) });// fin.status: SUCCESS | FAILED | CANCELLED| Method | Role |
|---|---|
createSession(options) |
Same fields as POST /v1/checkout/sessions. Returns a session. |
session.methods(country?) |
Available payment methods, filtered by country (ISO code). |
session.pay({ method, phone, name, email, country, otp }) |
Starts the payment. Returns { status, message, redirectUrl, application, qr, instructions, ussdCode, providerReference }. application and qr (image as a data URL) are only filled for APP_LINK; instructions carries the provider’s instructions when it gives any. |
session.getStatus() |
Current status: { status, paid, order_id, provider }. |
session.waitForResult({ intervalMs, timeoutMs, onTick }) |
Polls the status until a final state. |