Skip to content

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.

Terminal window
yarn workspace @xaalis/demo-shop setup # creates a test merchant and points its webhook at the shop
yarn workspace @xaalis/demo-shop dev # http://localhost:4200

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 trusted
if (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 signature
if (!rememberEvent(event.id)) return ok(); // at-least-once delivery: ignore repeats
if (event.livemode !== isLive) return ok(); // test and live share one URL
const p = event.data.object;
if (p.id === order.paymentId && p.amount === order.total && p.currency === "XOF") markPaid(order); // once

markPaid is idempotent: an order already paid is never paid again, whether the webhook or the return page gets there first.

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.