Trade completion
A P2P trade is completedwhen the escrow chain closes: the card has left the seller, the buyer’s protection window has run its course, and the seller’s payout clock can start. There are exactly three ways a trade completes, and the trade records which one happened (market_trades.completed_via).
Where this lives in code.
- Buyer confirm + auto-complete sweep:
apps/storefront/src/lib/market/completion.ts.- Confirm-receipt endpoint:
POST /api/market/trades/[id]/received.- Admin path:
updateEscrowStatusinapps/storefront/src/lib/market/db.ts.- Dispute window per tier:
apps/storefront/src/lib/escrow/service-tiers.ts, stamped ontomarket_trades.dispute_window_hoursat trade creation.- Payout release:
apps/storefront/src/lib/payouts/sweep.ts(requirescompleted+completed_at).
Path 1 — you confirm receipt (buyer_confirm)
Once the card has been dispatched to you (shipped_to_buyer), the trade page shows a Confirm received button. Pressing it:
- Marks the escrow
completedand stampscompleted_atanddelivered_at. - Starts the seller’s payout clock (see /methodology/payout-hold).
- Closes the dispute window — if something is wrong with the card, open a dispute before confirming.
- Recomputes both parties’ trust scores (/methodology/trust-score).
This is the only path that records a delivered_at timestamp, because it is the only path where anyone actually told us the card arrived. The platform sees confirmations, not deliveries — the carrier owns the delivery truth.
Path 2 — the window lapses (auto_window)
If you do nothing after dispatch, the trade completes on its own once the dispute window has elapsed:
dispatch time + dispute_window_hours
The window is stamped onto the trade at creation from its escrow tier (/methodology/escrow-tier): 48 hours for Direct Ship, 72 for Verified Ship, 168 (7 days) for Full Escrow. Trades created before window stamping fall back to their tier’s current default. The trade page shows your trade’s exact auto-complete date while you wait.
The sweep (run by the maintenance cron) will not auto-complete a trade that has:
- an open dispute (any status other than resolved/closed),
- an open return request (requested / accepted / shipping / received), or
- a pending cancellation handshake.
Any of those withholds completion while it is open — the sweep skips the trade for as long as the blocker stands. The deadline itself does not move: no time is paused or added back, so if the blocker resolves after the window has already elapsed, the trade completes at the next sweep run rather than restarting or resuming a remainder. Auto-completion does not verify that the card arrived — it means the protection window passed without anyone raising a problem. No delivered_at is recorded on this path.
Path 3 — an admin closes it (admin)
Admins can mark a trade completed manually (support cases, dispute resolutions that release the seller). This is the same override that existed before the two paths above; it now stamps completed_via = 'admin' so the record says who closed the trade. Dispute resolutions that end in a refund mark the trade refunded instead — that is not a completion.
What completion starts: the payout clock
The payout sweep only considers trades that are completed with a completed_atstamp. From that moment the seller’s funds wait out the trade’s payout_hold_days (set by trust tier at trade creation — /methodology/payout-hold), then release automatically if the seller has payouts enabled, or wait for a manual admin payout otherwise. Confirming receipt earlier starts this clock earlier — that is the honest trade-off the confirm button offers.
What completion ends — and what it doesn’t
- Ends: the dispute window. Disputes are for problems discovered before completion; raise them from the trade page while the trade is in flight.
- Doesn’t end:no-fault returns. If the seller’s listing accepted returns, the return window (
return_window_days) runs from completion — completing the trade is what starts it. - Doesn’t end: reviews. Completed (and refunded) trades are reviewable by both parties.
Worked example
Direct Ship trade, dispatched Monday 14:00 with a 48-hour dispute window. The buyer confirms receipt Tuesday 10:00 → trade completes Tuesday 10:00, completed_via = 'buyer_confirm', payout clock starts then. Had the buyer done nothing, the sweep would complete it after Wednesday 14:00, completed_via = 'auto_window' — later, so the seller is paid later. Had the buyer opened a dispute Tuesday, nothing completes until the dispute resolves.