Order lifecycle

The orders row owns business state only: status, payout_status, fulfillment_method. Payment provider truth (Pagar.me) lives on a 1:1 payments row; carrier/courier truth (ME, Lalamove, meetup PIN) lives on a 1:1 fulfillments row. The order machine fires normalized events; webhook handlers stamp payments.provider_status_raw / fulfillments.provider_status_raw so the raw trail is preserved alongside cüte's normalized read.

For the deeper dive on shipping money flow, label-buy timing, and cancellation refunds, see Fulfillment & money flow.

Canonical statuses

StatusMeaning
pending_paymentOrder created, payment not yet confirmed. Item is reserved.
activePayment confirmed. Seller is fulfilling. Splits pre/post-ship by orders.shippedAt.
awaiting_buyer_confirmationDelivered or handed off. 2-day buyer acceptance window.
completedFunds released. Item moves reserved → sold.
cancelledTerminal cancel. Item moves reserved → available.
disputedBuyer raised a dispute during the acceptance window. payout_status = blocked; the dispute region is active.

Fulfillment method (shipping | same_day | meetup) and payment status (pending | analyzing | paid | failed | expired | refunded | chargeback) are independent dimensions — see the diagrams below for how they fan out.

Payment flow (PIX vs credit card)

What happens when a buyer clicks "place order" — the two payment paths diverge here.

MethodInitial payments.statusConfirmationTimeout
PIXpendingWebhook order.paid after buyer scans QR30 min, then cron cancels
Credit cardanalyzingWebhook order.paid after anti-fraud passesPagar.me timeout

In both cases the item is reserved immediately at order creation (before payment confirms). If payment fails or expires, the cron releases the item back to available.

Happy path — shipping (carrier)

The standard order flow when the seller ships via a carrier (ME).

LegendLiveWaitingCompleted

Happy path — same-day (Lalamove / Uber Direct)

On-demand courier dispatched by cüte once the seller marks the parcel ready.

LegendLiveWaitingCompleted

Happy path — meetup

In-person handoff. Buyer and seller coordinate via chat; seller enters the buyer's 4-digit PIN to confirm handoff.

LegendLiveWaitingCompleted

Dispute path

Buyer raises a dispute during the 2-day acceptance window. The dispute itself is the machine's fifth region, mirroring disputes.status (none until a row exists): triage, party questions and the decision all move that region, and every move is an order event with a dispute from/to pair on order_events. A closure event is fired twice: the deciding side records it on the region, and the saga fires it again (role: system) after the settlement claim to move the order and the payout. The region, its role rules and the money fence are on the dispute region page.

LegendWaitingCompletedCancelled / failedDecision

Cancellation paths

Orders can be cancelled at two distinct stages — pre-payment, or after payment but before shipping. Once an order is shipped (active + shippedAt set) or delivered, cancellation gives way to the dispute flow.

LegendLiveWaitingCancelled / failed
StageWho can cancelTrigger / reason
pending_paymentbuyer, cron (payment_expired), webhook (payment_failed)Buyer abandons / payment fails / 30-min PIX timeout
active, shippedAt = nullbuyer, seller, admin, cron (ship_deadline_expired)Manual cancel, or 5 biz-day fulfillment deadline missed
active, shippedAt setCannot cancel once in transit; only dispute_opened after delivery
disputedthe saga, after an admin resolves for the buyer (dispute_resolved_refund)Dispute resolved in buyer's favor

Full state diagram

All possible transitions of the order region; the payment, fulfillment, payout and dispute regions move alongside it.

LegendLiveWaitingCompletedCancelled / failed

Guards

