Order adalah entitas bisnis utama. Alurnya melibatkan creation, payment, shipping, dan interaction dengan wallet & stock. OrderController.php 2.987 baris dengan banyak inkonsistensi.
Ringkasan alur
CREATE:
POST /api/order/create
→ OrderController@createOrder (:1612)
→ DB::beginTransaction (no-op di MongoDB!)
→ set status awal (pending/process/delivery tergantung shipping)
→ set payment_status='unpaid'
→ TIDAK ada validasi stok
→ TIDAK ada decrement stok
PAYMENT (transisi ke 'paid'):
Webhook DOKU / manual admin / polling
→ $order->payment_status = 'paid'; $order->save() ← WAJIB save() agar observer trigger
→ OrderObserver@updated (:18)
→ IF isDirty('payment_status') AND old != 'paid' AND new == 'paid':
→ IF payment_method == 'instant_payment':
WalletService::processDeposit (kredit wallet)
→ decrementStock (semua order item)
→ DigitalProductService->sendProductLink
STATUS CHANGE:
Manual via API (mass update, TIDAK trigger observer)
Atau $order->save() (trigger observer, tapi observer hanya dengar payment_status)1. Order creation
Endpoint
| Route | Handler |
|---|---|
POST /api/order/create (routes/api.php:224) | OrderController@createOrder (OrderController.php:1612) |
GET /orders/create (routes/web.php:222) | OrderController@create (render form only) |
| Shortlink | ShortlinkController set payment_status='unpaid' (:307-316) |
| Engine Bot | EngineBotPaymentController:300 set payment_method='instant_payment' |
Field yang di-set saat create
Dari OrderController.php:1778-1805:
| Field | Default | Catatan |
|---|---|---|
id | UUID | Generated di Order::boot() (Order.php:21-23) |
order_number | — | Helper di :2245 |
sequence | — | Helper di :2283 — baca latest() +1 tanpa lock |
customer_id, customer_name, customer_wa | — | Dari model Customer |
payment_method | string request | instant_payment, cod, bank_transfer, epayment |
store_id | $userStore->store_id | Scope multi-tenant (:1787) |
total_price | (int) $request->total_price | — |
status | dinamis | Digital → 'process'; Ada shipping → 'process'; Lain → 'pending'; Ada resi → 'delivery' |
payment_status | 'unpaid' | Hardcoded (:1795) |
tracking_status | 'none' | Update ke 'ready' saat shipping di-setup |
rating | 0 | — |
addresses | object | Bentuk flat atau {label, value} — tidak seragam |
shipping | object | {name, code, service, description, etd, cost, resi, courier} |
utm_* | null | utm_campaing — typo yang terlanjur dipakai |
2. Status transisi
Status order
Enum formal ada di app/Enums/StatusOrder.php:5-12: pending, process, cancel, completed, complained, refund. Tapi enum ini tidak dipakai — seluruh transisi pakai string literal.
String tambahan di codebase yang tidak ada di enum:
'delivery'— saat create pakai resi (OrderController.php:1743), update viamanual_receipt_input(:3223)'complete'— typo/inconsistency dengan'completed'(:1235,2784,2939)'complete,complained'— compound status (:1237) — menyulitkan query
payment_status values
| Value | Sumber | Trigger |
|---|---|---|
'unpaid' | create order (:1795) | Default awal |
'confirmed' | customer upload bukti transfer (PaymentController.php:73,77) | Manual |
'pending' | hapus bukti transfer (PaymentController.php:172-173) | Anomali — balik ke pending padahal sebelumnya confirmed |
'paid' | webhook DOKU / manual admin / polling | Trigger observer |
Tidak ada state machine formal
Hanya if/else bertingkat. Tidak ada guard yang melarang transisi dari paid → cancel atau lompatan status arbitrari. change_order_status (:1192-1222) menerima $request->status apa pun dan langsung update.
Endpoint yang mengubah status
| Endpoint | Method | Trigger observer? |
|---|---|---|
POST /api/order/change_order_status (:1192) | mass update | TIDAK |
POST /api/order/change_order_status_multiple (:1224) | mass update per item | TIDAK |
POST /api/order/bulk_order_status (:951) | bulk update | TIDAK |
POST /api/order/change_payment_status (:1260) | mass update | TIDAK — bug: stock & wallet tidak update |
POST /api/order/change_payment_status_multiple (:1280) | $order->save() | YA — comment eksplisit di :1290 |
POST /api/order/update_shipping/{id} (:1482) | mass update | TIDAK |
POST /api/order/manual_receipt_input (:3160) | $order->save() per item | YA (tapi tidak ubah payment_status, jadi observer handler tidak jalan) |
POST /api/order/upload_resi (:2716) | bulk CSV | — |
POST /api/order/update_InformationOrder/{id} (:2088) | $order->save() | YA |
3. OrderObserver
app/Observers/OrderObserver.php. Hanya updated event yang di-handle (:18). Tidak ada handler creating, created, updating, deleting, deleted.
Trigger condition
if ($order->isDirty('payment_status')
&& $order->payment_status === 'paid'
&& $order->getOriginal('payment_status') !== 'paid') {
// jalan
}(OrderObserver.php:21-24)
Yang dilakukan observer
- Wallet deposit hanya jika
payment_method === 'instant_payment'(:32):- Panggil
WalletService::processDeposit(storeId, order.id, total_price, ...)(:34-39) - Non-blocking — error di-log, order tetap sukses (
:40-48)
- Panggil
- Decrement stok produk via
decrementStock()(:52) — untuk semua order item (variant & non-variant). Juga non-blocking (:98-105) - Kirim link produk digital via
DigitalProductService->sendProductLink($order)(:55)
Observer tidak mengubah status order. Pengetusan status='process' untuk digital order dilakukan terpisah di DokuCheckoutController.php:393 (sebelum save), bukan di observer.
Quirk penting
4. Payment integration
Payment method
| Method | Sumber | Wallet deposit? |
|---|---|---|
cod | BankController.php:436-449 | TIDAK |
instant_payment | DOKU VA/QRIS | YA — lewat observer |
bank_transfer | PaymentController.php:52 | TIDAK |
epayment | PaymentController.php:52 | TIDAK |
Pembayaran gagal/expired
- Field
doku_payment_expired_atdisimpan saat create checkout (DokuCheckoutController.php:287,294) - Klien polling
POST /api/payment/check-status/{orderId}(routes/api.php:813) untuk status real-time - Jika expired:
EngineBotPaymentController::checkStatus(:398-407) returnstatus='expired'ke Engine Bot, TIDAK mengubahpayment_statusdi DB. Order tetapunpaid. - Tidak ada cron auto-cancel order unpaid expired — order tetap
unpaidselamanya kecuali admin manual
5. Shipping
Provider
| Provider | Endpoint base | Fungsi |
|---|---|---|
| RajaOngkir Komerce | https://rajaongkir.komerce.id/api/v1/ | Calculate cost + track waybill (RajaongkirController.php:112,176) |
| RajaOngkir Pro | https://pro.rajaongkir.com/api/cost | Legacy (RajaongkirController.php:144) |
| API Kurir | https://live.apikurir.id | Vendor berbeda (ShippingController.php:18) |
Resi update
Tiga jalur:
- Saat create order dengan input
number_receipt→ fieldshipping.residi-set (:1715) +status='delivery'(:1743) POST /api/order/manual_receipt_input(:3160) — bulk JSON array,$order->save()per itemPOST /api/order/update_shipping/{id}(:1482) — update address + shipping + resi sekaligus
Tracking webhook
Tidak ada. Endpoint /api/rajaongkir/waybill adalah pull (klien minta tracking ke rajaongkir), bukan webhook. Tidak ada route POST dari rajaongkir/apikurir ke aplikasi.
6. Order & wallet interaction
Kapan wallet di-debit/kredit?
| Aksi | Trigger | Method |
|---|---|---|
| Kredit (deposit) | OrderObserver::updated saat paid + instant_payment | WalletService::processDeposit (OrderObserver.php:32-49) |
| Debit (withdraw) | Manual via WalletService::requestWithdrawal | Tidak terkait lifecycle order |
Refund
Komisi/fee
- Deposit order: fee = 0 (
WalletService.php:358, hardcoded). Fulltotal_pricemasuk wallet - Withdraw: fee dari config
doku.wallet.withdrawal_fee(default5000). Net = amount - fee. Ini fee withdraw, bukan fee per-order
7. Edge case & bug
Race condition
DB::beginTransaction()diOrderController.php:1773— MongoDB tidak support ACID transactions.DB::rollBack()di:1949tidak akan rollback data yang sudah tersimpan- Helper
sequence()(:2283) danorder_number()(:2245) bacalatest()lalu +1 tanpa lock → dua request bersamaan bisa generate nomor urut sama change_payment_statustanpa lock, bisa double-trigger (observer punya guard idempotency, tapiOrderPaymentbisa di-double-count)
Stok tidak bisa negatif, tapi bisa tidak sinkron
decrementStock pakai max(0, $oldStock - $quantity) (OrderObserver.php:133,179). Tapi:
- Jika stok awal 0 dan qty > 0, stok tetap 0 tanpa error — order tetap sukses
- Decrement non-blocking (
:98-105) — kalau gagal (mis. product tidak ditemukan), error di-log tapi order tetappaid. Stok bisa tidak sinkron dengan order paid tanpa sinyal error ke user
Order bisa cancel setelah paid
Tidak ada guard. change_order_status (:1192-1222) menerima $request->status apa pun:
- Order
paid+cancel→ stok sudah diturunkan (irreversible), wallet sudah di-deposit (tidak ada debit balik) - Order
paid+refund→ sama, wallet tidak dikembalikan - Statistik dashboard exclude
cancel/refund, tapi wallet & stok sudah tidak konsisten
Referensi file
| File | Baris | Peran |
|---|---|---|
app/Http/Controllers/OrderController.php | 2.987 | Create, update, status change |
app/Observers/OrderObserver.php | 57 | Trigger wallet + stock + digital link |
app/Models/Order.php | — | Model (no casts, no SoftDeletes) |
app/Enums/StatusOrder.php | 12 | Enum (tidak dipakai) |
app/Http/Controllers/DokuCheckoutController.php | — | Create checkout + handle webhook |
app/Http/Controllers/PaymentController.php | — | Upload bukti transfer |