docs: sync from backend cbc32dc — template delivery rails

This commit is contained in:
Siavash Sameni
2026-05-31 15:52:30 +04:00
parent 9f8cc104c7
commit 35640e38cc
8 changed files with 124 additions and 22 deletions

View File

@@ -6,7 +6,7 @@ aliases: [Purchase Request, Buy Request, IPurchaseRequest]
# PurchaseRequest
> **Last updated:** 2026-05-31 — `budget.currency` aligned with template/Postgres enum (`USD`, `EUR`, `IRR`, `USDT`, `USDC`); `categoryId` added to `IRequestTableItem`
> **Last updated:** 2026-05-31 — `budget.currency` aligned with template/Postgres enum (`USD`, `EUR`, `IRR`, `USDT`, `USDC`); template checkout now preserves seller-owned delivery mode and overlays buyer address/email.
The central buyer-side document. A `PurchaseRequest` captures what a buyer wants to acquire (physical product, digital product, service, or consultation), the budget envelope, urgency, delivery details, and the entire lifecycle from creation through payment, delivery, and completion. Sellers respond by attaching [[SellerOffer]] documents; the buyer accepts one, a [[Payment]] is opened, and delivery is verified by a 6-digit code.
@@ -39,15 +39,15 @@ The central buyer-side document. A `PurchaseRequest` captures what a buyer wants
| `specifications[].key` | String | yes | — | trim | — | Spec key. |
| `specifications[].value` | String | yes | — | trim | — | Spec value. |
| `specifications[].label` | String | no | — | trim | — | Human label. |
| `deliveryInfo.deliveryType` | String | yes | `physical` | enum: `physical` / `online` | — | Delivery channel. |
| `deliveryInfo.address` | String | no | — | — | — | Physical address. |
| `deliveryInfo.deliveryType` | String | yes | `physical` | enum: `physical` / `online` | — | Delivery channel. Direct requests are buyer-selected; template checkout inherits the seller-selected [[RequestTemplate]] delivery mode. |
| `deliveryInfo.address` | String | no | — | — | — | Physical address. In template checkout this is built from the buyer's selected billing address only when the template requires physical delivery. |
| `deliveryInfo.preferredDate` | Date | no | — | — | — | Buyer's target date. |
| `deliveryInfo.notes` | String | no | — | — | — | Free-form notes. |
| `deliveryInfo.deliveryAddress.name` | String | no | — | — | — | Recipient name. |
| `deliveryInfo.deliveryAddress.phoneNumber` | String | no | — | — | — | Recipient phone. |
| `deliveryInfo.deliveryAddress.fullAddress` | String | no | — | — | — | Full address string. |
| `deliveryInfo.deliveryAddress.fullAddress` | String | no | — | — | — | Full address string copied from checkout billing for physical template orders. |
| `deliveryInfo.deliveryAddress.addressType` | String | no | — | — | — | e.g. Home / Office. |
| `deliveryInfo.email` | String | no | — | email regex | — | For digital delivery. |
| `deliveryInfo.email` | String | no | — | email regex | — | Buyer receiving email for digital/online template delivery. |
| `deliveryInfo.sellerDeliveryInfo.estimatedDeliveryDate` | Date | no | — | — | — | Seller's ETA date. |
| `deliveryInfo.sellerDeliveryInfo.estimatedDeliveryTime` | String | no | — | — | — | Seller's ETA time. |
| `deliveryInfo.sellerDeliveryInfo.trackingNumber` | String | no | — | — | — | Carrier tracking. |
@@ -129,6 +129,14 @@ None defined.
- **References**: [[User]] (`buyerId`, `preferredSellerIds[]`, `deliveryInfo.deliveryCodeUsedBy`, `deliveryInfo.deliveryAttempts[].sellerId`), [[Category]] (`categoryId`), [[SellerOffer]] (`offers[]`, `selectedOfferId`).
- **Referenced by**: [[SellerOffer]] (`purchaseRequestId`), [[Payment]] (`purchaseRequestId`), [[Dispute]] (`purchaseRequestId`), [[Chat]] (`relatedTo.id` when `relatedTo.type === 'PurchaseRequest'`), [[Review]] (`purchaseRequestId`).
## Template Checkout Mapping
When a buyer converts a [[RequestTemplate]], the seller's template remains authoritative for delivery mode:
- `physical` templates require a buyer billing/delivery address in checkout. The generated request stores both `deliveryInfo.address` and `deliveryInfo.deliveryAddress`.
- `online` templates require a buyer email in checkout. The generated request stores it in `deliveryInfo.email`.
- Mixed carts can produce multiple requests with different delivery modes; the checkout UI asks for the union of required buyer details.
## State Transitions
```mermaid

View File

@@ -6,7 +6,9 @@ aliases: [Template, Request Template, IRequestTemplate]
# RequestTemplate
A reusable template authored by a seller. When a buyer visits the template's `shareableLink`, the front-end pre-fills a new [[PurchaseRequest]] with the template's category, urgency, specs, delivery info, and an optional default seller `proposal`. The schema mirrors `PurchaseRequest` for fast cloning, plus template-specific bookkeeping (`isActive`, `usageCount`, `maxUsage`, `expiresAt`).
> **Last updated:** 2026-05-31 — seller-owned delivery mode and per-template payment rails documented.
A reusable template authored by a seller. When a buyer visits the template's `shareableLink`, the front-end pre-fills a new [[PurchaseRequest]] with the template's category, urgency, specs, seller-selected delivery mode, payment rail allowlist, and an optional default seller `proposal`. The schema mirrors `PurchaseRequest` for fast cloning, plus template-specific bookkeeping (`isActive`, `usageCount`, `maxUsage`, `expiresAt`).
> [!note] Source
> `backend/src/models/RequestTemplate.ts:65` — schema definition
@@ -34,9 +36,12 @@ A reusable template authored by a seller. When a buyer visits the template's `sh
| `specifications[].key` | String | yes | — | trim | — | Spec key. |
| `specifications[].value` | String | yes | — | trim | — | Spec value. |
| `specifications[].label` | String | no | — | trim | — | Human label. |
| `deliveryInfo.deliveryType` | String | no | `physical` | enum: `physical` / `online` | — | Delivery channel. |
| `deliveryInfo.notes` | String | no | — | — | — | Notes. |
| `deliveryInfo.email` | String | no | — | email regex | — | Digital delivery email. |
| `deliveryInfo.deliveryType` | String | no | `physical` | enum: `physical` / `online` | — | Seller-selected delivery channel. Buyers cannot override this at checkout. |
| `deliveryInfo.notes` | String | no | — | — | — | Seller notes about delivery. |
| `deliveryInfo.email` | String | no | — | email regex when non-empty | — | Legacy/optional field. Template checkout now asks the buyer for a receiving email when `deliveryType === "online"`. |
| `paymentConfig.useShopDefault` | Boolean | no | `true` | — | — | When `false`, the template's own chain/token allowlist overrides [[ShopSettings]]. New template UI defaults this to `false` so sellers choose rails explicitly. |
| `paymentConfig.allowedChains[]` | Number[] | no | `[1, 56]` | must contain at least one positive chain id | — | Chain ids accepted for this template, e.g. `1` Ethereum, `56` BSC. Empty arrays are rejected. |
| `paymentConfig.allowedTokens[]` | String[] | no | `["USDC", "USDT"]` | must contain at least one non-empty token symbol | — | Settlement tokens accepted for this template. Empty arrays are rejected. |
| `serviceInfo.duration` | Number | no | — | min 0.5 | — | Hours. |
| `serviceInfo.sessionType` | String | no | — | enum: `online` / `in_person` / `hybrid` | — | Session type. |
| `serviceInfo.location` | String | no | — | trim, maxlength 200 | — | Location. |
@@ -95,6 +100,12 @@ None defined.
- **References**: [[User]] (`sellerId`), [[Category]] (`categoryId`).
- **Referenced by**: [[PurchaseRequest]] (`metadata.templateId` as string), [[Review]] (`subjectId` when `subjectType === 'template'`).
## Checkout Semantics
- The seller chooses `deliveryInfo.deliveryType` on the template. The buyer checkout step only collects the required fulfillment details: a physical address for `physical`, a receiving email for `online`, and both when a cart mixes physical and online templates.
- `batch-convert` copies the seller's delivery mode into each generated [[PurchaseRequest]] and overlays the buyer-supplied billing/email details.
- Payment checkout resolves allowed rails through `paymentConfig`: template override first, then [[ShopSettings]], then the global supported default. A template with an explicit empty chain or token list is invalid.
## State Transitions
```mermaid

View File

@@ -6,7 +6,9 @@ aliases: [Shop, Storefront, IShopSettings]
# ShopSettings
One-to-one storefront configuration for a seller. Holds the shop name, description, avatar, cover image, public visibility flag, review toggles (`allowSellerReviews`, `allowTemplateReviews`), and social links. The unique constraint on `sellerId` enforces the one-shop-per-seller invariant.
> **Last updated:** 2026-05-31 — store-level payment rail defaults documented.
One-to-one storefront configuration for a seller. Holds the shop name, description, avatar, cover image, public visibility flag, review toggles (`allowSellerReviews`, `allowTemplateReviews`), social links, and store-level payment rail defaults. The unique constraint on `sellerId` enforces the one-shop-per-seller invariant.
> [!note] Source
> `backend/src/models/ShopSettings.ts:22` — schema definition
@@ -28,6 +30,8 @@ One-to-one storefront configuration for a seller. Holds the shop name, descripti
| `socialLinks.instagram` | String | no | `""` | — | — | Instagram URL. |
| `socialLinks.linkedin` | String | no | `""` | — | — | LinkedIn URL. |
| `socialLinks.twitter` | String | no | `""` | — | — | Twitter / X URL. |
| `paymentConfig.allowedChains[]` | Number[] | no | `[1, 56]` | update route requires at least one chain when supplied | — | Store-level accepted chain ids used by templates with `paymentConfig.useShopDefault === true`. |
| `paymentConfig.allowedTokens[]` | String[] | no | `["USDC", "USDT"]` | update route requires at least one token when supplied | — | Store-level accepted settlement tokens. |
| `createdAt` | Date | auto | — | — | — | Mongoose timestamp. |
| `updatedAt` | Date | auto | — | — | — | Mongoose timestamp. |
@@ -82,6 +86,9 @@ ShopSettings.findOneAndUpdate(
// Public shop directory
ShopSettings.find({ isPublic: true }).sort({ createdAt: -1 });
// Resolve seller-level payment rails for template checkout
ShopSettings.findOne({ sellerId }).select('paymentConfig');
```
> [!warning] Creating two shops will fail