GuardConditionUsed on
isShippingfulfillmentMethod === "shipping"label_purchased, label_generated, seller_shipped
isSameDayfulfillmentMethod === "same_day"courier_dispatched, courier_in_transit
isMeetupfulfillmentMethod === "meetup"handoff_confirmed, the one-tap buyer_accepted
notShippedorders.shippedAt IS NULLship_deadline_expired (don't auto-cancel an in-transit order)
noActiveDisputethe dispute region is none or a closureauto_accept_timeout
canOpenDisputethe dispute region is none and the event is fired by a buyer, seller or admindispute_opened
settlesResolvedRefund / settlesResolvedSeller / settlesResolvedPartial / settlesWithdrawn / settlesDismissedthe dispute region already holds the matching closure and the event is fired by system (the saga's second fire)the five dispute closures on the order and payout regions
byAdmin, byBuyerOrAdmin, bySellerOrAdmin, byBuyer, bySeller, asksBuyer, asksSellerthe event's role / partythe dispute region's own edges

See packages/commerce/orders/order.status-projection.ts for the full list.

Item status sync

Order statusItem status
pending_payment / active / awaiting_buyer_confirmation / disputedreserved
completedsold
cancelledavailable

Who changes the status, and when

TransitionEventActorTriggerNotes
pending_payment(order created)system (checkout API)Buyer places orderItem: available → reserved; payment + fulfillment rows created
pending_paymentactivepayment_confirmedwebhook (Pagar.me)PIX paid / CC anti-fraud passespayments.status = paid, payout_status = hold, fulfillments.status = awaiting_seller; seller's held funds credited
pending_paymentcancelledpayment_failedwebhookAnti-fraud declined / payment failedItem released
pending_paymentcancelledpayment_expiredcron30-min PIX timeoutItem released
pending_paymentcancelledbuyer_cancelbuyerBuyer abandons checkoutItem released
active (no state change)label_purchased / label_generatedsystem / webhook (ME)Seller clicks "Print label" — see FulfillmentSets fulfillments.gatewayShipmentId, labelUrl. orders.shippedAt still null.
active (no state change)seller_shippedwebhook (ME order.posted)Seller drops parcel at carrierorders.shippedAt = now, fulfillments.status = posted
active (no state change)courier_dispatched / courier_in_transitwebhook (Lalamove)Same-day courier flowfulfillments.status = courier_dispatched / in_transit
activeawaiting_buyer_confirmationdelivery_confirmedwebhook (ME / Lalamove)Carrier marks deliveredacceptByAt = now + 2 days, fulfillments.status = delivered
activeawaiting_buyer_confirmationhandoff_confirmedsellerSeller enters buyer's PIN (meetup)fulfillments.status = handoff_confirmed, acceptByAt = now + 2 days
activecancelledship_deadline_expiredcron5 biz days passed, not shippedRefund issued, item released. Guarded by notShipped.
activecancelledbuyer_cancel / seller_cancel / admin_cancelbuyer / seller / adminManualRefund + item released
awaiting_buyer_confirmationcompletedbuyer_acceptedbuyerBuyer clicks "Confirm receipt"payout_status = released, item: reserved → sold
awaiting_buyer_confirmationcompletedauto_accept_timeoutcron2 days post-delivery, no disputepayout_status = released. Guarded by noActiveDispute.
awaiting_buyer_confirmationdisputeddispute_openedbuyerBuyer raises disputeDispute region none → open; auto-accept blocked, payout_status = blocked. The open fence in order.claims.ts refuses it once a settlement claim is standing.
disputed (no state change)dispute_triaged / dispute_party_asked / dispute_party_responded / dispute_evidence_addedadmin / the awaited partyCase workOnly the dispute region moves
disputed (no state change)any of the five closures, first fireadmin (or buyer withdrawing)The decisionDispute region enters its closure; order and payout wait for the saga
disputedcancelleddispute_resolved_refund, second firesystem (saga, after the settlement claim)Dispute resolved for buyerBuyer refunded, payments.status = refunded
disputedcompleteddispute_resolved_seller / dispute_withdrawn / dispute_dismissed, second firesystemDispute closed in the seller's favourpayout_status = released
disputedcompleteddispute_resolved_partial, second firesystemSplit decisionpayments.status = partially_refunded, payout_status = released minus the refunded portion

Deadlines & timers

TimerWindowOwnerWhat happens at zero
PIX payment30 minutescron (expire-pending-payments)payment_expired → cancelled
CC anti-fraudPagar.me-managedPagar.meWebhook fires either order.paid or order.payment_failed
Fulfillment / ship deadline5 business days from payment_confirmedcron (overdue-orders sweep)ship_deadline_expired → cancelled + refund (gated by notShipped)
Auto-accept7 calendar days from delivery / handoffcron (auto-accept-orders)auto_accept_timeout → completed + funds released (gated by noActiveDispute)