Skip to content

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.

HTML
<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>
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).

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)
CSS example: your brand style
.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!" }.

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

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.

JavaScript
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.