@unifiedcommerce/plugin-marketplace 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +479 -0
- package/dist/analytics-models.d.ts +13 -0
- package/dist/analytics-models.d.ts.map +1 -0
- package/dist/analytics-models.js +69 -0
- package/dist/hooks.d.ts +4 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +187 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +105 -0
- package/dist/mcp-tools.d.ts +21 -0
- package/dist/mcp-tools.d.ts.map +1 -0
- package/dist/mcp-tools.js +183 -0
- package/dist/routes/b2b.d.ts +9 -0
- package/dist/routes/b2b.d.ts.map +1 -0
- package/dist/routes/b2b.js +156 -0
- package/dist/routes/commission.d.ts +6 -0
- package/dist/routes/commission.d.ts.map +1 -0
- package/dist/routes/commission.js +85 -0
- package/dist/routes/disputes-returns-reviews.d.ts +10 -0
- package/dist/routes/disputes-returns-reviews.d.ts.map +1 -0
- package/dist/routes/disputes-returns-reviews.js +179 -0
- package/dist/routes/payouts.d.ts +6 -0
- package/dist/routes/payouts.d.ts.map +1 -0
- package/dist/routes/payouts.js +40 -0
- package/dist/routes/sub-orders.d.ts +6 -0
- package/dist/routes/sub-orders.d.ts.map +1 -0
- package/dist/routes/sub-orders.js +44 -0
- package/dist/routes/util.d.ts +23 -0
- package/dist/routes/util.d.ts.map +1 -0
- package/dist/routes/util.js +41 -0
- package/dist/routes/vendor-portal.d.ts +14 -0
- package/dist/routes/vendor-portal.d.ts.map +1 -0
- package/dist/routes/vendor-portal.js +255 -0
- package/dist/routes/vendors.d.ts +11 -0
- package/dist/routes/vendors.d.ts.map +1 -0
- package/dist/routes/vendors.js +185 -0
- package/dist/schema.d.ts +3255 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +225 -0
- package/dist/schemas/b2b.d.ts +1009 -0
- package/dist/schemas/b2b.d.ts.map +1 -0
- package/dist/schemas/b2b.js +208 -0
- package/dist/schemas/commission.d.ts +532 -0
- package/dist/schemas/commission.d.ts.map +1 -0
- package/dist/schemas/commission.js +113 -0
- package/dist/schemas/disputes-returns-reviews.d.ts +1405 -0
- package/dist/schemas/disputes-returns-reviews.d.ts.map +1 -0
- package/dist/schemas/disputes-returns-reviews.js +270 -0
- package/dist/schemas/payouts.d.ts +375 -0
- package/dist/schemas/payouts.d.ts.map +1 -0
- package/dist/schemas/payouts.js +78 -0
- package/dist/schemas/sub-orders.d.ts +303 -0
- package/dist/schemas/sub-orders.d.ts.map +1 -0
- package/dist/schemas/sub-orders.js +67 -0
- package/dist/schemas/vendor-portal.d.ts +1785 -0
- package/dist/schemas/vendor-portal.d.ts.map +1 -0
- package/dist/schemas/vendor-portal.js +294 -0
- package/dist/schemas/vendors.d.ts +1348 -0
- package/dist/schemas/vendors.d.ts.map +1 -0
- package/dist/schemas/vendors.js +245 -0
- package/dist/services/commission.d.ts +81 -0
- package/dist/services/commission.d.ts.map +1 -0
- package/dist/services/commission.js +98 -0
- package/dist/services/contract-price.d.ts +64 -0
- package/dist/services/contract-price.d.ts.map +1 -0
- package/dist/services/contract-price.js +57 -0
- package/dist/services/dispute.d.ts +156 -0
- package/dist/services/dispute.d.ts.map +1 -0
- package/dist/services/dispute.js +77 -0
- package/dist/services/payout.d.ts +126 -0
- package/dist/services/payout.d.ts.map +1 -0
- package/dist/services/payout.js +130 -0
- package/dist/services/return.d.ts +181 -0
- package/dist/services/return.d.ts.map +1 -0
- package/dist/services/return.js +80 -0
- package/dist/services/review.d.ts +70 -0
- package/dist/services/review.d.ts.map +1 -0
- package/dist/services/review.js +60 -0
- package/dist/services/rfq.d.ts +122 -0
- package/dist/services/rfq.d.ts.map +1 -0
- package/dist/services/rfq.js +60 -0
- package/dist/services/sub-order.d.ts +336 -0
- package/dist/services/sub-order.d.ts.map +1 -0
- package/dist/services/sub-order.js +121 -0
- package/dist/services/vendor.d.ts +528 -0
- package/dist/services/vendor.d.ts.map +1 -0
- package/dist/services/vendor.js +119 -0
- package/dist/types.d.ts +67 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/package.json +43 -0
- package/src/analytics-models.ts +75 -0
- package/src/hooks.ts +215 -0
- package/src/index.ts +124 -0
- package/src/mcp-tools.ts +210 -0
- package/src/routes/b2b.ts +179 -0
- package/src/routes/commission.ts +95 -0
- package/src/routes/disputes-returns-reviews.ts +209 -0
- package/src/routes/payouts.ts +49 -0
- package/src/routes/sub-orders.ts +54 -0
- package/src/routes/util.ts +42 -0
- package/src/routes/vendor-portal.ts +277 -0
- package/src/routes/vendors.ts +201 -0
- package/src/schema.ts +260 -0
- package/src/schemas/b2b.ts +238 -0
- package/src/schemas/commission.ts +129 -0
- package/src/schemas/disputes-returns-reviews.ts +311 -0
- package/src/schemas/payouts.ts +90 -0
- package/src/schemas/sub-orders.ts +77 -0
- package/src/schemas/vendor-portal.ts +344 -0
- package/src/schemas/vendors.ts +281 -0
- package/src/services/commission.ts +120 -0
- package/src/services/contract-price.ts +80 -0
- package/src/services/dispute.ts +92 -0
- package/src/services/payout.ts +154 -0
- package/src/services/return.ts +92 -0
- package/src/services/review.ts +76 -0
- package/src/services/rfq.ts +82 -0
- package/src/services/sub-order.ts +136 -0
- package/src/services/vendor.ts +151 -0
- package/src/types.ts +164 -0
package/README.md
ADDED
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
# @unifiedcommerce/plugin-marketplace
|
|
2
|
+
|
|
3
|
+
A production-grade, headless multi-vendor marketplace plugin for the UnifiedCommerce Engine. This plugin transforms a single-tenant storefront into a fully operational marketplace where multiple vendors sell through a unified platform — handling everything from vendor onboarding and commission calculations through to dispute resolution and vendor payouts.
|
|
4
|
+
|
|
5
|
+
It is designed for both **B2C marketplaces** (think Amazon, Etsy, Zalando) and **B2B marketplaces** (think Alibaba, Faire, ThomasNet) — the B2B capabilities (RFQ, contract pricing) are opt-in and activate only when you need them.
|
|
6
|
+
|
|
7
|
+
## Philosophy
|
|
8
|
+
|
|
9
|
+
This plugin was built around five principles that informed every design decision:
|
|
10
|
+
|
|
11
|
+
### 1. Headless-first
|
|
12
|
+
|
|
13
|
+
Every single capability is a REST endpoint. There are no server-rendered views, no admin panels baked in, no assumptions about your frontend stack. Whether you're building with Next.js, Nuxt, Flutter, or a custom React Native app, the marketplace is just an API. Your frontend calls endpoints, receives JSON, and renders however you see fit.
|
|
14
|
+
|
|
15
|
+
This means you can build a vendor dashboard in React, a customer-facing marketplace in Svelte, and an admin panel in Vue — all consuming the same API surface. The plugin doesn't care.
|
|
16
|
+
|
|
17
|
+
### 2. Progressive complexity
|
|
18
|
+
|
|
19
|
+
A simple B2C marketplace should be simple to set up. You shouldn't have to understand RFQ workflows or contract pricing tiers just to let vendors list products and take orders.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// This is a complete, working marketplace:
|
|
23
|
+
import { marketplacePlugin } from "@unifiedcommerce/plugin-marketplace";
|
|
24
|
+
|
|
25
|
+
export default defineConfig({
|
|
26
|
+
plugins: [marketplacePlugin()],
|
|
27
|
+
// ... rest of your config
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
That's it. You get vendor registration, approval, product listing, order splitting, commission calculation, and payouts — all with sensible defaults (10% commission, weekly payouts, 7-day holdback, manual approval).
|
|
32
|
+
|
|
33
|
+
When you need more, you layer it on:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
marketplacePlugin({
|
|
37
|
+
defaultCommissionRateBps: 1500, // 15%
|
|
38
|
+
defaultPayoutSchedule: "biweekly",
|
|
39
|
+
defaultHoldbackDays: 14,
|
|
40
|
+
vendorApprovalMode: "auto",
|
|
41
|
+
requireVerifiedPurchase: true,
|
|
42
|
+
reviewModerationEnabled: true,
|
|
43
|
+
|
|
44
|
+
// B2B features — only activate when you need them
|
|
45
|
+
b2b: {
|
|
46
|
+
rfq: true,
|
|
47
|
+
contractPricing: true,
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
performanceThresholds: {
|
|
51
|
+
minRating: 3.5,
|
|
52
|
+
maxDefectRatePercent: 3,
|
|
53
|
+
maxLateShipmentRatePercent: 8,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. Convention over configuration
|
|
59
|
+
|
|
60
|
+
Every option has a default that makes sense for most marketplaces:
|
|
61
|
+
|
|
62
|
+
| Setting | Default | Why |
|
|
63
|
+
|---------|---------|-----|
|
|
64
|
+
| Commission rate | 10% (1000 bps) | Industry standard for general merchandise |
|
|
65
|
+
| Payout schedule | Weekly | Balances vendor cash flow with platform risk |
|
|
66
|
+
| Holdback period | 7 days | Covers most return windows |
|
|
67
|
+
| Payout minimum | $50 (5000 cents) | Avoids micro-transfers and processing fees |
|
|
68
|
+
| Vendor approval | Manual | Platform quality control from day one |
|
|
69
|
+
| Dispute deadline | 3 days | Vendors must respond within 72 hours |
|
|
70
|
+
| Return window | 30 days | Consumer protection standard |
|
|
71
|
+
|
|
72
|
+
You only override what you need. If a default works for you, you never have to think about it.
|
|
73
|
+
|
|
74
|
+
### 4. Event-driven
|
|
75
|
+
|
|
76
|
+
Every state transition in the system — vendor approved, sub-order shipped, payout completed, dispute escalated — can trigger external reactions. The plugin integrates with the core webhook system so external services can listen for events like `vendor.approved`, `suborder.shipped`, `payout.completed`, and `dispute.resolved`.
|
|
77
|
+
|
|
78
|
+
This is how you wire up email notifications, Slack alerts, ERP syncs, or any custom integration without touching plugin code.
|
|
79
|
+
|
|
80
|
+
### 5. Vendor-scoped by default
|
|
81
|
+
|
|
82
|
+
The vendor portal routes (`/api/marketplace/vendor/me/*`) automatically scope every query to the authenticated vendor's ID. A vendor calling `GET /api/marketplace/vendor/me/orders` will only ever see their own sub-orders. They cannot see another vendor's products, revenue, balance, or customer data. This isn't a feature you enable — it's how the system works.
|
|
83
|
+
|
|
84
|
+
The scoping is enforced at the route level by reading `actor.vendorId` from the authentication context. If a request hits a vendor portal endpoint without a `vendorId` on the actor, it gets a 403 — no data leaks, no configuration required.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## How It Works
|
|
89
|
+
|
|
90
|
+
### The Order Lifecycle
|
|
91
|
+
|
|
92
|
+
When a customer places an order containing items from multiple vendors, the marketplace plugin intercepts the `orders.afterCreate` hook and does the following:
|
|
93
|
+
|
|
94
|
+
1. **Groups line items by vendor** — Each item is traced back to its vendor through the `vendor_entities` linking table.
|
|
95
|
+
|
|
96
|
+
2. **Calculates commission per vendor** — The commission rules engine evaluates rules in priority order: vendor+category specific rules first, then category rules, vendor tier rules, volume tier rules, promotional rules, the vendor's flat rate, and finally the plugin default.
|
|
97
|
+
|
|
98
|
+
3. **Creates sub-orders** — Each vendor gets a separate sub-order with their line items, subtotal, commission amount, and payout amount.
|
|
99
|
+
|
|
100
|
+
4. **Credits the vendor balance** — Two ledger entries are created: a sale credit (the payout amount) and a commission debit. The vendor's running balance updates immediately.
|
|
101
|
+
|
|
102
|
+
From there, each vendor manages their own sub-order through the fulfillment lifecycle:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
pending → confirmed → processing → shipped → delivered
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The parent order can only transition to "fulfilled" when **all** vendor sub-orders are delivered. This is enforced by the `orders.beforeStatusChange` hook — the platform cannot prematurely mark an order complete.
|
|
109
|
+
|
|
110
|
+
### The Commission Rules Engine
|
|
111
|
+
|
|
112
|
+
The flat "X% on everything" model works for simple marketplaces, but real platforms need differentiated rates. The commission engine supports five rule types, evaluated in priority order:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
1. Vendor + category specific (highest priority)
|
|
116
|
+
2. Category-wide
|
|
117
|
+
3. Vendor performance tier
|
|
118
|
+
4. Sales volume tier
|
|
119
|
+
5. Promotional (time-limited)
|
|
120
|
+
6. Vendor flat rate (fallback)
|
|
121
|
+
7. Plugin default (last resort)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
This means you can express policies like:
|
|
125
|
+
- "Electronics are 8% commission, fashion is 20%"
|
|
126
|
+
- "Gold-tier vendors get a 2% discount on any rate"
|
|
127
|
+
- "New vendors pay only 5% for their first 90 days"
|
|
128
|
+
- "Vendor X has a negotiated 12% rate on electronics specifically"
|
|
129
|
+
|
|
130
|
+
Rules are managed through the admin API:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Create a category-based commission rule
|
|
134
|
+
curl -X POST /api/marketplace/commission-rules \
|
|
135
|
+
-d '{"name": "Fashion 20%", "type": "category", "categorySlug": "fashion", "rateBps": 2000}'
|
|
136
|
+
|
|
137
|
+
# Preview what rate a vendor would pay
|
|
138
|
+
curl -X POST /api/marketplace/commission-rules/preview \
|
|
139
|
+
-d '{"vendorId": "...", "categorySlug": "electronics"}'
|
|
140
|
+
# → {"rateBps": 800, "ratePercent": 8}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### The Financial Ledger
|
|
144
|
+
|
|
145
|
+
Every financial event — sale, commission deduction, refund, payout — creates an entry in the vendor balance ledger (`marketplace_vendor_balances`). Each entry records the amount, running balance, and a reference to the entity that triggered it (sub-order, payout, refund).
|
|
146
|
+
|
|
147
|
+
This gives you a complete, auditable financial history per vendor:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
+$85.00 sale sub_order/abc-123 "Sale from order 7f3a..."
|
|
151
|
+
-$15.00 commission sub_order/abc-123 "Commission (1500bps) on order 7f3a..."
|
|
152
|
+
-$70.00 payout payout/def-456 "Payout #def45678"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
When you run a payout cycle (`POST /api/marketplace/payouts/run`), the system:
|
|
156
|
+
1. Finds vendors whose balance exceeds their minimum payout threshold
|
|
157
|
+
2. Checks that eligible sales are past the holdback period
|
|
158
|
+
3. Calculates deductions (refunds, adjustments)
|
|
159
|
+
4. Creates a payout record with gross/deductions/net breakdown
|
|
160
|
+
5. Debits the vendor balance via a ledger entry
|
|
161
|
+
6. Returns the payout details for your payment provider integration
|
|
162
|
+
|
|
163
|
+
### Dispute Resolution
|
|
164
|
+
|
|
165
|
+
Disputes follow a three-stage process:
|
|
166
|
+
|
|
167
|
+
**Stage 1: Vendor Response** — Customer opens a dispute. The vendor gets a deadline (default: 3 days) to respond with evidence. If they miss the deadline, the dispute auto-escalates.
|
|
168
|
+
|
|
169
|
+
**Stage 2: Platform Review** — Both parties have submitted their evidence. The platform admin reviews and makes a binding decision: full refund, partial refund, replacement, or rejection.
|
|
170
|
+
|
|
171
|
+
**Stage 3: Escalation** — For complex cases, disputes can be manually escalated for specialized review. All actions, messages, and evidence are logged in the `evidence` JSONB array for a complete audit trail.
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# Customer opens a dispute
|
|
175
|
+
curl -X POST /api/marketplace/disputes \
|
|
176
|
+
-d '{"subOrderId": "...", "openedBy": "customer", "reason": "item_not_as_described", "description": "..."}'
|
|
177
|
+
|
|
178
|
+
# Vendor responds with evidence
|
|
179
|
+
curl -X POST /api/marketplace/disputes/:id/respond \
|
|
180
|
+
-d '{"party": "vendor", "note": "Item matches listing photos", "url": "https://..."}'
|
|
181
|
+
|
|
182
|
+
# Platform resolves
|
|
183
|
+
curl -X POST /api/marketplace/disputes/:id/resolve \
|
|
184
|
+
-d '{"resolution": "refund_partial", "refundAmountCents": 2500, "resolvedBy": "platform", "notes": "..."}'
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Quick Start
|
|
190
|
+
|
|
191
|
+
### 1. Install
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
bun add @unifiedcommerce/plugin-marketplace
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### 2. Add to your config
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import { defineConfig } from "@unifiedcommerce/core";
|
|
201
|
+
import { marketplacePlugin } from "@unifiedcommerce/plugin-marketplace";
|
|
202
|
+
|
|
203
|
+
export default defineConfig({
|
|
204
|
+
database: { provider: "postgresql" },
|
|
205
|
+
plugins: [
|
|
206
|
+
marketplacePlugin({
|
|
207
|
+
defaultCommissionRateBps: 1200, // 12%
|
|
208
|
+
}),
|
|
209
|
+
],
|
|
210
|
+
// ...
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### 3. Push the schema
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
bunx drizzle-kit push
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 4. Register a vendor
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
curl -X POST http://localhost:3000/api/marketplace/vendors \
|
|
224
|
+
-H "Content-Type: application/json" \
|
|
225
|
+
-d '{"name": "Acme Electronics", "email": "vendor@acme.com"}'
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### 5. Approve the vendor
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
curl -X POST http://localhost:3000/api/marketplace/vendors/:vendorId/approve
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### 6. Create a product with vendor metadata
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
curl -X POST http://localhost:3000/api/catalog/product \
|
|
238
|
+
-H "Content-Type: application/json" \
|
|
239
|
+
-d '{
|
|
240
|
+
"slug": "wireless-headphones",
|
|
241
|
+
"attributes": {"title": "Wireless Headphones"},
|
|
242
|
+
"metadata": {"vendorId": "<vendorId>", "basePrice": 4999}
|
|
243
|
+
}'
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The `catalog.afterCreate` hook automatically links the product to the vendor.
|
|
247
|
+
|
|
248
|
+
### 7. Place an order
|
|
249
|
+
|
|
250
|
+
When a customer checks out, the `orders.afterCreate` hook:
|
|
251
|
+
- Creates vendor sub-orders
|
|
252
|
+
- Calculates commission via the rules engine
|
|
253
|
+
- Credits the vendor balance ledger
|
|
254
|
+
|
|
255
|
+
### 8. Vendor fulfills their sub-order
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
# Vendor confirms
|
|
259
|
+
curl -X POST /api/marketplace/vendor/me/orders/:subOrderId/confirm
|
|
260
|
+
|
|
261
|
+
# Vendor ships with tracking
|
|
262
|
+
curl -X POST /api/marketplace/vendor/me/orders/:subOrderId/ship \
|
|
263
|
+
-d '{"trackingNumber": "1Z999AA10123456784", "carrier": "UPS"}'
|
|
264
|
+
|
|
265
|
+
# Vendor marks delivered
|
|
266
|
+
curl -X POST /api/marketplace/vendor/me/orders/:subOrderId/deliver
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### 9. Run payouts
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
curl -X POST /api/marketplace/payouts/run
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## API Reference
|
|
278
|
+
|
|
279
|
+
### Vendor Management (Platform Admin)
|
|
280
|
+
|
|
281
|
+
| Method | Endpoint | Description |
|
|
282
|
+
|--------|----------|-------------|
|
|
283
|
+
| GET | `/api/marketplace/vendors` | List vendors (filters: `?status`, `?tier`, `?search`) |
|
|
284
|
+
| POST | `/api/marketplace/vendors` | Register vendor |
|
|
285
|
+
| GET | `/api/marketplace/vendors/:id` | Get vendor detail |
|
|
286
|
+
| PATCH | `/api/marketplace/vendors/:id` | Update vendor |
|
|
287
|
+
| POST | `/api/marketplace/vendors/:id/approve` | Approve vendor |
|
|
288
|
+
| POST | `/api/marketplace/vendors/:id/reject` | Reject (body: `{reason}`) |
|
|
289
|
+
| POST | `/api/marketplace/vendors/:id/suspend` | Suspend (body: `{reason}`) |
|
|
290
|
+
| POST | `/api/marketplace/vendors/:id/reinstate` | Reinstate suspended vendor |
|
|
291
|
+
| GET | `/api/marketplace/vendors/:id/documents` | List verification documents |
|
|
292
|
+
| POST | `/api/marketplace/vendors/:id/documents/:docId/approve` | Approve document |
|
|
293
|
+
| POST | `/api/marketplace/vendors/:id/documents/:docId/reject` | Reject document |
|
|
294
|
+
| GET | `/api/marketplace/vendors/:id/balance` | Vendor balance ledger |
|
|
295
|
+
| GET | `/api/marketplace/vendors/:id/performance` | Performance metrics + rating |
|
|
296
|
+
|
|
297
|
+
### Vendor Portal (Self-Service)
|
|
298
|
+
|
|
299
|
+
All endpoints are scoped to the authenticated vendor via `actor.vendorId`. Returns 403 if the actor has no vendor association.
|
|
300
|
+
|
|
301
|
+
| Method | Endpoint | Description |
|
|
302
|
+
|--------|----------|-------------|
|
|
303
|
+
| GET | `/api/marketplace/vendor/me` | Current vendor profile |
|
|
304
|
+
| PATCH | `/api/marketplace/vendor/me` | Update own profile |
|
|
305
|
+
| POST | `/api/marketplace/vendor/me/documents` | Upload verification document |
|
|
306
|
+
| GET | `/api/marketplace/vendor/me/documents` | List own documents |
|
|
307
|
+
| GET | `/api/marketplace/vendor/me/products` | List own products |
|
|
308
|
+
| GET | `/api/marketplace/vendor/me/orders` | List own sub-orders |
|
|
309
|
+
| GET | `/api/marketplace/vendor/me/orders/:id` | Sub-order detail |
|
|
310
|
+
| POST | `/api/marketplace/vendor/me/orders/:id/confirm` | Confirm sub-order |
|
|
311
|
+
| POST | `/api/marketplace/vendor/me/orders/:id/ship` | Ship (body: `{trackingNumber, carrier}`) |
|
|
312
|
+
| POST | `/api/marketplace/vendor/me/orders/:id/deliver` | Mark delivered |
|
|
313
|
+
| POST | `/api/marketplace/vendor/me/orders/:id/cancel` | Cancel (body: `{reason}`) |
|
|
314
|
+
| GET | `/api/marketplace/vendor/me/payouts` | Payout history |
|
|
315
|
+
| GET | `/api/marketplace/vendor/me/balance` | Balance ledger |
|
|
316
|
+
| GET | `/api/marketplace/vendor/me/analytics` | Sales summary + ratings |
|
|
317
|
+
| GET | `/api/marketplace/vendor/me/reviews` | Reviews received |
|
|
318
|
+
| POST | `/api/marketplace/vendor/me/reviews/:id/respond` | Respond to a review |
|
|
319
|
+
| GET | `/api/marketplace/vendor/me/returns` | Return requests |
|
|
320
|
+
| POST | `/api/marketplace/vendor/me/returns/:id/approve` | Approve return |
|
|
321
|
+
| POST | `/api/marketplace/vendor/me/returns/:id/reject` | Reject return |
|
|
322
|
+
|
|
323
|
+
### Sub-Orders, Commissions, Payouts, Disputes, Returns, Reviews
|
|
324
|
+
|
|
325
|
+
| Group | Endpoints | Count |
|
|
326
|
+
|-------|-----------|-------|
|
|
327
|
+
| Sub-Orders (admin) | List, detail, force status | 3 |
|
|
328
|
+
| Commission Rules | CRUD + preview | 5 |
|
|
329
|
+
| Payouts | List, run cycle, retry, detail | 4 |
|
|
330
|
+
| Disputes | Open, list, detail, respond, escalate, resolve | 6 |
|
|
331
|
+
| Returns | Request, list, detail, ship-back, receive | 5 |
|
|
332
|
+
| Reviews | Submit, list, moderate | 3 |
|
|
333
|
+
|
|
334
|
+
### B2B (opt-in)
|
|
335
|
+
|
|
336
|
+
| Group | Endpoints | Enabled by |
|
|
337
|
+
|-------|-----------|------------|
|
|
338
|
+
| RFQ | Create, list, detail, respond, award, close | `b2b.rfq: true` |
|
|
339
|
+
| Contract Pricing | CRUD | `b2b.contractPricing: true` |
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Plugin Options
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
interface MarketplacePluginOptions {
|
|
347
|
+
// ── Commission ──────────────────────────────────────────────
|
|
348
|
+
defaultCommissionRateBps?: number; // Default: 1000 (10%)
|
|
349
|
+
|
|
350
|
+
// ── Vendor Onboarding ───────────────────────────────────────
|
|
351
|
+
vendorApprovalMode?: "manual" | "auto" | "invitation";
|
|
352
|
+
requiredDocuments?: Array<"business_license" | "tax_form" | "bank_proof" | "identity">;
|
|
353
|
+
|
|
354
|
+
// ── Payouts ─────────────────────────────────────────────────
|
|
355
|
+
defaultPayoutSchedule?: "daily" | "weekly" | "biweekly" | "monthly" | "manual";
|
|
356
|
+
defaultPayoutMinimumCents?: number; // Default: 5000 ($50)
|
|
357
|
+
defaultHoldbackDays?: number; // Default: 7
|
|
358
|
+
|
|
359
|
+
// ── Disputes ────────────────────────────────────────────────
|
|
360
|
+
vendorResponseDeadlineDays?: number; // Default: 3
|
|
361
|
+
autoEscalateOnMissedDeadline?: boolean; // Default: true
|
|
362
|
+
|
|
363
|
+
// ── Returns ─────────────────────────────────────────────────
|
|
364
|
+
returnWindowDays?: number; // Default: 30
|
|
365
|
+
autoApproveReturnsOnVendorTimeout?: boolean;
|
|
366
|
+
vendorReturnResponseDays?: number; // Default: 5
|
|
367
|
+
|
|
368
|
+
// ── Reviews ─────────────────────────────────────────────────
|
|
369
|
+
requireVerifiedPurchase?: boolean; // Default: true
|
|
370
|
+
reviewModerationEnabled?: boolean; // Default: false
|
|
371
|
+
|
|
372
|
+
// ── B2B (opt-in) ───────────────────────────────────────────
|
|
373
|
+
b2b?: {
|
|
374
|
+
rfq?: boolean; // Default: false
|
|
375
|
+
contractPricing?: boolean; // Default: false
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// ── Performance Enforcement ─────────────────────────────────
|
|
379
|
+
performanceThresholds?: {
|
|
380
|
+
minRating?: number; // Default: 3.0
|
|
381
|
+
maxDefectRatePercent?: number; // Default: 5
|
|
382
|
+
maxLateShipmentRatePercent?: number; // Default: 10
|
|
383
|
+
maxCancellationRatePercent?: number; // Default: 5
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Schema
|
|
391
|
+
|
|
392
|
+
The plugin manages 13 PostgreSQL tables:
|
|
393
|
+
|
|
394
|
+
| Table | Purpose |
|
|
395
|
+
|-------|---------|
|
|
396
|
+
| `marketplace_vendors` | Vendor profiles, status, tier, financial settings |
|
|
397
|
+
| `marketplace_vendor_entities` | Links vendors to catalog entities (products) |
|
|
398
|
+
| `marketplace_vendor_documents` | KYC/verification document uploads |
|
|
399
|
+
| `marketplace_commission_rules` | Category, tier, volume, and promotional commission rules |
|
|
400
|
+
| `marketplace_vendor_sub_orders` | Per-vendor order segments with fulfillment tracking |
|
|
401
|
+
| `marketplace_vendor_payouts` | Payout records with gross/deductions/net breakdown |
|
|
402
|
+
| `marketplace_vendor_balances` | Append-only financial ledger per vendor |
|
|
403
|
+
| `marketplace_disputes` | Dispute lifecycle with evidence trail |
|
|
404
|
+
| `marketplace_vendor_reviews` | Vendor ratings and reviews |
|
|
405
|
+
| `marketplace_return_requests` | Per-sub-order return/RMA requests |
|
|
406
|
+
| `marketplace_rfq` | Request for Quote (B2B) |
|
|
407
|
+
| `marketplace_rfq_responses` | Vendor bids on RFQs (B2B) |
|
|
408
|
+
| `marketplace_contract_prices` | Negotiated pricing per buyer/vendor/product (B2B) |
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## Architecture
|
|
413
|
+
|
|
414
|
+
```
|
|
415
|
+
plugin-marketplace/
|
|
416
|
+
src/
|
|
417
|
+
index.ts ← Plugin entrypoint, wires services/routes/hooks
|
|
418
|
+
types.ts ← TypeScript types, state machines, options interface
|
|
419
|
+
schema.ts ← All 13 Drizzle pgTable definitions
|
|
420
|
+
hooks.ts ← Catalog + order lifecycle hooks
|
|
421
|
+
mcp-tools.ts ← 8 AI agent tools
|
|
422
|
+
services/
|
|
423
|
+
vendor.ts ← Vendor CRUD, onboarding, documents
|
|
424
|
+
sub-order.ts ← Sub-order state machine
|
|
425
|
+
commission.ts ← Priority-based commission rules engine
|
|
426
|
+
payout.ts ← Balance ledger + payout scheduling
|
|
427
|
+
dispute.ts ← Dispute lifecycle
|
|
428
|
+
return.ts ← Return request lifecycle
|
|
429
|
+
review.ts ← Vendor reviews + aggregate ratings
|
|
430
|
+
rfq.ts ← RFQ lifecycle (B2B)
|
|
431
|
+
contract-price.ts ← Negotiated pricing (B2B)
|
|
432
|
+
routes/
|
|
433
|
+
vendors.ts ← Platform admin vendor management
|
|
434
|
+
vendor-portal.ts ← Vendor self-service (scoped by actor.vendorId)
|
|
435
|
+
sub-orders.ts ← Sub-order admin
|
|
436
|
+
commission.ts ← Commission rules CRUD
|
|
437
|
+
payouts.ts ← Payout management
|
|
438
|
+
disputes-returns-reviews.ts ← Trust & safety
|
|
439
|
+
b2b.ts ← RFQ + contract pricing (conditional)
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Type Safety
|
|
443
|
+
|
|
444
|
+
The plugin uses `PgDatabase<PgQueryResultHKT>` from `drizzle-orm/pg-core` — the driver-agnostic base type that both `PostgresJsDatabase` (production) and `PgliteDatabase` (tests) extend. This means:
|
|
445
|
+
|
|
446
|
+
- Row types are fully inferred from `pgTable` schema definitions
|
|
447
|
+
- No coupling to any specific PostgreSQL driver
|
|
448
|
+
- Zero `as any` casts in the entire codebase
|
|
449
|
+
- All catch blocks use `err: unknown` with proper type narrowing
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## MCP Tools
|
|
454
|
+
|
|
455
|
+
The plugin exposes 8 tools for AI agent integration:
|
|
456
|
+
|
|
457
|
+
| Tool | Description |
|
|
458
|
+
|------|-------------|
|
|
459
|
+
| `marketplace_vendor_list` | List vendors with filters |
|
|
460
|
+
| `marketplace_vendor_performance` | Get vendor metrics, tier, and rating |
|
|
461
|
+
| `marketplace_vendor_balance` | Get balance and recent ledger entries |
|
|
462
|
+
| `marketplace_suborder_update` | Transition sub-order status |
|
|
463
|
+
| `marketplace_dispute_summary` | List open disputes with deadlines |
|
|
464
|
+
| `marketplace_payout_run` | Trigger payout cycle |
|
|
465
|
+
| `marketplace_commission_preview` | Preview effective rate for vendor+category |
|
|
466
|
+
| `marketplace_rfq_list` | List open RFQs (B2B) |
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## What's Not Included (and Why)
|
|
471
|
+
|
|
472
|
+
These capabilities are intentionally outside the plugin's scope:
|
|
473
|
+
|
|
474
|
+
- **Split payment integration** (Stripe Connect, PayPal Marketplace) — Requires payment adapter extensions specific to your payment provider. The plugin calculates amounts; your adapter moves money.
|
|
475
|
+
- **Buy Box algorithm** — Requires a product-level multi-vendor offers table and ranking logic that varies wildly by marketplace type.
|
|
476
|
+
- **Fulfillment-by-platform** — Requires warehouse management beyond marketplace scope.
|
|
477
|
+
- **Vendor storefront theming** — Frontend concern. The plugin is headless.
|
|
478
|
+
- **Real-time inventory sync** — WebSocket infrastructure, separate concern.
|
|
479
|
+
- **ML recommendations** — External service integration.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AnalyticsModel } from "@unifiedcommerce/core";
|
|
2
|
+
/**
|
|
3
|
+
* Marketplace analytics models — SQL-based definitions for vendor-scoped analytics.
|
|
4
|
+
*
|
|
5
|
+
* These models describe the marketplace tables (marketplace_vendor_sub_orders,
|
|
6
|
+
* marketplace_vendor_balances, marketplace_vendor_reviews) and are registered
|
|
7
|
+
* on the DrizzleAnalyticsAdapter via the plugin's analyticsModels manifest slot.
|
|
8
|
+
*/
|
|
9
|
+
export declare const VENDOR_ORDERS_MODEL: AnalyticsModel;
|
|
10
|
+
export declare const VENDOR_BALANCE_MODEL: AnalyticsModel;
|
|
11
|
+
export declare const VENDOR_REVIEWS_MODEL: AnalyticsModel;
|
|
12
|
+
export declare const MARKETPLACE_ANALYTICS_MODELS: AnalyticsModel[];
|
|
13
|
+
//# sourceMappingURL=analytics-models.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics-models.d.ts","sourceRoot":"","sources":["../src/analytics-models.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D;;;;;;GAMG;AAEH,eAAO,MAAM,mBAAmB,EAAE,cAmBjC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,cAkBlC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,cAiBlC,CAAC;AAEF,eAAO,MAAM,4BAA4B,EAAE,cAAc,EAIxD,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marketplace analytics models — SQL-based definitions for vendor-scoped analytics.
|
|
3
|
+
*
|
|
4
|
+
* These models describe the marketplace tables (marketplace_vendor_sub_orders,
|
|
5
|
+
* marketplace_vendor_balances, marketplace_vendor_reviews) and are registered
|
|
6
|
+
* on the DrizzleAnalyticsAdapter via the plugin's analyticsModels manifest slot.
|
|
7
|
+
*/
|
|
8
|
+
export const VENDOR_ORDERS_MODEL = {
|
|
9
|
+
name: "VendorOrders",
|
|
10
|
+
table: "marketplace_vendor_sub_orders",
|
|
11
|
+
scopeRules: [
|
|
12
|
+
{ role: "vendor", filter: "vendor_id = :vendorId" },
|
|
13
|
+
],
|
|
14
|
+
measures: {
|
|
15
|
+
count: { type: "count" },
|
|
16
|
+
revenue: { sql: "subtotal", type: "sum" },
|
|
17
|
+
commissionPaid: { sql: "commission_amount", type: "sum" },
|
|
18
|
+
netPayout: { sql: "payout_amount", type: "sum" },
|
|
19
|
+
},
|
|
20
|
+
dimensions: {
|
|
21
|
+
id: { sql: "id", type: "string" },
|
|
22
|
+
vendorId: { sql: "vendor_id", type: "string" },
|
|
23
|
+
orderId: { sql: "order_id", type: "string" },
|
|
24
|
+
status: { sql: "status", type: "string" },
|
|
25
|
+
createdAt: { sql: "created_at", type: "time" },
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
export const VENDOR_BALANCE_MODEL = {
|
|
29
|
+
name: "VendorBalance",
|
|
30
|
+
table: "marketplace_vendor_balances",
|
|
31
|
+
scopeRules: [
|
|
32
|
+
{ role: "vendor", filter: "vendor_id = :vendorId" },
|
|
33
|
+
],
|
|
34
|
+
measures: {
|
|
35
|
+
totalCredits: { sql: "CASE WHEN amount_cents > 0 THEN amount_cents ELSE 0 END", type: "sum" },
|
|
36
|
+
totalDebits: { sql: "CASE WHEN amount_cents < 0 THEN ABS(amount_cents) ELSE 0 END", type: "sum" },
|
|
37
|
+
netBalance: { sql: "amount_cents", type: "sum" },
|
|
38
|
+
entryCount: { type: "count" },
|
|
39
|
+
},
|
|
40
|
+
dimensions: {
|
|
41
|
+
id: { sql: "id", type: "string" },
|
|
42
|
+
vendorId: { sql: "vendor_id", type: "string" },
|
|
43
|
+
type: { sql: "type", type: "string" },
|
|
44
|
+
createdAt: { sql: "created_at", type: "time" },
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
export const VENDOR_REVIEWS_MODEL = {
|
|
48
|
+
name: "VendorReviews",
|
|
49
|
+
table: "marketplace_vendor_reviews",
|
|
50
|
+
scopeRules: [
|
|
51
|
+
{ role: "vendor", filter: "vendor_id = :vendorId" },
|
|
52
|
+
],
|
|
53
|
+
measures: {
|
|
54
|
+
count: { type: "count" },
|
|
55
|
+
averageRating: { sql: "rating", type: "avg" },
|
|
56
|
+
},
|
|
57
|
+
dimensions: {
|
|
58
|
+
id: { sql: "id", type: "string" },
|
|
59
|
+
vendorId: { sql: "vendor_id", type: "string" },
|
|
60
|
+
rating: { sql: "rating", type: "number" },
|
|
61
|
+
status: { sql: "status", type: "string" },
|
|
62
|
+
createdAt: { sql: "created_at", type: "time" },
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
export const MARKETPLACE_ANALYTICS_MODELS = [
|
|
66
|
+
VENDOR_ORDERS_MODEL,
|
|
67
|
+
VENDOR_BALANCE_MODEL,
|
|
68
|
+
VENDOR_REVIEWS_MODEL,
|
|
69
|
+
];
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAIpE,OAAO,KAAK,EAAM,wBAAwB,EAAE,MAAM,SAAS,CAAC;AAO5D,wBAAgB,UAAU,CAAC,OAAO,EAAE,wBAAwB,GAAG,sBAAsB,EAAE,CA0MtF"}
|