Documentation Index
Fetch the complete documentation index at: https://developers.ligdicash.com/llms.txt
Use this file to discover all available pages before exploring further.
This guide walks through the end-to-end flow for an e-commerce site: from cart validation on the frontend to delivery confirmation on the backend. It uses hosted payin, the simplest integration mode and the one compatible with every operator.
Prerequisites: you have a LigdiCash API project with an Apikey and an Auth Token. See Create an API project.
Flow overview
Customer Frontend Backend LigdiCash
| | | |
|-- Validate -> | | |
| cart |-- POST /pay --> | |
| | |-- create -----> |
| | | <-- token ----- |
| | <-- pay_url ---- | |
|-- Redirect -> | | |
| |--- Open URL ---> | |
| [Payment on the LigdiCash page] | |
| | | <-- callback -- |
| | |-- confirm ----> |
| | | <-- status ---- |
| | |-- Confirms |
| | | order |
| <-- Return ---- | | |
| return_url |-- GET /status -> | |
| | <-- result ----- | |
Never call the LigdiCash API directly from the frontend. Your Apikey and Auth Token must stay on your backend. See Recommended architecture.
Step 1 — Store the order before payment
Before creating the LigdiCash invoice, persist the order in your database with status pending. You’ll need its identifier to track the payment.
INSERT INTO orders (id, customer_id, amount, status, created_at)
VALUES ('order_7f3a9b', 42, 15000, 'pending', NOW());
Use a non-guessable internal identifier (UUID or prefixed ID) as your transaction_id. You’ll pass it in custom_data and use it to identify the order in the callback.
Step 2 — Create the invoice on the backend
Your backend calls the create-invoice endpoint and stores the returned token.
curl -X POST https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/create \
-H "Apikey: {API_KEY}" \
-H "Authorization: Bearer {AUTH_TOKEN}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"commande": {
"invoice": {
"items": [
{
"name": "Premium subscription",
"description": "1 month of Premium access",
"quantity": 1,
"unit_price": 15000,
"total_price": 15000
}
],
"total_amount": 15000,
"devise": "XOF",
"description": "Order #order_7f3a9b",
"customer": "",
"customer_firstname": "Aminata",
"customer_lastname": "Ouedraogo",
"customer_email": "aminata@example.com",
"external_id": "",
"otp": ""
},
"store": {
"name": "My Store",
"website_url": "https://mystore.com"
},
"actions": {
"cancel_url": "https://mystore.com/payment/cancel",
"return_url": "https://mystore.com/payment/success",
"callback_url": "https://mystore.com/api/ligdicash/callback"
},
"custom_data": {
"transaction_id": "order_7f3a9b"
}
}
}'
Success response (response_code: "00"):
{
"response_code": "00",
"response_text": "https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/confirm?token=eyJ0...",
"description": "Checkout-Invoice created with success.",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
Store the token in your database linked to the order. You’ll need it to verify the payment.
UPDATE orders
SET ligdicash_token = 'eyJ0eXAiOiJKV1Qi...'
WHERE id = 'order_7f3a9b';
Step 3 — Redirect the customer to the payment page
Return response_text (the payment URL) to your frontend, then redirect the customer.
// Backend call → returns the payment URL
const { payUrl } = await fetch("/api/orders/order_7f3a9b/pay", {
method: "POST",
}).then((r) => r.json());
// Redirect in the same tab (recommended)
window.location.href = payUrl;
// Or in a new tab
// window.open(payUrl, "_blank");
Never open the LigdiCash URL in an <iframe>. LigdiCash blocks iframe rendering. Use a same-tab redirect, a new tab, or a popup. See Common pitfalls.
The customer lands on the LigdiCash payment page, picks their mobile money operator, and completes the payment. LigdiCash then redirects them to your return_url (success) or cancel_url (cancellation).
Step 4 — Handle the callback
LigdiCash sends a POST notification to your callback_url whenever the transaction status changes. Your backend must:
- Identify the order via
custom_data
- Re-verify the status using the
confirm endpoint (never trust the payload alone)
- Update the order in the database
- Reply
200 OK
app.post("/api/ligdicash/callback", express.json(), async (req, res) => {
// Reply 200 immediately to avoid retries
res.sendStatus(200);
// Extract transaction_id from custom_data
const customData = req.body.custom_data ?? [];
const entry = Array.isArray(customData)
? customData.find((e) => e.keyof_customdata === "transaction_id")
: null;
if (!entry) return;
const transactionId = entry.valueof_customdata;
// Look up the order and its LigdiCash token
const order = await db.orders.findById(transactionId);
if (!order || order.status !== "pending") return;
// Re-verify the status with LigdiCash
const confirm = await fetch(
`https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/confirm?token=${order.ligdicash_token}`,
{
headers: {
Apikey: process.env.LIGDICASH_API_KEY,
Authorization: `Bearer ${process.env.LIGDICASH_AUTH_TOKEN}`,
Accept: "application/json",
},
}
).then((r) => r.json());
if (confirm.status === "completed") {
await db.orders.update(transactionId, { status: "paid" });
await delivery.trigger(transactionId);
} else if (confirm.status === "notcompleted") {
await db.orders.update(transactionId, { status: "failed" });
}
// If "pending": do nothing, wait for the next callback
});
LigdiCash sends two POST requests to your callback for every event: one as application/x-www-form-urlencoded and one as application/json. Handle them idempotently. See Callback idempotency.
Step 5 — Verify the status on the frontend
After the redirect to your return_url, the frontend asks your backend for the final result to display. The return_url is a UI signal — it does not prove the payment succeeded.
Frontend — return_url page
// Read transaction_id from the URL (e.g. /payment/success?order=order_7f3a9b)
const params = new URLSearchParams(window.location.search);
const orderId = params.get("order");
const { status } = await fetch(`/api/orders/${orderId}/status`).then(
(r) => r.json()
);
switch (status) {
case "paid":
showConfirmation();
break;
case "pending":
// The callback hasn't arrived yet — show a loader and poll
showVerificationWait();
break;
case "failed":
showError();
break;
}
Backend — GET /api/orders/:id/status
app.get("/api/orders/:id/status", async (req, res) => {
const order = await db.orders.findById(req.params.id);
if (!order) return res.status(404).json({ error: "not_found" });
// If still pending, ask LigdiCash in real time
if (order.status === "pending") {
const confirm = await fetch(
`https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/confirm?token=${order.ligdicash_token}`,
{
headers: {
Apikey: process.env.LIGDICASH_API_KEY,
Authorization: `Bearer ${process.env.LIGDICASH_AUTH_TOKEN}`,
Accept: "application/json",
},
}
).then((r) => r.json());
if (confirm.status === "completed") {
await db.orders.update(order.id, { status: "paid" });
order.status = "paid";
}
}
res.json({ status: order.status });
});
Recap
Store the order
Create the order in your database with status pending before any LigdiCash call.
Create the invoice
Backend → POST /pay/v01/redirect/checkout-invoice/create. Store the returned token.
Redirect the customer
Frontend → redirect to response_text (same tab or popup). Never an iframe.
Handle the callback
Reply 200 immediately, then re-verify via confirm with the stored token.
Confirm the order
Update the status in your database and trigger delivery if status === "completed".
Display the result
Frontend → ask your backend for the real status after returning to return_url.
Related pages