Orders API and Online Payment (Konnect) Guide
This file documents the order-management endpoints exposed byOrdersController. It covers the cart/wishlist flow, the order lifecycle (creation, update, archive), assigning content for free, applying coupons, exporting invoices, and the full Konnect online-payment flow (init, webhook, history, public details page).
Table of Contents
- Overview
- Base URL and Headers
- Order Model
- Order Statuses
- List Orders
- Create Order
- Show Order
- Update Order Status / Notes
- Update Order Notes Only
- Archive Order
- Unarchive Order
- Add To Cart
- Update Cart
- Remove From Cart
- Convert Cart To Order
- Apply Coupon (Cart)
- Unapply Coupon (Cart)
- Assign Trainings To Students (Free)
- Unassign Trainings From Student
- Assign Exams To Students (Free)
- Unassign Exams From Student
- Export Order PDF (Single)
- Export Orders PDF (Bulk ZIP)
- Konnect — Overview
- Konnect — Initiate Payment
- Konnect — Webhook
- Konnect — Payment Details (proxy)
- Konnect — Order Webhook History
- Konnect — Public Details Page (by paymentRef)
- Internal Reused Methods
- Known Implementation Notes
1. Overview
This file covers:- the order data model (
App\Models\Order) and its status table (App\Models\OrderStatus) - the cart / wishlist / order type system (
typecolumn) - the public list, create, show, update, archive, and unarchive endpoints
- the full cart workflow:
add_to_cart,update_cart,remove_from_cart,convert_cart_to_order - coupon apply / unapply on the cart
- free assignment / unassignment of trainings and exams to students
- PDF invoice exports (single and bulk)
- the Konnect online-payment integration: init, webhook, payment details proxy, history, and public details page
2. Base URL and Headers
Base API URL:Authorization required), all other endpoints require Bearer token + the company-scope middleware.
3. Order Model
TheApp\Models\Order model is backed by the orders table. Soft deletes and the Archivable trait are enabled.
Order type constants
Fillable Fields
Auto-managed Fields
Appended Attributes
Relationships
Side-effects (booted())
creating: generates a uniquereferenceif missing, setscreated_at/updated_atupdating: refreshesupdated_atupdated: whenorder_status_idtransitions intoPAID_ONLINEorPAID_OFFLINEfrom a non-paid status, awards the student loyalty points based onLoyaltyPointsSettings.key='points_per_tnd'
Helper Methods
sync_order_items(array $trainingIds, array $quizIds)— replaces order items with the given lists, using each item’s currentfinal_price; updates the student’sowned_*slug arrays for any added/removed slugs. Used byconvert_cart_to_order(),store(),update_cart().assign_free_order_items(array $trainingIds, array $quizIds)— attaches items at price0, without detaching existing items, and adds the slugs to the student’sowned_*arrays. Used byassign_trainings_to_students(),assign_exams_to_students(), andVoucherController::apply_voucher().
4. Order Statuses
TheApp\Models\OrderStatus model is backed by the order_status table. Constants exposed:
Status table model
Status-driven business rules
PENDING_IDandPENDING_ONLINE_IDblock the cart: items in those statuses cannot be re-added to a cart for the same student/company (HTTP 409 withpending_conflicts).PAID_ONLINE_IDandPAID_OFFLINE_IDare considered “paid”. Updating an order into one of these triggers:- granting
owned_*slugs to the student - de-duplicating items already in another paid order (and noting it in
order_system_log) - awarding loyalty points (only on first transition into paid)
- sending the “Order Accepted” notification
- granting
archive()only accepts orders in:PAID_OFFLINE,PAID_ONLINE,CANCELLED,PAYMENT_FAILED.
5. List Orders
Returns paginated finalized orders (type = ORDER) for a company.
Query Parameters
Only one ofsearch_by_training,search_by_exam,search_by_studentcan be active at a time. When none is active, the defaultsearchmatches against the orderreference,payment_ref, orstudent.email.
Example Request
Success Response
6. Create Order
Creates a finalizedORDER directly (skipping the cart). Items are attached using their current final_price.
Request JSON
trainingsis required whenquizzesis empty (and vice-versa)- IDs must be
distinctand exist
Success Response
7. Show Order
Returns one order with student/status/trainings/quizzes/coupon eager-loaded.Query Parameters
Example Request
Success Response
8. Update Order Status / Notes
Updatesorder_status_id (and optional notes). When transitioning to a paid status, the controller:
- grants the student ownership of the order’s items
- removes duplicates already paid in another order (recorded in
order_system_log) - awards loyalty points on first transition to paid
- sends an “Order Accepted” or “Order Cancelled” notification (after DB commit)
Request JSON
Success Response
9. Update Order Notes Only
Admin / non-student endpoint to update only thenotes field.
Request JSON
Success Response
Forbidden Response (student / instructor)
10. Archive Order
Soft-archives an order. Only orders inPAID_OFFLINE, PAID_ONLINE, CANCELLED, or PAYMENT_FAILED status can be archived.
Example Request
Success Response
Already Archived / Wrong Status
11. Unarchive Order
Restores an archived order.Success Response
12. Add To Cart
Adds a single training or a single quiz to the student’s cart. Creates the cart on first use.Request JSON
Provide exactly one oftraining_idorquiz_id. The endpoint blocks the request when:Every
- the same item is already in another
Pending/Pending Onlineorder (HTTP 409, includespending_conflicts)- the item was previously unassigned from the student (HTTP 403)
- the item is already in the cart (HTTP 403,
This training is already in your cart./This exam is already in your cart.)messageof the cart and checkout endpoints (add / remove / convert to order, coupon format, Konnect init) is translated from theAccept-Languageheader (en,fr,ar; any other value falls back toen). The examples on this page are the English ones;pending_conflictsand the slugs inside the message are never translated.
Success Response
Pending Conflict Response
13. Update Cart
Replaces the entire cart (or wishlist) contents in one call.Request JSON
typeis2for cart,1for wishlisttrainingsandquizzesare required (eachmin:1)
Success Response
Pending Conflict Response (HTTP 409)
Same shape asadd_to_cart (with pending_conflicts).
14. Remove From Cart
Removes a single training or quiz from the student’s cart. If the cart becomes empty and has no coupon, it is deleted.Request JSON
Provide exactly one oftraining_idorquiz_id.company_idis optional but recommended on multi-tenant deployments.
Success Response (cart still has items)
Success Response (cart deleted)
Not Found Response
15. Convert Cart To Order
Converts the student’s cart into a finalizedORDER with status PENDING. The cart is detached, items are migrated to the new order via sync_order_items, and an “Order Received” notification is sent (after DB commit).
Example Request
Success Response
Empty Cart Response
Pending Conflict Response (HTTP 409)
16. Apply Coupon (Cart)
Applies a coupon code to the student’s active cart.Request JSON
- code must be uppercase letters and digits only
- coupon must exist, be
active, in its date window, have remainingmax_usage, and meetapplicable_if_total_is_above - the coupon cannot have been previously applied/used by this student
Success Response
Common Error Responses (HTTP 400/403)
17. Unapply Coupon (Cart)
Removes the currently applied coupon from the student’s cart and refunds one usage to the coupon.Success Response
No Coupon Applied Response
18. Assign Trainings To Students (Free)
Bulk-createsPAID_OFFLINE orders for one or more students, attaching the listed trainings at price 0 and granting ownership.
Request JSON
Success Response
19. Unassign Trainings From Student
Bulk-createsPAID_OFFLINE orders to record the unassignment, then flips access_revoked = true on the original order_items rows and removes the slugs from the student’s owned_trainings.
Request JSON
Success Response
20. Assign Exams To Students (Free)
Same as section 18, but for standalone exams (quizzes whosetraining_id and chapter_id are both null). Quizzes that are linked to a training or chapter are rejected.
Request JSON
Success Response
Validation Error (non-standalone exam)
21. Unassign Exams From Student
Mirror of section 19, but for exams.Request JSON
Success Response
22. Export Order PDF (Single)
Renders the order as a PDF invoice (pdf_order_template blade) and returns it inline.
Example Request
Success Behavior
- returns
Content-Type: application/pdf - file name pattern:
order-{reference}.pdf(ororder-{id}.pdfif no reference)
23. Export Orders PDF (Bulk ZIP)
Renders multiple orders as PDFs and returns them as a ZIP download. Per-order errors are written into the ZIP asorder-{id}-ERROR.txt.
Request JSON
Success Behavior
- returns a ZIP download named
orders-export-{uuid}.zip - the temporary ZIP file is deleted after sending
24. Konnect — Overview
Konnect is the online-payment gateway. The flow is:- Init —
POST /api/order/{user_id}/konnect/initflips the student’s CART to ORDER +PENDING_ONLINE, then calls Konnect’s/init. The response containspayUrl(open in browser to pay) andpaymentRef(stored on the order). - Pay — the student opens
payUrl, completes payment on Konnect. - Webhook — Konnect calls
GET /api/konnect/webhook?payment_ref=.... The controller fetches the payment from Konnect, updatesorder_status_id, grants ownership oncompleted. - Inspection —
GET /api/konnect/payments/{paymentId}proxies to Konnect for raw status;GET /api/order/{id}/konnect-historyreturns the local webhook trace;GET /order/konnect-payment-details/{paymentRef}is a public details page (HTML or?json=1).
Required Environment Variables
Money Scale
- The init endpoint converts
discounted_priceto millimes before sending to Konnect (amount = round(discounted_price * 1000)). - The DB stores prices in TND (the controller’s
priceScale = 1000).
KonnectHistory Model
Backed by tablekonnect_history (note: singular).
Two private helpers append lines:
appendKonnectHistoryRaw($order, $rawLine)— raw line, no timestamp prefix (used for the human “Order passed by …” line)historyFromWebhook($order, $action, $note=null)— adds a[YYYY-mm-dd HH:ii:ss] {action}; {note}line
25. Konnect — Initiate Payment
Resolves the student’s CART (or returns 404 if none), flips it to ORDER +PENDING_ONLINE, generates a Konnect payment, persists payment_ref, and sends a customizable notification to the student with the payUrl.
Request JSON (all optional)
notif_email— boolean, whentruethe notification is also emailednotif_type_id— defaults toNotificationType::SUCCESS_ID
Example Request
Success Response
No Cart Response
Empty Cart Response
Zero Amount Response
Konnect Init Failure (HTTP 502)
26. Konnect — Webhook
Public endpoint hit by Konnect (or by the success page after redirect). Resolves the order viapayment_ref, fetches the live payment from Konnect, applies the corresponding status change, and grants ownership on completed.
Content negotiation:
- pass
?json=1(orAccept: application/json) to get JSON- pass
?view=1or noAcceptfor HTML (renderskonnect_payment_page_success/konnect_payment_page_pending/konnect_payment_page_failed)
Status Mapping
When the order is already paid, the endpoint returns idempotently with200and"Order already paid.".
Example Request (JSON)
Success Response (completed)
Pending Response (HTTP 202)
Failure Response (HTTP 402)
Validation / Not Found Errors
27. Konnect — Payment Details (proxy)
Proxies aGET /payments/{paymentId} call to Konnect and returns their raw JSON. Useful in Postman to inspect a payment’s status manually.
Example Request
Success Response (raw Konnect payload)
Validation / Konnect Error Pass-through
Konnect 4xx errors (e.g., 404 for unknown payment) are passed through with the same status code and body.
28. Konnect — Order Webhook History
Returns the Konnect history trace stored locally for one order.Example Request
Success Response
Empty History Response
29. Konnect — Public Details Page (by paymentRef)
Combines the order invoice data, a live Konnect lookup, and the local webhook history. Renders an HTML page by default; pass?json=1 for JSON. Public (no Bearer token).
Example Request (JSON)
Success Response
Order Not Found / Konnect Error
- When the local order is not found,
orderisnullbut the page still renders the live Konnect data and a minimal invoice shell. - When Konnect itself fails the request,
konnect.okisfalseandkonnect.errordescribes the failure (e.g.,"Payment not found on Konnect.").
Validation Error
30. Internal Reused Methods
These methods are part ofOrdersController but do not have their own route:
findPendingConflicts(int $studentId, int $companyId, array $trainingIds, array $quizIds): array— returns trainings/quizzes already attached to aPENDINGorPENDING_ONLINEorder for the same student/company. Used byadd_to_cart(),update_cart(),convert_cart_to_order().pendingConflictMessage(array $conflicts): string— formats the human message for the conflict response.getThemeForCompany(int $companyId): array— loads platform branding (logo, name) for invoice PDFs.buildOrderPdfViewData(Order $order): array— gathers the data passed to thepdf_order_templateblade.appendKonnectHistoryRaw(Order $order, string $rawLine): void— appends a non-timestamped line tokonnect_history.webhook_history.historyFromWebhook(Order $order, string $action, ?string $note = null): void— appends a timestamped[date] action; noteline.konnectViewName(string $status): string— picks the Blade view for the webhook HTML response (konnect_payment_page_success/konnect_payment_page_pending/konnect_payment_page_failed).
Order::sync_order_items(array $trainingIds, array $quizIds)— see section 3.Order::assign_free_order_items(array $trainingIds, array $quizIds)— see section 3.
31. Known Implementation Notes
index()defaults totype = ORDER; carts and wishlists are excluded unless atypequery parameter is sent explicitly.- The cart auto-deletes itself in
remove_from_cart()when empty AND no coupon is attached, so subsequent calls to “show the cart” can return nothing legitimately. convert_cart_to_order()deletes the source cart after migrating items so there are no “ghost carts” left on the user.- Loyalty points are awarded twice in code (in the
Order::booted()updatedhook and inupdate()’s duplicate-removal path). The hook usesLoyaltyPointsSettings.key='points_per_tnd';update()sumsloyalty_pointsper training/quiz. initiate_konnect_payment()requires a CART; it does not create one. Make sureadd_to_cart(orupdate_cart) was called first.konnect_webhook()is publicly reachable and resolves orders strictly bypayment_ref. The legacy{id}path segment is preserved but ignored.orderKonnectDetailsByRef()is public (declared outside theauth:sanctumgroup) and can be browsed without a token — anyone with thepaymentRefcan see the invoice-style page.- Konnect’s
KONNECT_URL,KONNECT_INIT_URL,KONNECT_API_KEY, andKONNECT_WALLET_IDmust be configured in.env. The amount is always sent in millimes. - Several Konnect-related routes are commented out in
routes/api.php(e.g. older variants of the init route). The currently active routes are listed above.