Flouci Online-Payment Integration Guide
This file documents the online-payment flow powered by Flouci (a Tunisian payment gateway). It is a focused companion to Orders & payment (the broader order/cart system) and zooms in on everything related to paying for an order online: initiating a payment, the callback/webhook that confirms it, the online payment statuses, the inspection endpoints, history tracing, and the public details page.Migration note: this gateway replaces Konnect (decommissioned). The endpoint shapes were kept deliberately close to the old Konnect ones, so frontends only need to swap the URL prefixAll endpoints live inkonnect→flouciand read the renamedflouci_statusfield. The order columnorders.payment_refnow stores the Floucipayment_id.
App\Http\Controllers\OrdersController. Amounts are handled in millimes (1 TND = 1000 millimes).
Table of Contents
- Overview & End-to-End Flow
- Base URL and Headers
- Environment Configuration
- Money / Amount Scale
- Online Payment Statuses
payment_ref/payment_id& Order Resolution- Endpoint — Initiate Payment
- Endpoint — Webhook / Callback (status confirmation)
- Endpoint — Verify Payment (proxy)
- Endpoint — Order Webhook History
- Endpoint — Public Payment Details Page
- Flouci History Model
- Notifications on Init
- Idempotency & Ownership Grant
- Error Reference
- Implementation Notes & Gotchas
1. Overview & End-to-End Flow
Flouci is the online alternative to the offline / free-assignment payment paths. The lifecycle is:- Build the cart —
add_to_cart/update_cart(see Orders & payment). Init does not create a cart; it requires one to already exist. - Init —
POST /api/order/{user_id}/flouci/initflips the student’sCARTrow in place to anORDERwith statusPENDING_ONLINE, calls Flouci’s/generate_payment, stores the returnedpayment_idon the order (payment_refcolumn), and returnspay_url(Flouci’slink) +payment_ref. - Pay — the student opens
pay_urland completes payment on Flouci’s checkout. - Webhook / redirect — Flouci redirects the browser to (and pings the webhook with)
GET /api/flouci/webhook?payment_id=.... The API looks up the order by the storedpayment_id, verifies the live payment via Flouci’s/verify_payment/{id}, maps its status to an order status, and grants content ownership onSUCCESS. - Inspect — the verify proxy, history, and public-details endpoints let you read the live and stored payment state.
2. Base URL and Headers
Base API URL:{{base_url}}/api.
The webhook and the public details page are intentionally unauthenticated — Flouci’s servers (and the browser redirect) call the webhook, and the details page is shareable bypayment_id. Anyone holding thepayment_idcan view the invoice-style details page.
3. Environment Configuration
These must be set in.env:
Authentication: every Flouci call sends the header
Authorization: Bearer <FLOUCI_PUBLIC_KEY>:<FLOUCI_PRIVATE_KEY>.
Flouci /generate_payment payload (sent server-side)
?payment_id=... to success_link, fail_link, and webhook.
4. Money / Amount Scale
- Flouci expects millimes for TND (1 TND = 1000 millimes; e.g.
1.500 TND→1500), sent as a string. - The init endpoint computes the amount from the order’s appended
discounted_price:
- The amount must be
> 0, otherwise init returns HTTP 422 ("Order amount must be greater than 0 for online payment."). discounted_pricealready reflects any applied coupon.
5. Online Payment Statuses
The Flouci flow uses four of the sixOrderStatus values (App\Models\OrderStatus):
Flouci → Order status mapping (webhook)
The webhook only trusts the response when the verify call returns top-levelsuccess: true.
APENDINGwebhook never downgrades an order that is alreadyPAID_ONLINE.
6. payment_ref / payment_id & Order Resolution
- On a successful init, Flouci returns a
payment_id, which the API persists onorders.payment_ref. - Every subsequent Flouci interaction is resolved by that id. The webhook accepts
payment_id(Flouci’s query-param name) orpayment_ref(alias) and resolvesOrder::where('payment_ref', $id). - The id must match
^[A-Za-z0-9_-]+$. Anything else → HTTP 422"Invalid payment_id format.".
7. Endpoint — Initiate Payment
Resolves the student’sCART, flips it in place to an ORDER with PENDING_ONLINE, calls Flouci /generate_payment, stores the payment_id, and notifies the student with the pay link.
Request JSON (all optional — controls the notification)
Success Response (HTTP 200)
Error Responses
8. Endpoint — Webhook / Callback (status confirmation)
Public endpoint hit by Flouci after the student pays — both as the browser redirect (success_link / fail_link) and the server-to-server webhook. Resolves the order by payment_id, verifies the live payment with Flouci, maps the status, persists it, and grants ownership on SUCCESS.
Query Parameters
Content Negotiation
?json=1orAccept: application/json→ JSON payload.?view=1or a browser (no JSONAccept) → an HTML page:SUCCESS→flouci_payment_page_successPENDING→flouci_payment_page_pending- anything else →
flouci_payment_page_failed
Behavior
- Reads
payment_id(orpayment_ref) and validates it (^[A-Za-z0-9_-]+$). - Looks up the order by stored id. If none → 404.
- Idempotency: if the order is already
PAID_ONLINEorPAID_OFFLINE, returns immediately with 200 and"Order already paid."(no Flouci call, no re-grant). - Otherwise
GETsFLOUCI_VERIFY_URL/{payment_id}, checkssuccess == true, readsresult.status, maps it (see §5), saves the order, and onSUCCESScallsgrantOwnedAssetsFromOrder($order).
Success Response — SUCCESS (HTTP 200)
Pending (HTTP 202) / Failure (HTTP 402)
Same payload shape withflouci_status = PENDING (202) or FAILURE / EXPIRED (402). Already-paid hits return 200 with "Order already paid." and flouci_status: "SUCCESS".
Validation / Not Found / Server Errors
9. Endpoint — Verify Payment (proxy)
Proxies aGET /verify_payment/{paymentId} to Flouci and returns their raw JSON. Useful for manually inspecting a payment’s status (e.g. in Postman).
Success Response (raw Flouci payload, pass-through status)
Errors
Flouci 4xx errors (e.g. 404 for an unknown payment) are passed through with their original status code and body.
10. Endpoint — Order Webhook History
Returns the locally stored history trace for one order (append-only log of init + webhook events).Success Response
11. Endpoint — Public Payment Details Page
Combines the local order/invoice data, a live Flouci verify lookup, and the stored webhook history. Renders an HTML page by default; pass?json=1 for JSON. Public — no Bearer token.
Success Response (JSON)
Partial / Error States
- Order not found locally:
orderisnull, but the page still renders the live Flouci data and a minimal invoice shell. - Flouci lookup failed:
flouci.okisfalseandflouci.errordescribes the failure (e.g."Payment not found on Flouci.").
12. Flouci History Model
Backed by tableflouci_history (note: singular). One row per order via Order::flouciHistory() (hasOne).
Two private controller helpers append to it:
appendFlouciHistoryRaw($order, $rawLine)— appends a raw line (no timestamp prefix). Used for the human"Order passed by … with coupon …"line.historyFromWebhook($order, $action, $note = null)— appends a timestamped[YYYY-mm-dd HH:ii:ss] {action}; {note}line.
This trace is separate from the global audit log (insert_in_history_table(...)); both may be written during the Flouci flow.
13. Notifications on Init
After a successful init, a notification is sent to the student carrying the pay link (viaNotificationService::toUser):
- Default title:
"Payment link ready 🎉". - Default message: embeds the order reference, the amount (
X.XXX TND), thepay_url, and a “check status anytime” hint. - Email: sent when
notif_emailis truthy (defaulttrue). - Type:
notif_type_id(defaultNotificationType::SUCCESS_ID). - Failure-safe: if the notification fails, the init flow still succeeds; the failure is logged and traced in the Flouci history.
14. Idempotency & Ownership Grant
- Idempotent webhook: once an order is
PAID_ONLINE/PAID_OFFLINE, repeated webhook hits short-circuit with 200"Order already paid."— no second Flouci verify and no double-grant. - Ownership is granted at
SUCCESS, not at init. The webhook callsstudent->grantOwnedAssetsFromOrder($order), which pushes the order’s training/exam slugs into the student’sowned_trainings/owned_exams. - Defensive cleanup:
sanitizeOwnedAssets()runs at init, and onSUCCESS/ failure to strip any stale ownership predating the paid-gating guard. - Loyalty points are awarded on the first transition into a paid status (via the
Order::booted()updatedhook).
15. Error Reference
16. Implementation Notes & Gotchas
- Init requires a pre-existing
CART. It does not create one — calladd_to_cart/update_cartfirst. - The CART row is flipped in place to an
ORDER(Pending Online) — no new row is created. payment_id(stored inpayment_ref) is the source of truth. The webhook and details endpoints resolve by it; Flouci sends it as thepayment_idquery param.- Webhook is public — never embed secrets in it; treat it as internet-reachable.
- Public details page is browsable by anyone holding the
payment_id. - Amounts are always millimes, sent as a string; the init endpoint hard-codes
priceScale = 1000(DB in TND → millimes). - Only trust verified payments: the webhook requires top-level
success == trueandresult.status == "SUCCESS"before granting ownership. - Two history trails: the Flouci-specific
flouci_history.webhook_history(human-readable trace) and the global audit log (insert_in_history_table).
Related docs: Orders & payment (full cart/order/coupon/assignment flow — Flouci is summarized there too), Notifications (notifications), Archive, delete & restore flow (order archive lifecycle).