View File

@@ -5,7 +5,7 @@ tags: [api, marketplace, reference]
# Marketplace API
> **Last updated:** 2026-05-29aligned with code (see [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md))
> **Last updated:** 2026-05-31request-template delivery mode and payment rail validation updated.
All marketplace endpoints live under `/api/marketplace/*`. The router is composed of several files mounted from `app.ts`:
@@ -345,13 +345,23 @@ A [[RequestTemplate]] is a re-usable "shop product" a seller can publish. Buyers
quantity?: number; // 1-10000
budget?: { min?: number; max?: number; currency: "USD" | "EUR" | "IRR" | "USDT" | "USDC" };
urgency?: "low" | "medium" | "high" | "urgent";
deliveryInfo?: { deliveryType: "physical" | "online"; email?: string };
deliveryInfo?: {
deliveryType: "physical" | "online"; // seller-selected; buyer cannot override at checkout
notes?: string;
email?: string; // optional legacy field; empty string is accepted
};
paymentConfig?: {
useShopDefault: boolean; // false = template override, true = shop defaults
allowedChains: number[]; // at least one positive chain id when paymentConfig is sent
allowedTokens: string[]; // at least one non-empty token symbol when paymentConfig is sent
};
maxUsage?: number | null; // 0/null = unlimited
expiresAt?: string | null; // ISO date
images?: string[]; // URLs from [[File API]]
}
```
**Response 201:** `{ data: { template } }` with a generated `shareableLink`.
**Validation:** If `paymentConfig` is present, both `allowedChains` and `allowedTokens` must be non-empty. The UI now defaults new templates to explicit template rails, so a seller must choose at least one chain and one token before publishing.
### GET /api/marketplace/request-templates
@@ -399,7 +409,7 @@ A [[RequestTemplate]] is a re-usable "shop product" a seller can publish. Buyers
### POST /api/marketplace/request-templates/batch-convert
**Description:** Convert several templates at once (cart checkout).
**Description:** Convert several templates at once (cart checkout). The seller's template delivery mode is preserved; buyer-supplied checkout details are only overlaid where that mode requires them.
**Auth required:** Bearer JWT (buyer)
**Request body:**
```ts
@@ -410,8 +420,25 @@ A [[RequestTemplate]] is a re-usable "shop product" a seller can publish. Buyers
sellerId: string; // MongoId
}>;
status?: "pending" | "pending_payment" | "active";
paymentConfirmed?: boolean;
paymentData?: Record<string, unknown>;
deliveryInfo?: {
email?: string; // copied to generated online requests
billing?: {
name?: string;
phoneNumber?: string;
address?: string;
city?: string;
state?: string;
country?: string;
zipCode?: string;
addressType?: string;
fullAddress?: string; // copied to generated physical requests
};
};
}
```
**Delivery mapping:** `online` templates use `deliveryInfo.email`; `physical` templates use `deliveryInfo.billing` to fill `deliveryInfo.address` and `deliveryInfo.deliveryAddress` on the generated [[PurchaseRequest]].
### POST /api/marketplace/request-templates/complete-payment

View File

@@ -5,7 +5,7 @@ tags: [api, payment, reference, request-network, escrow]
# Payment API
> **Last updated:** 2026-05-31 — Postgres integration promotion, oracle quote persistence, AMN scanner rail-switch fix, capped webhook confirmation persistence, and partial gasless permit endpoints.
> **Last updated:** 2026-05-31 — Postgres integration promotion, oracle quote persistence, AMN scanner rail-switch fix, capped webhook confirmation persistence, seller/template payment rail options, and partial gasless permit endpoints.
The payment surface is split across provider-neutral payment routers, Request Network checkout/webhook routes, derived-destination custody routes, and admin safety routes:
@@ -23,7 +23,7 @@ The payment surface is split across provider-neutral payment routers, Request Ne
Core model: [[Payment]]. Coordination logic to avoid race conditions when multiple sources update the same payment is in `paymentCoordinator.ts`.
> [!warning] Persistence status
> Payment APIs still create/read/update Mongo `Payment` documents on backend `2.6.82`. The Postgres branch adds schemas, repos, migrations, and optional quote persistence, but it is not a full payment-domain cutover. `/api/payment/request-network/intents` can write `payment_quotes` only when `ORACLE_QUOTING_ENABLED=true`; the payment record itself remains Mongo-backed unless future service wiring changes that boundary.
> Payment APIs still create/read/update Mongo `Payment` documents on backend `2.6.83`. The Postgres branch adds schemas, repos, migrations, and optional quote persistence, but it is not a full payment-domain cutover. `/api/payment/request-network/intents` can write `payment_quotes` only when `ORACLE_QUOTING_ENABLED=true`; the payment record itself remains Mongo-backed unless future service wiring changes that boundary.
## Configuration / health
@@ -175,6 +175,30 @@ Core model: [[Payment]]. Coordination logic to avoid race conditions when multip
```
**Response 200:** `{ success: true, data: { paymentId, paymentUrl, providerPaymentId, raw, ... } }`
### GET /api/payment/request-network/options
**Description:** Resolves the chain/token rails a buyer may use for a seller or template checkout. Precedence is template override (`RequestTemplate.paymentConfig.useShopDefault === false`), then store defaults (`ShopSettings.paymentConfig`), then the global supported default.
**Auth required:** Bearer JWT
**Query params:** `sellerId?`, `templateId?`
**Response 200:**
```ts
{
success: true;
data: {
allowedChains: number[];
allowedTokens: string[];
source: "item" | "store" | "default";
chains: Array<{
chainId: number;
name: string;
shortName: string;
tokens: Array<{ symbol: string; address: string; decimals: number }>;
}>;
};
}
```
**Frontend use:** Template checkout calls this with both `sellerId` and the real `templateId` before creating payment intents, then defaults to BSC/USDT when allowed or the first returned rail otherwise.
### POST /api/payment/request-network/intents
**Description:** Richer buyer intent endpoint used by the provider-selection checkout. It can dispatch either to `request.network` or `amn.scanner`, validates the seller's allowed chain/token choices, and re-points an existing pending intent when the buyer changes rail. When `ORACLE_QUOTING_ENABLED=true`, the backend ignores client-supplied `amount`, loads the seller offer price, computes a depeg-protected quote, and uses the computed settlement amount for the provider intent.

