Flow docs updated (11 files): - Delivery Confirmation: reversed actor roles (buyer generates, seller verifies), fixed endpoint paths (/delivery-code/generate, /delivery-code/verify) - Passkey (WebAuthn): removed stub/simulated-key claims; real @simplewebauthn/server attestation is implemented; refresh tokens are persisted - Dispute: corrected resolve schema (action enum), removed non-existent statuses, documented security gaps (no role guards on status/resolve/assign), route shadowing, all socket events are TODO stubs - Seller Offer: corrected all endpoint paths, removed 'active' status, documented withdraw dead code, missing seller history page, select-offer notification gap - Notification: corrected mark-all-read method+path, fixed GET /:id broken lookup, added unread-count-update socket event - Authentication: corrected rate limiter (counts all attempts), axios 403 not handled, deleteAccount wrong endpoint bug, changePassword no UI - Password Reset: corrected 6-digit code (not 8), documented no-complexity gap on reset-with-code vs token reset - Payment Flow DePay: /create→/save, removed phantom sub-routes, SIM_ bypass risk, PaymentProvider type gap, getProviderIntentEndpoint routing bug - Payment Flow SHKeeper: removed phantom polling endpoint, fixed release/refund paths - Purchase Request: added pending_payment/active statuses, fixed sellers/attachments endpoints, corrected socket events, PUT→PATCH bug - Escrow: documented dispute resolve does not touch escrow, route shadowing, confirm-delivery auth gap Issues created (35 files in Issues/): - 9 security issues (critical) including: dispute privilege escalation ×4, unauthenticated payment/scanner endpoints ×2, SIM_ production bypass, confirm-delivery ownership gap - 26 additional major/critical bugs covering broken endpoints, missing features, data integrity gaps, and frontend-backend mismatches Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
152 lines
9.3 KiB
Markdown
152 lines
9.3 KiB
Markdown
---
|
|
title: Delivery Confirmation Flow
|
|
tags: [flow, delivery, escrow-release, code]
|
|
related_models: ["[[PurchaseRequest]]", "[[Payment]]"]
|
|
related_apis: ["POST /api/marketplace/purchase-requests/:id/delivery-code/generate", "POST /api/marketplace/purchase-requests/:id/delivery-code/verify"]
|
|
---
|
|
|
|
# Delivery Confirmation Flow
|
|
|
|
> **Last updated:** 2026-05-29 — aligned with code (see [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md))
|
|
|
|
After the escrow is funded ([[PRD - Request Network In-House Checkout]] / [[Escrow Flow]]) and the seller has prepared the item, the seller **marks shipped**, the buyer **generates and reads out the delivery code**, the seller **verifies the code** to confirm receipt, and the escrow becomes eligible for release ([[Payout Flow]]).
|
|
|
|
## Actors
|
|
|
|
- **Buyer** — after the order reaches `delivery` status, explicitly generates the delivery code and reads it out to the seller at hand-off.
|
|
- **Seller** — types the code into their dashboard to confirm delivery.
|
|
- **Backend** — `DeliveryService` (`backend/src/services/delivery/DeliveryService.ts`), exposed through the marketplace routes (`backend/src/services/marketplace/routes.ts`).
|
|
- **MongoDB** — `purchaserequests.deliveryInfo` subdocument fields.
|
|
- **Socket.IO** — `delivery-code-generated`, `delivery-update`.
|
|
|
|
## Preconditions
|
|
|
|
- `PurchaseRequest.status` is `payment`, `processing`, or `delivery`.
|
|
- `Payment.escrowState === 'funded'`.
|
|
|
|
## Step-by-step narrative
|
|
|
|
1. **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`. No code is generated at this point.
|
|
2. **Buyer generates the delivery code** — once status is `delivery`, the buyer explicitly triggers `POST /api/marketplace/purchase-requests/:id/delivery-code/generate` (buyerId is enforced server-side). `DeliveryService.generateDeliveryCode(requestId)`:
|
|
- Generates a 6-digit code (`Math.floor(100000 + Math.random()*900000)`).
|
|
- Sets `deliveryInfo.deliveryCode`, `deliveryCodeGeneratedAt = now`, `deliveryCodeExpiresAt = now + 7d`, `deliveryCodeUsed = false`.
|
|
- Emits `delivery-code-generated` and `delivery-update` to `request-{requestId}`.
|
|
- The code is displayed to the buyer in `frontend/src/sections/request/components/buyer-steps/step-5-receive-goods.tsx`.
|
|
3. **Buyer reads code to seller** — at hand-off the buyer reads the 6-digit code out loud (or shows it) to the seller.
|
|
4. **Seller enters code** — seller types the code into `frontend/src/sections/request/components/seller-steps/delivery-code-verification.tsx`.
|
|
5. **Verification** — `POST /api/marketplace/purchase-requests/:id/delivery-code/verify` with `{ code }` (selectedOffer.sellerId is enforced server-side):
|
|
- 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-update` `status-changed`.
|
|
- Triggers buyer/seller notifications via `notifyDeliveryConfirmed` (see `PurchaseRequestService.ts:631-641`).
|
|
6. **Alternative path — buyer fast-track** — the buyer can also call `PATCH .../confirm-delivery` to set status to `delivered` without any code (used when the code path fails, e.g. code expired or lost). **⚠️ Authorization gap:** this endpoint currently has no authorization check; any authenticated user can call it.
|
|
7. **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]].
|
|
|
|
## Sequence diagram
|
|
|
|
```mermaid
|
|
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"
|
|
Note over BE,DB: No code generated here
|
|
|
|
B->>FE: View delivery code in step-5-receive-goods
|
|
FE->>BE: POST /api/marketplace/purchase-requests/{id}/delivery-code/generate
|
|
BE->>DB: deliveryInfo.deliveryCode=XXXXXX\nexpires=+7d
|
|
BE->>IO: emit request-{id} 'delivery-code-generated'
|
|
FE->>B: Display 6-digit code
|
|
|
|
B->>S: At hand-off, read the 6-digit code aloud
|
|
S->>FE: Enter code in delivery-code-verification
|
|
FE->>BE: POST /api/marketplace/purchase-requests/{id}/delivery-code/verify {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
|
|
```
|
|
|
|
## API calls
|
|
|
|
| Method | Endpoint | Purpose |
|
|
|---|---|---|
|
|
| `PATCH` | `/api/marketplace/purchase-requests/:id` `{status:"delivery"}` | Seller marks shipped |
|
|
| `GET` | `/api/marketplace/purchase-requests/:id/delivery-code` | Retrieve current code (buyer + seller) |
|
|
| `POST` | `/api/marketplace/purchase-requests/:id/delivery-code/generate` | Buyer generates delivery code (buyer only) |
|
|
| `POST` | `/api/marketplace/purchase-requests/:id/delivery-code/verify` | Seller verifies code (seller only) |
|
|
| `GET` | `/api/marketplace/purchase-requests/:id/delivery-code/status` | Check code status (buyer + seller) |
|
|
| `PATCH` | `/api/marketplace/purchase-requests/:id/confirm-delivery` | Buyer fast-track confirm (no code) — ⚠️ no auth check |
|
|
|
|
### Phantom frontend actions (routes do NOT exist on backend)
|
|
|
|
These Redux/API actions exist in the frontend but call endpoints that return 404:
|
|
|
|
| Frontend action | Called path | Behaviour |
|
|
|---|---|---|
|
|
| `regenerateDeliveryCode` | `/delivery-code/regenerate` | 404s; frontend falls back to `/delivery-code/generate` |
|
|
| `getDeliveryAttempts` | `/delivery-code/attempts` | 404s — feature not implemented |
|
|
| `getDeliveryStats` | `/delivery/stats` | 404s — feature not implemented |
|
|
|
|
## Two paths to `delivered` status
|
|
|
|
1. **Code path** — seller calls `POST .../delivery-code/verify` with the correct, unexpired code → status becomes `delivered`.
|
|
2. **Fast-track path** — buyer calls `PATCH .../confirm-delivery` (no code required) → also becomes `delivered`. ⚠️ Currently no authorization check on this endpoint.
|
|
|
|
## Database writes
|
|
|
|
- **`purchaserequests.deliveryInfo`** — `deliveryCode`, `deliveryCodeGeneratedAt`, `deliveryCodeExpiresAt`, `deliveryCodeUsed`, `deliveryCodeUsedAt`.
|
|
- **`purchaserequests.status`** — `delivery` → `delivered` → (eventually `seller_paid` → `completed`).
|
|
- **`notifications`** — generated for both parties.
|
|
|
|
## Socket events emitted
|
|
|
|
- **`delivery-code-generated`** → `request-{id}` (with code, expiresAt).
|
|
- **`delivery-update`** → `request-{id}` (`type: 'code-generated'`).
|
|
- **`purchase-request-update`** `status-changed` on `delivery → delivered`.
|
|
|
|
## Side effects
|
|
|
|
- The code is shown only to the **buyer** in their dashboard. The buyer verbally shares it with the seller — there is no backend push of the code to the seller.
|
|
- 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`. Buyer can generate a new code via `POST .../delivery-code/generate` (the `regenerateDeliveryCode` frontend action also falls through to this endpoint).
|
|
- **Already used code** → `400 Code already used`.
|
|
- **Buyer never generates / 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 / expired code** → buyer re-triggers `POST .../delivery-code/generate` to get a fresh code, invalidating the old one.
|
|
|
|
> [!tip] The buyer holds the code, not the seller
|
|
> The seller should ask the buyer for the code at hand-off. If the buyer disputes "never received", an unused code is strong circumstantial evidence that delivery has not been confirmed; a used code = seller confirmed receipt.
|
|
|
|
## Linked flows
|
|
|
|
- [[PRD - Request Network In-House Checkout]] / [[Escrow Flow]] — funding precondition.
|
|
- [[Escrow Flow]] — state transitions triggered by confirmation.
|
|
- [[Payout Flow]] — fires after confirmation (manual today).
|
|
- [[Dispute Flow]] — escape hatch.
|
|
- [[Notification Flow]] — channel for the code.
|
|
|
|
## Source files
|
|
|
|
- Backend: `backend/src/services/delivery/DeliveryService.ts`
|
|
- Backend: `backend/src/services/marketplace/routes.ts` (delivery endpoints)
|
|
- Backend: `backend/src/services/marketplace/PurchaseRequestService.ts:631-641` (confirmation notifications)
|
|
- Frontend: `frontend/src/sections/request/components/seller-steps/step-3-ship-goods.tsx`
|
|
- Frontend: `frontend/src/sections/request/components/seller-steps/delivery-code-verification.tsx`
|
|
- Frontend: `frontend/src/sections/request/components/buyer-steps/step-5-receive-goods.tsx`
|