Skip to main content
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 prefix konnect → flouci and read the renamed flouci_status field. The order column orders.payment_ref now stores the Flouci payment_id.
All endpoints live in App\Http\Controllers\OrdersController. Amounts are handled in millimes (1 TND = 1000 millimes).

Table of Contents

  1. Overview & End-to-End Flow
  2. Base URL and Headers
  3. Environment Configuration
  4. Money / Amount Scale
  5. Online Payment Statuses
  6. payment_ref / payment_id & Order Resolution
  7. Endpoint — Initiate Payment
  8. Endpoint — Webhook / Callback (status confirmation)
  9. Endpoint — Verify Payment (proxy)
  10. Endpoint — Order Webhook History
  11. Endpoint — Public Payment Details Page
  12. Flouci History Model
  13. Notifications on Init
  14. Idempotency & Ownership Grant
  15. Error Reference
  16. Implementation Notes & Gotchas

1. Overview & End-to-End Flow

Flouci is the online alternative to the offline / free-assignment payment paths. The lifecycle is:
Steps:
  1. Build the cart — add_to_cart / update_cart (see Orders & payment). Init does not create a cart; it requires one to already exist.
  2. Init — POST /api/order/{user_id}/flouci/init flips the student’s CART row in place to an ORDER with status PENDING_ONLINE, calls Flouci’s /generate_payment, stores the returned payment_id on the order (payment_ref column), and returns pay_url (Flouci’s link) + payment_ref.
  3. Pay — the student opens pay_url and completes payment on Flouci’s checkout.
  4. 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 stored payment_id, verifies the live payment via Flouci’s /verify_payment/{id}, maps its status to an order status, and grants content ownership on SUCCESS.
  5. 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 by payment_id. Anyone holding the payment_id can 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)

Flouci appends ?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_price already reflects any applied coupon.

5. Online Payment Statuses

The Flouci flow uses four of the six OrderStatus values (App\Models\OrderStatus):

Flouci → Order status mapping (webhook)

The webhook only trusts the response when the verify call returns top-level success: true.
A PENDING webhook never downgrades an order that is already PAID_ONLINE.

6. payment_ref / payment_id & Order Resolution

  • On a successful init, Flouci returns a payment_id, which the API persists on orders.payment_ref.
  • Every subsequent Flouci interaction is resolved by that id. The webhook accepts payment_id (Flouci’s query-param name) or payment_ref (alias) and resolves Order::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’s CART, 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=1 or Accept: application/json → JSON payload.
  • ?view=1 or a browser (no JSON Accept) → an HTML page:
    • SUCCESS → flouci_payment_page_success
    • PENDING → flouci_payment_page_pending
    • anything else → flouci_payment_page_failed

Behavior

  1. Reads payment_id (or payment_ref) and validates it (^[A-Za-z0-9_-]+$).
  2. Looks up the order by stored id. If none → 404.
  3. Idempotency: if the order is already PAID_ONLINE or PAID_OFFLINE, returns immediately with 200 and "Order already paid." (no Flouci call, no re-grant).
  4. Otherwise GETs FLOUCI_VERIFY_URL/{payment_id}, checks success == true, reads result.status, maps it (see §5), saves the order, and on SUCCESS calls grantOwnedAssetsFromOrder($order).

Success Response — SUCCESS (HTTP 200)

Pending (HTTP 202) / Failure (HTTP 402)

Same payload shape with flouci_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 a GET /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: order is null, but the page still renders the live Flouci data and a minimal invoice shell.
  • Flouci lookup failed: flouci.ok is false and flouci.error describes the failure (e.g. "Payment not found on Flouci.").

12. Flouci History Model

Backed by table flouci_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 (via NotificationService::toUser):
  • Default title: "Payment link ready 🎉".
  • Default message: embeds the order reference, the amount (X.XXX TND), the pay_url, and a “check status anytime” hint.
  • Email: sent when notif_email is truthy (default true).
  • Type: notif_type_id (default NotificationType::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 calls student->grantOwnedAssetsFromOrder($order), which pushes the order’s training/exam slugs into the student’s owned_trainings / owned_exams.
  • Defensive cleanup: sanitizeOwnedAssets() runs at init, and on SUCCESS / 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() updated hook).

15. Error Reference


16. Implementation Notes & Gotchas

  • Init requires a pre-existing CART. It does not create one — call add_to_cart / update_cart first.
  • The CART row is flipped in place to an ORDER (Pending Online) — no new row is created.
  • payment_id (stored in payment_ref) is the source of truth. The webhook and details endpoints resolve by it; Flouci sends it as the payment_id query 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 == true and result.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).