COM-004 — Checkout with Wallet (end-to-end)
Validated against classs/CommerceOrdersModule.php::checkoutWithWallet() (:563) and the live schema on 2026-09-06.
This is the archetype flow and the answer to the Definition-of-Done example ("customer places an order and pays using wallet"). Every other paid flow in the platform should be documented in this same shape. Read ../../40-financial/central-finance-ledger.md alongside it.
Actors & preconditions
- Actor: logged-in customer (
customer_master,buyer_id > 0). - Gate: cart has ≥1 valid item; wallet has sufficient balance (wallet-first); COD only if
admin_global_settings.ecommerce.allow_cod = 1.
- Entry:
POST api/commerce/cart.php(checkout action) →CommerceOrdersModule::checkoutWithWallet($userId, $items, $shippingInput).
The flow (each hop cited)
Customer (mycart.php)
│ POST items[] + shipping (address, phone, email, delivery_mode, payment_method, coupon)
▼
api/commerce/cart.php ← passes $_POST['items'] through
▼
CommerceOrdersModule::checkoutWithWallet() :563
1. Auth/guard userId>0, items non-empty :565–570
2. Shipping validation address req; phone = /^[0-9]{10}$/; email valid :572–592
3. Delivery mode quick ⇒ requires drop_lat/drop_lng :582,593–595
4. Payment method default 'wallet'; 'cod' only if allow_cod=1 :597–609
5. Normalize items product_id/inventory_id + qty; drop invalid :611–637
6. Server-side price from seller_inventory | products_master | :646–748
product_variants — NEVER from client ⚠ guard
• publicOnly guard: unpublished/paused/suspended ⇒ refuse :667–683
• variant price from product_variants.price by id/uid ⇒ ₹1 exploit fix :692–748
7. Build order lines unit_price × qty ⇒ line_total; sum ⇒ subtotal :750–764
8. Routing (opt.) CommerceRoutingEngine candidates; quick filter :766–782
9. Create order INSERT orders_master (one row per line) (creation pass)
10. Status history INSERT order_status_history insertOrderStatusHistory() :179
11. Wallet debit WalletModule debit ⇒ wallet_transactions (wallet-first)
12. Finance ledger ensureOrderFinanceMaster + recordOrderFinanceEntry :399,462
+ refreshOrderFinanceAggregates :510
13. Commission CommissionEngineModule ⇒ commission_transactions (require_once :19)
14. Delivery-trust owner receiving link / OTP scaffolding :292–393
15. Notifications buyer + seller (see notifications section)
▼
Response { success, order_uid/order_number, ... }
Tables written (validated)
| Step | Table | What |
|---|---|---|
| 9 | orders_master | one row per line item — buyer/seller/product, pricing, GST, status=created, payment_method, payment_status |
| 10 | order_status_history | initial status row |
| 11 | wallet_transactions | debit against wallet_accounts (buyer) |
| 12 | kv_order_finance_master | money header keyed (ecommerce, orders_master, order_id) |
| 12 | kv_order_finance_entries | order_paid/customer/debit, platform-fee, merchant-credit lines |
| 13 | commission_transactions | recorded 5% platform take |
| 8/14 | commerce_order_routing, order_delivery_links | fulfilment + delivery-trust |
Money math (per line)
unit_price (from DB) × quantity = line_total; summed to subtotal; + gst_amount (+ cgst/sgst/igst) + shipping/delivery_charges + platform_fee − discount_amount ⇒ total_amount. Wallet debited total_amount; platform take (commission) recorded as a platform finance entry + commission_transactions; merchant net = gross − commission − fees, settled later.
Failure branches (validated messages)
| Cause | Where | Result |
|---|---|---|
| not logged in | :565 | Please login to place order |
| empty cart / no valid items | :568,:635 | Cart is empty / No valid items to checkout |
| bad address/phone/email | :584–592 | field-specific message |
| quick delivery w/o location | :593 | prompt to enable location or pick Regular |
| item no longer available | :672–678 | An item in your cart is no longer available… |
| price ≤ 0 | :685 | Invalid price for product ID <n> |
| quick unavailable for item | :780 | prompt to select Regular |
| wallet insufficient | WalletModule debit | debit fails ⇒ order not paid (verify exact message in Wallet doc) |
What could break if you change this (change-impact)
Wallet balance integrity, commission recorded exactly once, finance ledger keyed to ecommerce (not another module), the two money-integrity guards (server-side pricing, publicOnly). Re-run the cross-module isolation test (finance/commission must not attach to another module's order id).
Errors (feed the Error Center; prefix COM-E)
COM-E001price ≤ 0 after server resolution (:685) — root cause: product has no valid
selling_price/variant price; inspect products_master.selling_price, product_variants.price.
COM-E002item unavailable at checkout (:672) — root cause: product unpublished or seller
company suspended/de-verified after carting; inspect product activestatus/publish + company state.
COM-E003quick delivery unresolvable (:780) — no eligible/opted-in seller within radius;
inspect commerce_order_routing, seller quick_delivery_enabled.
Re-validate
php -r 'require "config/baseconfig/config.php"; global $conn;
print_r($conn->query("SHOW COLUMNS FROM orders_master")->fetchAll(PDO::FETCH_COLUMN));'
grep -n "function checkoutWithWallet" classs/CommerceOrdersModule.php