Order Lifecycle
Orders are the heart of the marketplace. Each order represents items from a single seller that need to be shipped to a buyer. When someone buys from multiple sellers in one checkout, we create multiple orders — one per seller — all linked to the same transaction.
Understanding the order lifecycle is essential for debugging issues, building new features, or just knowing what’s happening behind the scenes when a user says “my order is stuck.”
The State Machine
Orders move through a series of statuses, from creation to completion (or cancellation). Here’s the full picture — it might look complex at first, but we’ll break it down piece by piece.
Understanding Status Categories
Not all statuses are created equal. Some are “in progress” waiting for someone to take action, while others are final states where the order is done and no more changes can happen.
Active Statuses
These statuses mean the order is still “in flight” — someone needs to do something.
| Status | What’s happening | Who needs to act |
|---|---|---|
pending_shipment | Order just created, waiting for seller to ship | Seller |
cancellation_requested | Buyer wants to cancel, seller has 48h to ship or accept | Seller |
shipped | Package is on its way | Buyer (wait for delivery) |
delivered | Package arrived, 7-day hold period | System (auto-completes) |
dispute_open | Buyer opened a dispute, negotiation phase | Both parties |
dispute_escalated | Dispute couldn’t be resolved, needs admin | Admin |
Common Flows
Most orders follow one of a few common paths. Let’s walk through each one.
The Happy Path: Successful Delivery
This is what we hope happens with every order:
Order Created
Checkout succeeds, order starts in pending_shipment
Seller Ships
Seller adds tracking number, status becomes shipped
Delivery Confirmed
Tracking shows delivered, status becomes delivered
7-Day Hold
Buyer has 7 days to inspect items and open a dispute if needed
Order Completes
After 7 days with no dispute, status becomes completed and funds release to seller
Cancellation Flow
Sometimes buyers change their mind or sellers can’t fulfill an order. Here’s how cancellations work:
The 48-Hour Grace Period: When a buyer requests cancellation, the seller gets 48 hours to either accept the cancellation or ship the order anyway. If they ship, the cancellation is voided and the order continues normally. This protects sellers who were already packing when the request came in.
Key Cancellation Rules
| Rule | Details |
|---|---|
| Buyer request window | Buyers can request cancellation within 7 days of order creation |
| Seller 48h grace | Seller can “void” cancellation by shipping within 48 hours |
| Seller cancel | Available anytime before shipment — immediate refund |
| Auto-cancel | Orders pending shipment for 5+ days are automatically cancelled |
Delivery and the 7-Day Hold
After an order is delivered, we don’t immediately release funds to the seller. There’s a 7-day hold period that gives buyers time to:
- Inspect the items they received
- Make sure everything matches the listing
- Open a dispute if something is wrong
Once the order reaches a terminal status (like completed), a separate 7-day review window opens for both parties to submit ratings. See the Ratings & Reviews page for details.
The Order Data Model
When you’re debugging or building features, you’ll work with the Order document. Here are the key fields:
| Field | Type | Purpose |
|---|---|---|
orderNumber | String | Human-readable ID like OR-9F4K2D8M-1 |
transactionId | ObjectId | Links to the parent Transaction |
buyerId / sellerId | ObjectId | References to User documents for both parties |
status | Enum | Current state from the state machine |
items | Array | Snapshot of purchased items (frozen at checkout) |
subtotal | Number | Item total in cents |
shippingAmount | Number | Shipping cost in cents |
sellerFee | Number | Platform fee in cents |
funds | Object | Tracks held/refunded/transferred amounts |
shipping | Object | Tracking number, carrier info |
statusHistory | Array | Audit trail of all transitions |
dispute | Object | Dispute details (if any) |
Status History: The Audit Trail
Every time an order’s status changes, we record it. This creates a complete history that’s invaluable for customer support and debugging.
interface OrderStatusChange {
from: OrderStatus // Previous status
to: OrderStatus // New status
at: Date // When it changed
actor: 'buyer' | 'seller' | 'system' | 'admin'
reason?: string // Why (for cancellations, etc.)
metadata?: Record<string, unknown>
}The actor field tells you who triggered the change. system means it was an automated job (like auto-cancel or auto-complete), while admin means someone from the platform team manually intervened.
Domain Service: Business Rules
The OrderDomainService contains pure functions that encode all the business rules. These are the source of truth for “can this action happen?”
| Function | What it checks |
|---|---|
isTransitionAllowed(from, to) | Is this status transition valid? |
canShip(order) | Can seller mark as shipped? (status = pending_shipment) |
canBuyerRequestCancellation(order) | Within 7-day window? Not already shipped? |
canSellerCancel(order) | Is order still pending? |
canOpenDispute(order) | Is order delivered/shipped and within 30 days? |
canRefund(order) | Is this status refundable? |
shouldAutoCancel(order) | Has order been pending for 5+ days? |
shouldAutoRelease(order) | Is order delivered + 7 days? |
Always use these domain functions to check permissions. Don’t write your own status checks — the rules can be subtle and change over time.
Background Automation
Several order lifecycle events happen automatically via a cron-triggered background job (process-order-lifecycle) that runs every 30 minutes. The job queries for orders matching specific criteria and processes them through the state machine.
| Job | Trigger | What happens |
|---|---|---|
| Auto-cancel unshipped | Order in pending_shipment for 5+ calendar days | Transitions to cancelled_auto, full refund initiated, inventory restored. Both buyer and seller notified. |
| Buyer cancellation expiry | Order in cancellation_requested where cancellation.refundAt has passed (48h window) | Transitions to cancelled_buyer, full refund initiated. Seller didn’t ship or accept within the grace period. |
| Auto-complete delivered | Order in delivered where funds.payoutEligibleAt has passed (7-day hold) | Transitions to completed. Fund release is then handled by the separate stuck fund operations job after the settlement delay. |
The handler follows a “query batch, process each, continue on error” pattern — if one order fails, the rest still get processed. Individual errors are logged and the job reports a summary at the end.
Fund operations (refunds and transfers) are not handled by the order lifecycle job. They’re handled by a separate process-stuck-fund-operations job. See Fund Management for details.
Code Locations
| Component | Location |
|---|---|
| Order Model | packages/core/db/src/mongo/marketplace/order.model.ts |
| Order Domain Service | packages/backend/domains/order/src/order-domain.service.ts |
| Order State Machine | packages/backend/domains/order/src/order.transitions.ts |
| Order Orchestrator | packages/backend/features/orders/src/order-orchestrator.service.ts |
| Order Lifecycle Handler | packages/backend/features/orders/src/order-lifecycle.handler.ts |
| Order Query Service | packages/backend/features/orders/src/order-query.service.ts |
| Status Constants | packages/backend/domains/order/src/order-status.ts |