Skip to content

Admin Order Management ​

Flow ID: AD-03 Module(s): eshop Complexity: Very High Last Updated: 2026-09-29 — 4.124.0 resync: citation drift corrections, is_paid semantics (#754), ip_address release note, Business Rule 13 (#837); previously corrected stale PlaceOrderService.php, Adv_orders_admin.php, Adv_order_model.php, and rest_routes.php citations drifted by the #760 invoice-identity guard and the #688/PR#260 edit()-cancel-reporting change; documented the new InvoiceIdentityIncompleteException guard as Business Rule 12

Business Context ​

Order management covers the full order lifecycle: viewing, status transitions, voucher generation for shipping providers, shipment closure, invoice generation, and order editing (which is actually clone + cancel). The status state machine governs stock adjustments, payment reconciliation, and loyalty point handling.

Entry Points ​

TypePath / TriggerControllerMethod
AdminOrder listingecommercen/eshop/controllers/Adv_orders_admin.phpindex()
AdminOrder detailsameview($id)
AdminEdit ordersameedit($id)
AdminBatch cancelsamesetBatchCanceled()
AdminCreate vouchersamesetPendingWithVoucher()
AdminClose shipmentssameclose_pending_jobs()

Order Status State Machine ​

StatusMeaningNext
PENDINGAwaiting payment/acceptance→ PENDING_ACCEPTED, CANCELED
PENDING_ACCEPTEDAccepted, payment received (COD)→ PENDING_ACCEPTED_VOUCHER, CANCELED
PENDING_ACCEPTED_VOUCHERVoucher created, awaiting shipment→ SENT / PAID_SENT, CANCELED
PAIDPayment received (prepaid)→ SENT / PAID_SENT
SENTCOD orders shipped→ INVOICED
PAID_SENTPrepaid orders shipped→ INVOICED
INVOICEDCompleted(terminal)
CANCELEDCanceled(terminal)
RETURNReturned(terminal)

SENT vs PAID_SENT determined by payment method: COD (delivery/byphone) → SENT; prepaid (card, paypal, etc.) → PAID_SENT.

Stock Management ​

TransitionStock EffectignoreStock
Order creation (PENDING)Deducted (-qty)false
Any → CANCELEDReturned (+qty)false
PENDING_ACCEPTED_VOUCHER → SENT/PAID_SENTNo changetrue
Edit (clone + cancel)Cancel returns stock, new order deducts—

Stock is adjusted via returnOrderStock() (+qty per basket item to product_codes.stock) and removeOrderStock() (-qty).

Voucher Lifecycle ​

  1. Create: setPendingWithVoucher() → calls transporter API (ACS, Geniki, DHL, etc.) → saves gtcode + gtjobcode → status = PENDING_ACCEPTED_VOUCHER
  2. Close: close_pending_jobs() → calls transporter pickup/closure API → status = SENT or PAID_SENT (stock NOT adjusted)
  3. Cancel: cancelVoucher() → calls transporter cancel API → clears gtcode/gtjobcode → reverts to PENDING_ACCEPTED or PAID

Supported transporters: ACS, Geniki (v1/v2), DHL, ELTA, Speedex, Center, EasyMail, FIS, BoxNow, DailyCourier, Taxydema (v1/v2), Skroutz, ASAP.

Order Editing ​

Adv_orders_admin.php:1079-1256 is actually clone + cancel:

  1. Cancel original order (returns stock, rolls back points/coupons)
  2. Create new order with modified items/prices/addresses
  3. Clone order notes, send new confirmation email
  4. Configurable: keepProductPriceForEdit — retain old or use current prices

Cancellation Restrictions ​

Cannot cancel if:

  • Already CANCELED
  • Payway is bank card provider (eurobank, alpha, ethniki, piraeus, apcopay) — refund not automated
  • PayByBank PAID orders — payment already settled
  • Status = PENDING with non-PayByBank card payment

Business Rules ​

  1. Stock deducted at creation, returned at cancel -- Never double-adjusted during voucher lifecycle. Stock adjustments happen via returnOrderStock() (+qty) and removeOrderStock() (-qty) on product_codes.stock. (ecommercen/eshop/controllers/Adv_orders_admin.php)
  2. Loyalty points rolled back on cancel -- Both spent points (returned to customer) and earned points (removed) are reversed. (ecommercen/eshop/controllers/Adv_orders_admin.php, cancel flow)
  3. Edit = clone + cancel -- Original order canceled, new order created. Configurable via keepProductPriceForEdit whether to retain old or use current prices. (ecommercen/eshop/controllers/Adv_orders_admin.php, edit() (:1079-1256, cancel call at :1187))
  4. Voucher status blocks cancel -- Must cancel voucher before canceling order. Voucher cancellation calls transporter API and clears gtcode/gtjobcode. (ecommercen/eshop/controllers/Adv_orders_admin.php, cancelVoucher())
  5. Invoice uses order barcode -- Generated via BarcodeGeneratorPNG. (ecommercen/eshop/controllers/Adv_orders_admin.php)
  6. Batch cancel validates each order -- Per-order validation before canceling, respects payment method and status restrictions. (ecommercen/eshop/controllers/Adv_orders_admin.php, setBatchCanceled())
  7. Payment method determines SENT vs PAID_SENT -- COD/byphone orders transition to SENT; prepaid (card, paypal, etc.) transition to PAID_SENT. (ecommercen/eshop/controllers/Adv_orders_admin.php, close_pending_jobs())
  8. Cancel blocked for card payments -- Cannot cancel orders with bank card payment methods (eurobank, alpha, ethniki, piraeus, apcopay) since refunds are not automated. (ecommercen/eshop/controllers/Adv_orders_admin.php, cancellation restrictions)
  9. Order status emails on transition -- Status changes trigger automated email and SMS notifications via the SY-24 Email Dispatch system. The email templates used (order_created, order_update, order_on_store, notify_admin) are catalogued in AD-53 Email Template Viewer.
  10. Coupon usage rolled back on cancel -- If order used a coupon, the coupon usage count is decremented. (ecommercen/eshop/controllers/Adv_orders_admin.php, cancel flow)
  11. Gift stock rolled back on cancel -- Gift product stock is returned when an order containing gift items is canceled. See AD-09 Gift Rules.
  12. REST-placed INVOICE orders require complete tax identity (#760) -- PlaceOrderService::placeOrder() refuses an order with wantsInvoice === true when any of afm/doy/profession/company/companyAddress is missing or whitespace-only, via the missingInvoiceIdentityFields() helper (src/Domains/Checkout/PlaceOrderService.php:1026-1045), throwing InvoiceIdentityIncompleteException (error code invoice_identity_incomplete) at the guard (:160-187). This mirrors the legacy storefront checkout's trim|required validation on the same five fields when the choice is invoice (ecommercen/eshop/controllers/Adv_order.php:557-563).
  13. REST place-order saves a signed-in customer's details (#837) -- for a non-guest customer, PlaceOrderService::placeOrder() writes the checkout details onto the shop_customer row via CheckoutCustomerDetailsWriter once the order row exists and before the payment step (src/Domains/Checkout/PlaceOrderService.php:571-581). Guests are skipped; the writer logs and swallows its own failure. The legacy storefront already did this through update_customer() (Adv_order::previewOrder(), per the in-code comment at PlaceOrderService.php:571-573).

Order Lifecycle Hooks ​

The order system fires HTTP webhook notifications on key lifecycle transitions using OrderForErpHookFireTrait and OrderCancelHookFireTrait (Guzzle HTTP with Bearer token authentication).

Configuration: application/config/api.php under internalApi.apiOrderWebHooks:

  • erpReady -- URL for order-ready webhook
  • cancel -- URL for order-cancel webhook
  • return -- URL for order-return webhook
Hook MethodTrigger PointHTTP CallDescription
afterOrderSuccessHooksOrder successinternalApiOrderForErpHook($orderId)HTTP GET to configured ERP webhook URL -- notifies ERP that order is ready
afterOrderCancelHooksOrder cancellation (controller)internalApiOrderCancelHook($orderId)HTTP GET to cancel webhook URL -- notifies ERP of cancellation
afterCancelOrderHooksOrder cancellation (model-level)internalApiOrderCancelHook($orderId)Same cancel notification fired from model layer
(orphaned)Never firedinternalApiOrderReturnHook($orderId)Exists in code but is not called from any trigger point -- dead code

Note: internalApiOrderReturnHook is implemented but orphaned -- no code path currently invokes it. If return webhook notifications are needed, a trigger must be wired into the return status transition.

Data Model ​

shop_order Table (Primary) ​

ColumnTypeDescription
idINT(11) PK AIInternal order ID
cancel_idINT(11)ID of canceled predecessor (for edit/clone). Default 0.
store_idINT(11) NULLPickup store ID (NULL for delivery orders)
transport_idINT(11) NULLTransporter FK (NULL for store pickup)
order_serialVARCHAR(255)Human-readable serial number (generated via createSerial())
webignoreINT(1)0 = online order, 1 = phone/admin order
total_qtyINT(11)Total item quantity in basket
gen_tax_serviceVARCHAR(2)Geniki Taxydromiki's cash-on-delivery service designation — ΑΜ/ΑΝ, Geniki's own codes (default ΑΜ per database/initial/initial.sql:1312); read by AdvSetPendingWithVoucher::createVoucherGeniki()/createVoucherGenikiV2() (:145,:207) and the admin writer, never by createVoucherAcs()
order_currencyVARCHAR(3)ISO currency code
currency_rateDECIMAL(11,4)Exchange rate at order time
currency_idINT(11)Currency FK
pricing_*VARCHARBilling address fields: name, surname, address, city, region, postal, county, country, phone, mobile
shipping_*VARCHARShipping address fields: same set as billing
afmVARCHAR(30)Tax ID (VAT number) for invoices. On receipt orders (paymerch !== 'invoice') this is written as the empty string instead of the customer's submitted value, matching Adv_order_model::setUpAdminOrderDataReceiptInvoice()'s receipt arm — see docs/decisions/760-paymerch-invoice-flag.md (D7). Same on doy/company/profession/company_address below
doyVARCHAR(100)Tax office for invoices (empty string on receipt orders — see afm above)
professionVARCHAR(255)Profession (invoice) (empty string on receipt orders — see afm above)
companyVARCHAR(255)Company name (invoice) (empty string on receipt orders — see afm above)
company_addressVARCHAR(255)Company address (invoice) (empty string on receipt orders — see afm above)
paywayVARCHAR(255)Payment method slug (e.g., delivery, paypal, stripe, eurobank, paybybank)
viva_paywayVARCHAR(255)Viva Wallet sub-payway code
paymerchVARCHAR(25)receipt or invoice
customer_idINT(11)Customer FK
customer_commentsTEXTCustomer notes
admin_commentsTEXTAdmin notes
courier_commentsTEXTCourier delivery notes
totalDECIMAL(11,2)NET items-only accounting total — excludes shipping, coupon, points and gift packaging. The one NET column in the money contract; never a display subtotal
total_vatDECIMAL(11,2)GROSS grand total (VAT + transport + delivery − reward + gift packaging) — the amount actually charged. Despite the name, this is "total with VAT included", not "the VAT amount"
transportation_costDECIMAL(11,2)Shipping cost
delivery_costDECIMAL(11,2)Cash-on-delivery surcharge, charged only for payway === 'delivery' and waived once the cart reaches the transporter's DELIVERY_COST_MIN_FREE. Written by both the legacy checkout and, since #568, PlaceOrderService (value resolved at src/Domains/Checkout/PlaceOrderService.php:342, written at :481) — REST-placed orders previously always left this column at its default. Added to total_vat only, never to the NET total column above
statusVARCHAR(30)Order status (see state machine above)
gtcodeVARCHAR(30)Transporter voucher code
gtjobcodeVARCHAR(255)Transporter job/tracking code
gtstatusVARCHAR(255)Voucher status from transporter API
gtflagTINYINT(1)Voucher processing flag
gtdatetimeDATETIMEVoucher creation timestamp
gtcountINT(3)Voucher request attempt counter
notesMEDIUMTEXTLegacy order notes
charge_toTINYINT(1)Charge recipient flag
entry_dateVARCHAR(30)UNIX timestamp string of order creation
entry_datetimeDATETIMEOrder creation datetime
canceled_dateDATETIME NULLCancellation timestamp
ip_addressVARCHAR(45)Client IP at order time. Written by the storefront checkout only (Adv_order_model::create_order()), resolved through MY_Input::ip_address() (CloudFlare CF-Connecting-IP, then X-Forwarded-For, then REMOTE_ADDR). NULL for admin, public, Shopflix and Skroutz orders, which go through create_order_admin() and are covered by the admin audit log instead, and NULL for REST-placed orders (PlaceOrderService). Was never written at all before 4.124 (write at ecommercen/eshop/models/Adv_order_model.php:448-453; only create_order() writes it, PlaceOrderService does not)
user_agentTEXTBrowser user agent
admin_order_checkINT(1)1 = admin has viewed this order
is_email_sentTINYINT(1)Status email sent flag
is_paidTINYINT(1)"Someone has checked this order" flag (0/1), not a reliable paid indicator. Set by the legacy gateway handlers via set_is_paid() (ecommercen/eshop/models/Adv_order_model.php:1916-1918), by the admin action (ecommercen/eshop/controllers/Adv_orders_admin.php:766) and by a cron (per the PaymentConfirmationService comment). REST PaymentConfirmationService::confirmPayment() deliberately does not write it (#754, product-owner ruling, src/Domains/Checkout/PaymentConfirmationService.php:58-81), so REST-confirmed orders stay is_paid = 0 and drop out of admin filter[isPaid] and the isPaid sort
paidTINYINT(1)Admin-toggled paid flag (differs from is_paid)
is_visibleTINYINT(1)Customer-facing visibility
invoised_date_timeDATETIME NULLInvoice generation timestamp
printed_date_timeDATETIME NULLPrint timestamp
sent_date_timeDATETIME NULLShipment closure timestamp
sms_statusTINYINT(1) NULLnull=no SMS, 1=requested, 2=sent success, 3=sent fail, 4=pending callback
coupon_idINT(11) NULLApplied coupon FK
coupon_valueDECIMAL(11,2)Coupon discount amount
points_spendINT(11)Loyalty points redeemed
points_rewardDECIMAL(11,2)Cash value of points reward
points_addedTINYINT(1)0=product points not added to customer, 1=added
skroutz_refererTINYINT(1) NULLReferrer tracking code
delivery_warningTINYINT(1)Delivery delay warning flag
delivery_warning_msg_statusTINYINT(1)0=not sent, 1=sent, 2=admin opted out
meta_dataVARCHAR(255)Serialized extra data
remindTINYINT(1)Reminder flag
remind_atDATETIME NULLReminder scheduled time
pbb_payment_codeVARCHAR(255)PayByBank payment reference
tran_ticketVARCHAR(32)Viva Wallet transaction ticket
providerVARCHAR(50)Marketplace provider tag (skroutz_smart_cart, shopflix_marketplace, etc.)
invoice_pdfVARCHAR(255)Uploaded invoice PDF filename
gift_packagingTINYINT(1)Gift wrapping enabled
gift_packaging_messageTEXTGift wrapping message
gift_packaging_costDECIMAL(11,2)Gift wrapping cost
json_invoiceLONGTEXTJSON invoice data (e-invoicing)

Indexes (27 non-PRIMARY KEY definitions for query optimization):

  • order_serial -- order lookup (not unique; database/initial/initial.sql:1396)
  • status, entry_datetime, status_entry_datetime -- listing and filtering
  • customer_id, customer_id_status_is_visible -- customer order history
  • cancel_id -- edit chain lookup
  • gtcode_gtflag_gtcount -- voucher processing queries
  • paid, sms_status, coupon_id, skroutz_referer -- filtering
  • store, store_payway_customer, store_customer -- store-based queries
  • transport_id -- transporter filtering
  • delivery_warning_msg -- composite for delivery delay job
  • remind_remind_at -- reminder job
  • points_added -- loyalty points job
  • status_invoised_date_time -- invoice reporting

shop_order_basket Table ​

ColumnTypeDescription
idINT(11) PK AIBasket item ID
order_idINT(11)FK to shop_order.id
product_code_idINT(11)FK to product_codes.id (SKU)
product_vatDECIMAL(11,2)Effective VAT percentage at time of order — the adjusted rate from VatForOrder::vat() (legacy vat_rate_captured), not the raw catalogue vat.value
priceDECIMAL(11,2)Unit price (VAT-inclusive)
item_pointsINT(11) NULLLoyalty points per unit
qtyINT(11)Quantity ordered
subtotalDECIMAL(11,2)Line total, VAT-inclusive (price * qty)
discount_stringVARCHAR(10)Discount label (e.g., 10%)
discount_priceDECIMAL(11,2)Discount amount taken off the unit (original_price - price), VAT-inclusive — legacy save_price, not the discounted price itself
original_priceDECIMAL(11,2)Pre-discount unit price (VAT-inclusive)
gift_idINT(11) NULLGift rule FK (non-NULL = gift item)
track_typeTINYINT(1)0=normal, smart/AI recommendation types
added_atINT(11) NULLUNIX timestamp when added to cart
track_idVARCHAR(255) NULLRecommendation tracking ID
optionsTEXT NULLSerialized product options (bundles, variations)

Supporting Tables ​

TablePurpose
shop_order_dhl_vouchersDHL-specific voucher data: dispatch confirmation, tracking number, product code
shop_order_smart_pointSmart point/locker delivery data: transporter_id, shop_id, json_data
shop_order_tagsOrder tag definitions (id, tag name, active flag)
shop_order_tags_lpOrder-to-tag linking table (order_id, tag_id)
shop_order_basket_options_applied_bundlesBundle application records for basket items

Complete Batch Operations Reference ​

The batch actions system in doBatchActions() dispatches through three method chains: doBatchActionsGeniki() for per-transporter operations, doBatchActionsSystem() for system operations, and doBatchActionsClient() for client-repo overrides.

Per-Transporter Batch Actions (dynamic, one per active transporter) ​

Action KeyMethodDescription
set_pending_with_voucher_{transporterId}setPendingWithVoucher()Create vouchers via transporter API for selected orders
cancel_jobs_{transporterId}cancelVoucher()Cancel vouchers via transporter API for selected orders
print_vouchers_pdf_{transporterId}printVouchersPdf()Print voucher PDFs for selected orders

System Batch Actions ​

Action KeyMethodDescription
set_status_canceledsetBatchCanceled()Cancel selected orders with per-order validation, stock return, points rollback
set_paidsetPaid()Mark selected orders as paid
set_unpaidunsetPaid()Unmark selected orders as paid
print_selectedprintSelected()Print invoices for selected orders (HTML or PDF via DomPDF)
print_selected_xlsexportXls()Export selected orders to Excel (XLSX via PhpSpreadsheet)
print_selected_products_xlsexportProductsXls()Export product breakdown from selected orders to Excel
addPointsaddPointsSelected()Add earned loyalty points to customers for selected orders
removePointsremovePointsSelected()Remove earned loyalty points from customers for selected orders
markInformDelayOrdermarkInformDelayOrderSelected()Mark delivery delay notification sent
unMarkInformDelayOrderunMarkInformDelayOrderSelected()Clear delivery delay notification flag
set_order_tagsbatch_action()Assign tags to selected orders (redirect to tag selection UI)
unset_order_tagsbatch_action()Remove tags from selected orders (redirect to tag selection UI)

Client Extension Point ​

doBatchActionsClient() is an empty hook method that client repos can override to add custom batch actions (e.g., ERP sync, custom exports).

extendJsonState() (ecommercen/eshop/controllers/Adv_orders_admin.php:3314) is a template-method seam allowing client repos to inject Vue jsonState keys into order forms without copying the entire action. Invoked before jsonEncodeForVue() on three view actions: add() (:605), edit() (:1234), and repeat() (:3160). The $action parameter is a string identifying the caller ('add', 'edit', or 'repeat').

Close Shipments (Transporter Integration) ​

The close_pending_jobs($transporterId) method implements the shipment closure flow for 15 supported transporter providers:

Provider ClassClosure MethodAPI Integration
ACSclosePendingJobsAcs()ACS issuePickupList() API -- remote pickup request
GT (Geniki v1)closePendingJobsGeniki()Geniki closeOpenJobs() API -- remote closure
GTV2 (Geniki v2)closePendingJobsGenikiV2()GenikiV2 closeOpenJobs() API -- remote closure
DHLclosePendingJobsDhl()DHL API -- remote closure with dispatch confirmation
ELTAclosePendingJobsLocally()Local status update only (no remote API)
SPEEDEXclosePendingJobsLocally()Local status update only
CENTERclosePendingJobsLocally()Local status update only
EASYMAILclosePendingJobsLocally()Local status update only
FISclosePendingJobsLocally()Local status update only
BOXNOWclosePendingJobsLocally()Local status update only
DAILYCOURIERclosePendingJobsLocally()Local status update only
TAXYDEMAclosePendingJobsLocally()Local status update only
SKROUTZclosePendingJobsLocally()Local status update only
ASAPclosePendingJobsLocally()Local status update only
TAXYDEMAV2closePendingJobsLocally()Local status update only

Closure flow:

  1. Fetch all orders with status PENDING_ACCEPTED_VOUCHER for the transporter
  2. Call transporter API (for ACS/Geniki/DHL) or update locally
  3. For each successfully closed order: set status to SENT or PAID_SENT via orderGetSentStatusForPayWayVoucher(), a thin wrapper over isOrderPaidAtDeliveryByPayWay()'s landing-status classification (see AD-34 Voucher Generation for the full semantics) -- SENT for the three payways that land at PENDING_ACCEPTED (delivery, bank_transfer, paid_at_store), PAID_SENT for every other payway. stripe, xpay, and ethniki_nbgpay were previously misclassified as SENT until Advisable-com/ecommercen#530; the same helper drives orderGetRevertedStatusForPayWayVoucher() on voucher cancellation (AD-34's Voucher Cancellation section), which now likewise reverts those three to PAID instead of PENDING_ACCEPTED
  4. Send shipment email via adv_mailer->order_has_been_updated()
  5. Set is_email_sent = true

Invoice Generation ​

The invoice($orderSerial) method generates printable invoices:

  • Loads full order data via getRecordForInvoice()
  • Generates barcode image using BarcodeGeneratorPNG (Code 128 format)
  • Optionally includes Geniki voucher barcode if GT provider is configured
  • Supports batch printing via printSelected() which can output HTML or PDF (DomPDF, A4 format)

Export Capabilities ​

ExportMethodFormatContent
Order XLSexportXls()XLSX (PhpSpreadsheet)Customer ID, address, phone, postal, weight, COD amount, courier notes
Products XLSexportProductsXls()XLSXProduct ID, name, quantity, order count
Orders CSVexportOrders()CSV (semicolon, UTF-8 BOM)28 columns: order ID/serial, billing/shipping addresses, payway, totals, status
Searched Orders CSVexportSearchedOrders()CSV (semicolon, UTF-8 BOM)Product-level: product code, shelf code, barcodes, VAT, price, qty, vendor, order serial

Order Repeat (Without Cancellation) ​

The repeat($orderId) method creates a new order from an existing order without canceling the original:

  • Pre-fills form with original order's customer/address/products data
  • Creates new order with fresh entry_datetime but preserves entry_date and skroutz_referer
  • Supports SmartPoint (locker) delivery selection
  • Fires ERP hooks and sends confirmation email/SMS for the new order
  • Does NOT cancel the original order (unlike edit() which does clone+cancel)

Admin Order Creation (POS / Phone) ​

Manual order creation via Adv_orders_admin::add() — including the fake cart mechanism, 5 AJAX product-search endpoints, POS-specific field defaults (webignore=1, status=PENDING_ACCEPTED, order_currency=EUR, unconditional stock decrement), and customer 3-tier resolution — is documented in AD-54 POS / Phone Orders.

SMS Provider Integration ​

Status notification SMS is sent via multiple provider integrations (sendSmsForOrder() helper):

ProviderSMS Status Mapping
plivo2 (sent) or 3 (fail)
routeeViberViber first, fallback to SMS; status from provider
routee2 (delivered), 4 (pending), 3 (fail)
bulker4 (pending) or 3 (fail)
yuboto_omni4 (pending) or 3 (fail)
omni_messaging4 (pending) or 3 (fail)
yubotoDirect status from provider

ERP Integration (Farmakon) ​

The postErpOrdersJob() method queues a FarmakonPostOrders job for ERP synchronization. This is available when FARMAKON.IS_ENABLED registry flag is set. The job processes orders with status PENDING_ACCEPTED or PAID via getOrdersForFarmakon().

REST API Endpoints ​

All REST endpoints require JWT authentication. Routes support an optional (\w{2})/ language prefix (e.g., /el/rest/...). POST is used for both create and update operations (not PUT).

Order (8 operations) ​

MethodPathAuthDescription
GET/rest/order/orderJWTList orders (paginated, filterable)
GET/rest/order/order/itemJWTGet order field metadata (schema introspection)
GET/rest/order/order/{id}JWTGet single order by ID
POST/rest/order/orderJWTCreate new order
POST/rest/order/order/{id}JWTUpdate existing order
DELETE/rest/order/order/{id}JWTDelete order
POST/rest/order/order/{id}/cancelJWTCancel order (triggers stock return, points rollback)
GET/rest/order/order/{id}/trackingJWTGet order tracking information

Note (6 operations) ​

Notes are polymorphic -- the entity_type field supports customer, product, and order scoping. Stored in shop_notes table.

MethodPathAuthDescription
GET/rest/order/noteJWTList notes (paginated, filterable by entity_type)
GET/rest/order/note/itemJWTGet note field metadata
GET/rest/order/note/{id}JWTGet single note by ID
POST/rest/order/noteJWTCreate new note
POST/rest/order/note/{id}JWTUpdate existing note
DELETE/rest/order/note/{id}JWTDelete note

Vat (6 operations) ​

MethodPathAuthDescription
GET/rest/order/vatJWTList VAT rates (paginated, filterable)
GET/rest/order/vat/itemJWTGet VAT rate field metadata
GET/rest/order/vat/{id}JWTGet single VAT rate by ID
POST/rest/order/vatJWTCreate new VAT rate
POST/rest/order/vat/{id}JWTUpdate existing VAT rate
DELETE/rest/order/vat/{id}JWTDelete VAT rate

For the canonical shop_product_vats schema, delete-guard gap in the modern REST layer, validator gaps, and all related security issues, see AD-50 VAT Management.

Basket (3 operations -- read-only) ​

Write routes exist in code but are commented out in route config. Basket items are managed through the order lifecycle, not directly via REST.

MethodPathAuthDescription
GET/rest/order/basketJWTList order basket items (paginated, filterable)
GET/rest/order/basket/itemJWTGet basket item field metadata
GET/rest/order/basket/{id}JWTGet single basket item by ID

DhlVoucher (3 operations -- read-only) ​

Write routes exist in code but are commented out in route config. DHL vouchers are created through the transporter integration flow, not directly via REST.

MethodPathAuthDescription
GET/rest/order/dhl-voucherJWTList DHL vouchers (paginated, filterable)
GET/rest/order/dhl-voucher/itemJWTGet DHL voucher field metadata
GET/rest/order/dhl-voucher/{id}JWTGet single DHL voucher by ID

SmartPoint (3 operations -- read-only) ​

Write routes exist in code but are commented out in route config. Smart point/locker delivery records are managed through the order creation flow, not directly via REST.

MethodPathAuthDescription
GET/rest/order/smart-pointJWTList smart point delivery records (paginated, filterable)
GET/rest/order/smart-point/itemJWTGet smart point field metadata
GET/rest/order/smart-point/{id}JWTGet single smart point record by ID

Route config: application/config/rest_routes.php -- read routes at lines 745-863 (Note write routes are interleaved at :825-831); Order write routes at :1414-1424, Vat write routes at :1434-1440. Controllers: src/Rest/Order/Controllers/ -- Order, Note, Vat, Basket, DhlVoucher, SmartPoint.

Known Issues & Security Gaps ​

  1. REST checkout truncates gen_tax_service and corrupts courier codes — Write fixed (#760). src/Domains/Checkout/PlaceOrderService.php:536 now writes 'paymerch' => $data->wantsInvoice ? 'invoice' : 'receipt' and omits gen_tax_service from the INSERT payload entirely (:471-545), so MySQL applies the column's own default, varchar(2) NOT NULL DEFAULT 'ΑΜ' (database/initial/initial.sql:1312), instead of a truncated in/re. The column is Geniki Taxydromiki's cash-on-delivery service designation — not ACS's, as an earlier version of this entry claimed — read only inside AdvSetPendingWithVoucher::createVoucherGeniki() (ecommercen/libraries/vouchers/AdvSetPendingWithVoucher.php:106-166, the read at :145) and createVoucherGenikiV2() (:168-229, the read at :207); createVoucherAcs() (:231-330) never reads it. The admin editor's dropdown (application/views/admin/orders/update.php:197-201) offers ΑΜ/ΑΝ — Geniki's own two service codes — and now matches the stored value again, and paymerch carries the customer's actual choice instead of NULL. Data repair still open. The 30 rows already written before this fix keep their truncated gen_tax_service (in/re) and a NULL paymerch — #760 ships no patcher (docs/decisions/760-paymerch-invoice-flag.md, decision D4): no client currently uses the REST checkout, so the corrupted rows have no live consumer today, even though the repair itself was found safe (D4 retains the analysis for reuse). Revisit before any client is ever pointed at the REST checkout, not after — that is the point at which the corrupted rows would gain a live reader.
  2. Order serial collision vulnerability — Adv_order_model::createSerial() (ecommercen/eshop/models/Adv_order_model.php:924-953) resolves collisions with a count_all_results() check-then-retry (:942-949), but shop_order.order_serial carries only a plain non-UNIQUE KEY (database/initial/initial.sql:1396). Two concurrent checkouts can both pass the count and commit the same serial.

Tests ​

Coverage gaps noted. No comprehensive test suite specifically exercises the order lifecycle state machine, batch operations, or concurrent order creation. Unit tests for Adv_orders_admin and Adv_order_model::createSerial() are absent or incomplete.

Customer Flows ​

Admin Flows ​

Integration Flows ​

System Flows ​

Wiki Guides: Job Manager Guide | DHL Guide | Stripe Guide