Skip to main content
Orders API and Online Payment (Konnect) Guide
This file documents the order-management endpoints exposed by OrdersController. 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

  1. Overview
  2. Base URL and Headers
  3. Order Model
  4. Order Statuses
  5. List Orders
  6. Create Order
  7. Show Order
  8. Update Order Status / Notes
  9. Update Order Notes Only
  10. Archive Order
  11. Unarchive Order
  12. Add To Cart
  13. Update Cart
  14. Remove From Cart
  15. Convert Cart To Order
  16. Apply Coupon (Cart)
  17. Unapply Coupon (Cart)
  18. Assign Trainings To Students (Free)
  19. Unassign Trainings From Student
  20. Assign Exams To Students (Free)
  21. Unassign Exams From Student
  22. Export Order PDF (Single)
  23. Export Orders PDF (Bulk ZIP)
  24. Konnect — Overview
  25. Konnect — Initiate Payment
  26. Konnect — Webhook
  27. Konnect — Payment Details (proxy)
  28. Konnect — Order Webhook History
  29. Konnect — Public Details Page (by paymentRef)
  30. Internal Reused Methods
  31. 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 (type column)
  • 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:
Suggested Postman variables:
Headers:
For PDF exports use:
The Konnect public details endpoint and the webhook endpoint are public (no Authorization required), all other endpoints require Bearer token + the company-scope middleware.

3. Order Model

The App\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 unique reference if missing, sets created_at/updated_at
  • updating: refreshes updated_at
  • updated: when order_status_id transitions into PAID_ONLINE or PAID_OFFLINE from a non-paid status, awards the student loyalty points based on LoyaltyPointsSettings.key='points_per_tnd'

Helper Methods

  • sync_order_items(array $trainingIds, array $quizIds) — replaces order items with the given lists, using each item’s current final_price; updates the student’s owned_* slug arrays for any added/removed slugs. Used by convert_cart_to_order(), store(), update_cart().
  • assign_free_order_items(array $trainingIds, array $quizIds) — attaches items at price 0, without detaching existing items, and adds the slugs to the student’s owned_* arrays. Used by assign_trainings_to_students(), assign_exams_to_students(), and VoucherController::apply_voucher().

4. Order Statuses

The App\Models\OrderStatus model is backed by the order_status table. Constants exposed:

Status table model

Status-driven business rules

  • PENDING_ID and PENDING_ONLINE_ID block the cart: items in those statuses cannot be re-added to a cart for the same student/company (HTTP 409 with pending_conflicts).
  • PAID_ONLINE_ID and PAID_OFFLINE_ID are 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
  • 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 of search_by_training, search_by_exam, search_by_student can be active at a time. When none is active, the default search matches against the order reference, payment_ref, or student.email.

Example Request

Success Response


6. Create Order

Creates a finalized ORDER directly (skipping the cart). Items are attached using their current final_price.

Request JSON

  • trainings is required when quizzes is empty (and vice-versa)
  • IDs must be distinct and 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

Updates order_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 the notes field.

Request JSON

Success Response

Forbidden Response (student / instructor)


10. Archive Order

Soft-archives an order. Only orders in PAID_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

OR
Provide exactly one of training_id or quiz_id. The endpoint blocks the request when:
  • the same item is already in another Pending / Pending Online order (HTTP 409, includes pending_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.)
Every message of the cart and checkout endpoints (add / remove / convert to order, coupon format, Konnect init) is translated from the Accept-Language header (en, fr, ar; any other value falls back to en). The examples on this page are the English ones; pending_conflicts and 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

  • type is 2 for cart, 1 for wishlist
  • trainings and quizzes are required (each min:1)

Success Response

Pending Conflict Response (HTTP 409)

Same shape as add_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 of training_id or quiz_id. company_id is 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 finalized ORDER 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 remaining max_usage, and meet applicable_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-creates PAID_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-creates PAID_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 whose training_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 (or order-{id}.pdf if 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 as order-{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:
  1. Init — POST /api/order/{user_id}/konnect/init flips the student’s CART to ORDER + PENDING_ONLINE, then calls Konnect’s /init. The response contains payUrl (open in browser to pay) and paymentRef (stored on the order).
  2. Pay — the student opens payUrl, completes payment on Konnect.
  3. Webhook — Konnect calls GET /api/konnect/webhook?payment_ref=.... The controller fetches the payment from Konnect, updates order_status_id, grants ownership on completed.
  4. Inspection — GET /api/konnect/payments/{paymentId} proxies to Konnect for raw status; GET /api/order/{id}/konnect-history returns 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_price to 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 table konnect_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, when true the notification is also emailed
  • notif_type_id — defaults to NotificationType::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 via payment_ref, fetches the live payment from Konnect, applies the corresponding status change, and grants ownership on completed.
Content negotiation:
  • pass ?json=1 (or Accept: application/json) to get JSON
  • pass ?view=1 or no Accept for HTML (renders konnect_payment_page_success / konnect_payment_page_pending / konnect_payment_page_failed)

Status Mapping

When the order is already paid, the endpoint returns idempotently with 200 and "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 a GET /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, order is null but the page still renders the live Konnect data and a minimal invoice shell.
  • When Konnect itself fails the request, konnect.ok is false and konnect.error describes the failure (e.g., "Payment not found on Konnect.").

Validation Error


30. Internal Reused Methods

These methods are part of OrdersController but do not have their own route:
  • findPendingConflicts(int $studentId, int $companyId, array $trainingIds, array $quizIds): array — returns trainings/quizzes already attached to a PENDING or PENDING_ONLINE order for the same student/company. Used by add_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 the pdf_order_template blade.
  • appendKonnectHistoryRaw(Order $order, string $rawLine): void — appends a non-timestamped line to konnect_history.webhook_history.
  • historyFromWebhook(Order $order, string $action, ?string $note = null): void — appends a timestamped [date] action; note line.
  • 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).
The model also exposes:
  • 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 to type = ORDER; carts and wishlists are excluded unless a type query 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() updated hook and in update()’s duplicate-removal path). The hook uses LoyaltyPointsSettings.key='points_per_tnd'; update() sums loyalty_points per training/quiz.
  • initiate_konnect_payment() requires a CART; it does not create one. Make sure add_to_cart (or update_cart) was called first.
  • konnect_webhook() is publicly reachable and resolves orders strictly by payment_ref. The legacy {id} path segment is preserved but ignored.
  • orderKonnectDetailsByRef() is public (declared outside the auth:sanctum group) and can be browsed without a token — anyone with the paymentRef can see the invoice-style page.
  • Konnect’s KONNECT_URL, KONNECT_INIT_URL, KONNECT_API_KEY, and KONNECT_WALLET_ID must 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.