Example: a complete shop
apps/demo-shop is a small shop (“Boutique Démo Dakar”) that integrates Xaalis the way every merchant should. It’s
about 250 lines of TypeScript with the Node SDK, and its tests run against a real Xaalis stack.
yarn workspace @xaalis/demo-shop setup # creates a test merchant and points its webhook at the shopyarn workspace @xaalis/demo-shop dev # http://localhost:4200The three moments of a payment
Section titled “The three moments of a payment”1. Checkout — create the payment for exactly the order total.
const payment = await xaalis.payments.create( { amount: order.total, // integer XOF client_reference: order.id, success_url: `${baseUrl}/orders/${order.id}`, cancel_url: `${baseUrl}/orders/${order.id}`, metadata: { order_id: order.id }, }, // A double click or retry can't create a second payment. Prefixed with the mode (test/live keys share scope, B-4). { idempotencyKey: `${xaalis.testMode ? "test" : "live"}:order:${order.id}` },);return redirect(payment.checkout_url);2. The customer comes back — show, don’t trust. Landing on success_url proves nothing. The page shows
“Confirmation en cours…” and asks Xaalis from the server:
const p = await xaalis.payments.retrieve(order.paymentId); // server to server: this *can* be trustedif (p.status === "succeeded" && p.amount === order.total) markPaid(order);3. The webhook — the source of truth.
const event = constructEvent(rawBody, req.header("xaalis-signature"), webhookSecret); // throws on a bad signatureif (!rememberEvent(event.id)) return ok(); // at-least-once delivery: ignore repeatsif (event.livemode !== isLive) return ok(); // test and live share one URLconst p = event.data.object;if (p.id === order.paymentId && p.amount === order.total && p.currency === "XOF") markPaid(order); // oncemarkPaid is idempotent: an order already paid is never paid again, whether the webhook or the return page gets
there first.
What its tests prove
Section titled “What its tests prove”| Scenario | Outcome |
|---|---|
| Checkout | a payment for exactly the order total, redirect to the hosted checkout |
| Customer lands on the return page before paying | order stays unpaid |
| Customer pays | the signed webhook marks the order paid, exactly once |
| Payment declined | order marked failed |
| Forged signature · replayed event · signed event with the wrong amount · live event on a test shop | rejected or ignored; order unchanged |
| Webhook lost | the return page’s server-side check still completes the order |
The same rules, in PHP for WordPress, are in the WooCommerce plugin.