Lewati ke konten utama

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.tsx
  • interface/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.tsx
  • infrastructure/adapters/BiteshipAdapter.ts

Phase 3, Order creation

Buyer: Confirms order, selects payment method. System:

  1. Creates orders row with state AWAITING_PAYMENT
  2. Calls Xendit /v2/invoices with order metadata + idempotency key
  3. Stores invoice ID on order
  4. 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.tsx
  • domain/order/CreateOrder.ts
  • infrastructure/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:

  1. Verifies HMAC signature (timing-safe compare)
  2. Verifies idempotency key not already processed
  3. Compares paid amount to expected total
  4. Match: Updates order to READY_TO_SHIP, notifies seller
  5. Mismatch: Updates to PAYMENT_REVIEW, alerts ops

Critical guard: Amount mismatch never auto-progresses. Always pauses for ops review.

Files:

  • interface/http/routes-webhooks.tsx
  • domain/order/ConfirmPayment.ts
  • infrastructure/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_SHIPLATE_TO_SHIP. Ops can force-cancel.

Files:

  • interface/http/routes-shipments.tsx
  • domain/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:

  1. Buyer confirms manually → COMPLETED
  2. 48 hours pass → auto-COMPLETED
  3. 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

FailureWhat happensRecovery
Buyer abandons checkoutOrder stays AWAITING_PAYMENTAuto-cancels after 12 hours
Xendit webhook delayedOrder stuck pendingOutboxProcessor retries every 15 min
Biteship API downRate quote failsCircuitBreaker returns fallback message, retry after 30s
Payment amount mismatchOrder to PAYMENT_REVIEWOps investigates, manually approves or rejects
Seller doesn't shipAfter 2 days → LATE_TO_SHIPOps can force-cancel and refund
Webhook duplicateIdempotencyGuard blocks reprocessingOriginal outcome preserved

Reliability patterns in action

Three patterns that make the checkout robust:

PatternWhere it firesWhat it prevents
IdempotencyGuardOrder creation, Xendit webhook, Biteship webhookDuplicate orders, double payments
OutboxProcessorNotifications, fin_disbursement creationLost side effects when DB commit succeeds but downstream fails
CircuitBreakerAll external API callsCascading failures when partner is down

Where to look in the codebase

What you wantWhere it lives
Order creationdomain/order/CreateOrder.ts
Payment confirmationdomain/order/ConfirmPayment.ts
Xendit adapterinfrastructure/adapters/XenditAdapter.ts
Biteship adapterinfrastructure/adapters/BiteshipAdapter.ts
Webhook routesinterface/http/routes-webhooks.tsx
Order routesinterface/http/routes-orders.tsx
Shipment routesinterface/http/routes-shipments.tsx
Reliability layerinfrastructure/reliability/

Next: Order States →