Checkout Flow
The most critical path on the platform. Every step from "Beli sekarang" to "READY_TO_SHIP" with all parties, integrations, and failure modes.
The full sequence
The 7 phases
Phase 1, Discovery to listing detail
Buyer: Browses, taps a listing.
System: Fetches listing from listings table. Increments view count. Logs to analytics.
Files:
src/app/pages/ListingDetail.tsxinterface/http/routes-listings.tsx
Phase 2, Shipping rate quote
Buyer: Selects address, taps courier option. System: Calls Biteship for live rates. Adds IDR 2,000 markup per option. Returns to client.
Why server-side: Prevents client-side fee tampering. Biteship's quote always passes through SEKEN's NestJS backend.
Status: ✅ Rate quote live · ✅ HMAC signing of the quote token shipped (PS-975, 2026-06-13) — verified in POST /cart/checkout-order (INVALID_SHIPPING_QUOTE on tamper)
Files:
interface/http/routes-shipping.tsxinfrastructure/adapters/BiteshipAdapter.ts
Phase 3, Order creation
Buyer: Confirms order, selects payment method. System:
- Creates
ordersrow with stateAWAITING_PAYMENT - Calls Xendit
/v2/invoiceswith order metadata + idempotency key - Stores invoice ID on order
- Returns Xendit invoice URL to client
Idempotency: Order creation uses IdempotencyGuard, duplicate requests with same key return existing order, never create twice.
Files:
interface/http/routes-orders.tsxdomain/order/CreateOrder.tsinfrastructure/adapters/XenditAdapter.ts
Phase 4, Payment
Buyer: Completes payment in Xendit-hosted page. System: Waits for webhook.
Payment methods supported:
- QRIS (most common, fastest)
- BCA Virtual Account
- Bank transfer (multiple banks)
Failure mode: 12-hour timeout on AWAITING_PAYMENT → auto-cancel.
Phase 5, Webhook processing
Xendit: Sends webhook on payment confirmation. System:
- Verifies HMAC signature (timing-safe compare)
- Verifies idempotency key not already processed
- Compares paid amount to expected total
- Match: Updates order to
READY_TO_SHIP, notifies seller - Mismatch: Updates to
PAYMENT_REVIEW, alerts ops
Critical guard: Amount mismatch never auto-progresses. Always pauses for ops review.
Files:
interface/http/routes-webhooks.tsxdomain/order/ConfirmPayment.tsinfrastructure/reliability/IdempotencyGuard.ts
Phase 6, Shipping
Seller: Generates Biteship label in seller dashboard.
System: Calls Biteship /v1/orders with order details. Stores tracking number on order. Updates state to SHIPPED.
Failure mode: 2-day timeout on READY_TO_SHIP → LATE_TO_SHIP. Ops can force-cancel.
Files:
interface/http/routes-shipments.tsxdomain/shipping/CreateShipment.ts
Phase 7, Delivery and confirmation
Biteship: Sends webhook on delivery.
System: Updates state to DELIVERED. Starts two timers:
- 24-hour return window (buyer can request return)
- 48-hour auto-confirm (escrow auto-releases)
Three outcomes:
- Buyer confirms manually →
COMPLETED - 48 hours pass → auto-
COMPLETED - Buyer opens return or dispute → branches to returns/disputes flow
On COMPLETED: Trigger fin_disbursement row creation. Finance admin processes payout.
Money flow during checkout
For a IDR 120,000 item:
Buyer pays IDR 152,000 total at checkout
├── 120,000 → escrow (held until completion)
├── 22,000 → forward shipping (Biteship 20k + 2k markup)
├── 6,500 → Buyer Protection Fee (SEKEN revenue)
├── 1,000 → asuransi (Biteship insurance)
└── 2,500 → biaya pembayaran (Xendit 1.5k + 1k markup)
SEKEN earns ~IDR 10,065 per transaction:
├── 6,500 (BPF)
├── 2,000 (shipping markup)
└── 2,500 (payment markup, before Xendit fee)
See Money Flow for full breakdown and sankey diagram.
State transitions on the order
Full state machine including dispute and return branches: see Order States.
Failure modes and recovery
| Failure | What happens | Recovery |
|---|---|---|
| Buyer abandons checkout | Order stays AWAITING_PAYMENT | Auto-cancels after 12 hours |
| Xendit webhook delayed | Order stuck pending | OutboxProcessor retries every 15 min |
| Biteship API down | Rate quote fails | CircuitBreaker returns fallback message, retry after 30s |
| Payment amount mismatch | Order to PAYMENT_REVIEW | Ops investigates, manually approves or rejects |
| Seller doesn't ship | After 2 days → LATE_TO_SHIP | Ops can force-cancel and refund |
| Webhook duplicate | IdempotencyGuard blocks reprocessing | Original outcome preserved |
Reliability patterns in action
Three patterns that make the checkout robust:
| Pattern | Where it fires | What it prevents |
|---|---|---|
| IdempotencyGuard | Order creation, Xendit webhook, Biteship webhook | Duplicate orders, double payments |
| OutboxProcessor | Notifications, fin_disbursement creation | Lost side effects when DB commit succeeds but downstream fails |
| CircuitBreaker | All external API calls | Cascading failures when partner is down |
Where to look in the codebase
| What you want | Where it lives |
|---|---|
| Order creation | domain/order/CreateOrder.ts |
| Payment confirmation | domain/order/ConfirmPayment.ts |
| Xendit adapter | infrastructure/adapters/XenditAdapter.ts |
| Biteship adapter | infrastructure/adapters/BiteshipAdapter.ts |
| Webhook routes | interface/http/routes-webhooks.tsx |
| Order routes | interface/http/routes-orders.tsx |
| Shipment routes | interface/http/routes-shipments.tsx |
| Reliability layer | infrastructure/reliability/ |
Next: Order States →