POST /api/marketplace/purchase-requests/:id/delivery-code
POST /api/marketplace/purchase-requests/:id/verify-delivery
Delivery Confirmation Flow
After the escrow is funded (Payment Flow - SHKeeper / Payment Flow - DePay & Web3) and the seller has prepared the item, the seller marks shipped, the buyer enters a delivery code to confirm receipt, and the escrow becomes eligible for release (Payout Flow).
Actors
Seller — marks the order shipped and presents the delivery code to the buyer at hand-off.
Buyer — confirms by entering the code in the dashboard.
Backend — DeliveryService (backend/src/services/delivery/DeliveryService.ts), exposed through the marketplace routes (backend/src/services/marketplace/routes.ts).
PurchaseRequest.status is payment, processing, or delivery.
Payment.escrowState === 'funded'.
Step-by-step narrative
Seller marks shipped — from the seller step frontend/src/sections/request/components/seller-steps/step-3-ship-goods.tsx, clicks "Mark as shipped". This patches the request status to delivery.
Delivery code generation — when the order transitions to delivery, DeliveryService.generateDeliveryCode(requestId) is invoked. It:
Generates a 6-digit code (Math.floor(100000 + Math.random()*900000)).
Emits delivery-code-generated and delivery-update to request-{requestId}.
Sends a notification to the buyer with the code (in-app, and via email if configured).
Buyer entry — buyer meets the courier / picks up the item, enters the code in frontend/src/sections/request/components/seller-steps/delivery-code-verification.tsx (also surfaced on the buyer side via step-5-receive-goods.tsx).
Verification — POST /api/marketplace/purchase-requests/:id/verify-delivery with { code }:
Matches code against deliveryInfo.deliveryCode.
Checks deliveryCodeExpiresAt > now and deliveryCodeUsed === false.
On success: deliveryInfo.deliveryCodeUsed = true; deliveryCodeUsedAt = now. Status flips delivery → delivered.
Emits purchase-request-updatestatus-changed.
Triggers buyer/seller notifications via notifyDeliveryConfirmed (see PurchaseRequestService.ts:631-641).
Optional auto-release timer — once status === 'delivered', a scheduled job can flip the request to confirming and then to seller_paid after a grace period (e.g. 48h). The auto-release worker is not yet implemented; today an admin completes the chain via Payout Flow.
Manual fast-track — the buyer can also tap "Confirm I received it" to skip the code (used when the code path fails — e.g. lost in transit) which patches status to delivered. This relies on admin trust.
Sequence diagram
sequenceDiagram
autonumber
actor S as Seller
actor B as Buyer
participant FE as Frontend
participant BE as Backend
participant DB as MongoDB
participant IO as Socket.IO
S->>FE: Click "Mark as shipped"
FE->>BE: PATCH /api/marketplace/purchase-requests/{id} {status:"delivery"}
BE->>DB: PurchaseRequest.status="delivery"
BE->>BE: DeliveryService.generateDeliveryCode
BE->>DB: deliveryInfo.deliveryCode=XXXXXX\nexpires=+7d
BE->>IO: emit request-{id} 'delivery-code-generated'
BE->>B: notification w/ code (in-app/email)
S->>B: At hand-off, share the 6-digit code (verbally)
B->>FE: Enter code in dashboard
FE->>BE: POST /api/marketplace/purchase-requests/{id}/verify-delivery {code}
BE->>DB: match code, expires>now, !used
BE->>DB: set deliveryCodeUsed = true
BE->>DB: set status = "delivered"
BE->>IO: emit request-{id} 'purchase-request-update' status-changed
BE->>B: notifyDeliveryConfirmed
BE->>S: notifyDeliveryConfirmed
Note over BE: Auto-release timer (planned) → seller_paid → payout
purchase-request-updatestatus-changed on delivery → delivered.
new-notification → user-{buyerId} with the code.
Side effects
Code is emitted via socket and in-app notification. If a malicious actor has access to the buyer's notifications, they could intercept and confirm delivery prematurely. Treat the code as confidential at the UI layer.
Triggers the path that eventually frees up the escrow (manual today via Payout Flow, auto in the future).
Error / edge cases
Wrong code → 400 Invalid delivery code.
Expired code (>7 days) → 400 Code expired. Admin can regenerate via the manual endpoint.
Already used code → 400 Code already used.
Buyer never confirms → status remains delivery. Auto-release timer (not yet built) should trigger delivered after N days. Until then, admin intervention.
Seller delivers but never marks shipped → buyer can dispute via Dispute Flow; the dispute resolution will release the escrow regardless.
Lost code → POST /:id/delivery-code regenerates a new 6-digit value, invalidates the old one, and re-notifies. Restrict to admin/seller to avoid abuse.
[!tip] Use the code as proof-of-handover
The seller should ask the courier or the buyer at the door for the code before leaving the item. If the buyer disputes "never received", an unused code is strong circumstantial evidence; a used code = buyer confirmed.