Card machines were designed for shops. QR Pay JJJ is designed for the way South Africans work — in taxis, on street corners, in living rooms, on WhatsApp.
Become a merchantMoney lands in your wallet the moment your customer confirms.
During beta, keep every rand. No hidden card-machine cuts.
Only signed-in wallet holders can send. No chargebacks, no bounced cards.
Any smartphone, any bank, any suburb. If they have a camera, they can pay.
Every payment logged. Filter sent vs received. Export coming soon.
Show a service type — driver, tutor, vendor — so customers know who they're paying.
Developers
Drop a button on your Shopify, WooCommerce, or custom store. We handle checkout, notify you via webhook when the customer pays, and settle straight into your wallet.
Sign in, open Merchant → API keys, and copy the key. Treat it like a password.
curl -X POST https://qr-pay-jjj.lovable.app/api/public/v1/checkouts \
-H "Authorization: Bearer qp_live_..." \
-H "Content-Type: application/json" \
-d '{
"amount_cents": 24900,
"reference": "order_1234",
"note": "Cappuccino x2",
"success_url": "https://your-store.com/thanks",
"cancel_url": "https://your-store.com/cart"
}'
# → { "id": "chk_…", "url": "https://.../checkout/chk_…", "status": "pending", "expires_at": "..." }<script src="https://qr-pay-jjj.lovable.app/qrpay.js" async></script>
<button data-qrpay data-checkout="chk_..." data-success-redirect="/thanks">
Pay with QR Pay
</button>Opens a popup checkout, dispatches qrpay:paid and qrpay:cancelled events, and redirects on success.
We POST checkout.paid, checkout.expired, and checkout.cancelled to your endpoint with an X-QRPay-Signature: t=<ts>,v1=<hmac> header. HMAC-SHA256 of ts + "." + rawBody using your signing secret.
import crypto from "node:crypto";
app.post("/webhooks/qrpay", express.raw({type: "application/json"}), (req, res) => {
const [t, v1] = req.header("X-QRPay-Signature").split(",");
const ts = t.split("=")[1];
const sig = v1.split("=")[1];
const expected = crypto
.createHmac("sha256", process.env.QRPAY_WEBHOOK_SECRET)
.update(ts + "." + req.body.toString())
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.status(401).end();
}
const event = JSON.parse(req.body.toString());
if (event.event === "checkout.paid") fulfil(event.checkout.reference);
res.status(200).end();
});Create your account, generate your QR, start accepting payments today.
Get started