View File

@@ -5,7 +5,7 @@ related_models: ["[[PurchaseRequest]]", "[[Category]]", "[[Address]]", "[[Seller
related_apis: ["POST /api/marketplace/purchase-requests", "GET /api/marketplace/purchase-requests", "PATCH /api/marketplace/purchase-requests/:id"]
---
> **Last updated:** 2026-05-29aligned with code (see Doc vs Code Audit Report)
> **Last updated:** 2026-05-31template checkout delivery/payment rail behavior added.
> [!warning] Audit — 2026-05-29
> This document was corrected against the live codebase. Key changes: status enum updated (added `pending_payment`, `active`; removed undocumented `finalized`/`archived`); urgency values expanded to include `urgent`; sellers endpoint corrected; attachment upload endpoint corrected; `request-cancelled` socket event removed (non-existent); `new-purchase-request` fan-out target corrected to shared `sellers` room; socket room join/leave events documented; description minimum corrected to 5 chars; PUT vs PATCH mismatch flagged as known bug; two frontend actions hitting non-existent backend endpoints flagged as not implemented.
@@ -212,6 +212,7 @@ sequenceDiagram
## Linked flows
- [[RequestTemplate]] checkout — seller chooses physical vs online delivery on the template; buyer checkout collects only the required address/email details and `batch-convert` creates one [[PurchaseRequest]] per seller/template group.
- [[Seller Offer Flow]] — sellers respond to the published request.
- [[Negotiation Flow]] — counter-offer mechanics in `in_negotiation`.
- [[PRD - Request Network In-House Checkout]] — buyer pays for the accepted offer.

View File

@@ -69,6 +69,14 @@ Either path requires:
> [!warning]
> Your payout address is the **single source of funds out of escrow**. Triple-check it. If it's wrong, funds can be irretrievably lost.
### 2.5 Payment methods
**Shop Settings → Payments** controls the default payment rails for templates that inherit shop settings:
- Choose at least one supported network, such as Ethereum or BSC.
- Choose at least one supported token, such as USDT or USDC.
- Individual request templates can override these defaults with their own accepted network/token list.
---
## 3. Request Templates
@@ -83,15 +91,19 @@ Templates are pre-defined product/service offerings. Buyers can create a request
2. **Category** — primary category.
3. **Description** — rich text, use images.
4. **Pricing** — fixed price or "starts at" range. Specify currency.
5. **Delivery window**typical days from acceptance.
6. **Customisations** — list of options (size, color, quantity) buyers can choose.
7. **Videos** (optional) — embed up to N video URLs.
8. **Default proposal** — your standard offer text that auto-populates when a buyer creates from this template.
9. **Expiration** — leave blank for evergreen; set a date for limited-time offers.
10. **Visibility** — public (anyone can use) or unlisted (shareable URL only).
5. **Delivery method**choose either physical delivery or online/email delivery. Buyers cannot override this at checkout.
6. **Payment methods** — choose at least one network and one token for the template, or explicitly inherit shop defaults.
7. **Delivery window** — typical days from acceptance.
8. **Customisations** — list of options (size, color, quantity) buyers can choose.
9. **Videos** (optional) — embed up to N video URLs.
10. **Default proposal** — your standard offer text that auto-populates when a buyer creates from this template.
11. **Expiration** — leave blank for evergreen; set a date for limited-time offers.
12. **Visibility** — public (anyone can use) or unlisted (shareable URL only).
Click **Publish**. You'll get a shareable URL: `https://amn.gg/shop/{seller}/{templateId}`.
For physical templates, checkout asks the buyer for a delivery/billing address. For online templates, checkout asks the buyer for the email that should receive the digital item.
### 3.2 Manage templates
**Dashboard → Request Templates** shows all templates with:

View File

@@ -11,6 +11,18 @@ entries on top. Maintained by agents per the rule in `../AGENTS.md`.
---
### 2026-05-31 — backend@cbc32dc, frontend@08e8da9 — seller-owned template delivery and payment rails
**Commits:** backend `cbc32dc`, frontend `08e8da9` (backend `2.6.83`, frontend `2.7.23`)
**Touched:**
- Backend: `src/models/RequestTemplate.ts`, `src/services/marketplace/RequestTemplateService.ts`, `src/services/marketplace/requestTemplateRoutes.ts`, `src/services/marketplace/requestTemplateController.ts`, `__tests__/marketplace-request-budget-validation.test.ts`, `scripts/smoke/marketplace-request-budget.sh`, `package.json`, `package-lock.json`
- Frontend: `src/sections/request-template/request-template-checkout-billing-address.tsx`, `src/sections/request-template/request-template-checkout-payment.tsx`, `src/sections/request-template/request-template-new-edit-form.tsx`, `src/sections/request-template/request-template-details-summary.tsx`, `src/sections/request-template/view/seller-shop-view.tsx`, `src/sections/request-template/view/public-seller-shop-view.tsx`, `src/web3/components/multi-seller-provider-payment.tsx`, `src/actions/request-template.ts`, `src/types/request-template.ts`, `src/sections/request-template/context/types.ts`, `package.json`, `package-lock.json`
**Why:** Template checkout let buyers choose delivery even though fulfillment is a seller decision, and payment creation could fail when a seller/template had no usable network/token allowlist. Sellers now choose physical vs online delivery on the template, new templates require at least one chain/token rail, checkout asks buyers only for the needed address/email details, and template payment intents resolve rails with the real `templateId`.
**Verification:** Backend `npm test -- --runTestsByPath __tests__/marketplace-request-budget-validation.test.ts`; backend `npm run typecheck`; backend `git diff --check`; backend `BASE_URL=http://localhost:5001 ./scripts/smoke/marketplace-request-budget.sh` skipped with exit 77 because `ACCESS_TOKEN` and `CATEGORY_ID` were not set; frontend `npx tsc --noEmit --ignoreDeprecations 6.0`; frontend `git diff --check`. No browser verification was possible because the Browser tool was unavailable in this session.
**Linked docs updated:** [[RequestTemplate]], [[PurchaseRequest]], [[ShopSettings]], [[Marketplace API]], [[Payment API]], [[Purchase Request Flow]], [[Seller Guide]]
---
### 2026-05-31 — backend@a4d72df, frontend@07db9b0, scanner@ca62e7a — cap confirmations at per-chain acceptance floors
**Commits:** backend `a4d72df`, frontend `07db9b0`, scanner `ca62e7a` (backend `2.6.82`, frontend `2.7.22`, scanner `0.1.7`)