@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.
Files changed (122) hide show
  1. package/README.md +479 -0
  2. package/dist/analytics-models.d.ts +13 -0
  3. package/dist/analytics-models.d.ts.map +1 -0
  4. package/dist/analytics-models.js +69 -0
  5. package/dist/hooks.d.ts +4 -0
  6. package/dist/hooks.d.ts.map +1 -0
  7. package/dist/hooks.js +187 -0
  8. package/dist/index.d.ts +4 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +105 -0
  11. package/dist/mcp-tools.d.ts +21 -0
  12. package/dist/mcp-tools.d.ts.map +1 -0
  13. package/dist/mcp-tools.js +183 -0
  14. package/dist/routes/b2b.d.ts +9 -0
  15. package/dist/routes/b2b.d.ts.map +1 -0
  16. package/dist/routes/b2b.js +156 -0
  17. package/dist/routes/commission.d.ts +6 -0
  18. package/dist/routes/commission.d.ts.map +1 -0
  19. package/dist/routes/commission.js +85 -0
  20. package/dist/routes/disputes-returns-reviews.d.ts +10 -0
  21. package/dist/routes/disputes-returns-reviews.d.ts.map +1 -0
  22. package/dist/routes/disputes-returns-reviews.js +179 -0
  23. package/dist/routes/payouts.d.ts +6 -0
  24. package/dist/routes/payouts.d.ts.map +1 -0
  25. package/dist/routes/payouts.js +40 -0
  26. package/dist/routes/sub-orders.d.ts +6 -0
  27. package/dist/routes/sub-orders.d.ts.map +1 -0
  28. package/dist/routes/sub-orders.js +44 -0
  29. package/dist/routes/util.d.ts +23 -0
  30. package/dist/routes/util.d.ts.map +1 -0
  31. package/dist/routes/util.js +41 -0
  32. package/dist/routes/vendor-portal.d.ts +14 -0
  33. package/dist/routes/vendor-portal.d.ts.map +1 -0
  34. package/dist/routes/vendor-portal.js +255 -0
  35. package/dist/routes/vendors.d.ts +11 -0
  36. package/dist/routes/vendors.d.ts.map +1 -0
  37. package/dist/routes/vendors.js +185 -0
  38. package/dist/schema.d.ts +3255 -0
  39. package/dist/schema.d.ts.map +1 -0
  40. package/dist/schema.js +225 -0
  41. package/dist/schemas/b2b.d.ts +1009 -0
  42. package/dist/schemas/b2b.d.ts.map +1 -0
  43. package/dist/schemas/b2b.js +208 -0
  44. package/dist/schemas/commission.d.ts +532 -0
  45. package/dist/schemas/commission.d.ts.map +1 -0
  46. package/dist/schemas/commission.js +113 -0
  47. package/dist/schemas/disputes-returns-reviews.d.ts +1405 -0
  48. package/dist/schemas/disputes-returns-reviews.d.ts.map +1 -0
  49. package/dist/schemas/disputes-returns-reviews.js +270 -0
  50. package/dist/schemas/payouts.d.ts +375 -0
  51. package/dist/schemas/payouts.d.ts.map +1 -0
  52. package/dist/schemas/payouts.js +78 -0
  53. package/dist/schemas/sub-orders.d.ts +303 -0
  54. package/dist/schemas/sub-orders.d.ts.map +1 -0
  55. package/dist/schemas/sub-orders.js +67 -0
  56. package/dist/schemas/vendor-portal.d.ts +1785 -0
  57. package/dist/schemas/vendor-portal.d.ts.map +1 -0
  58. package/dist/schemas/vendor-portal.js +294 -0
  59. package/dist/schemas/vendors.d.ts +1348 -0
  60. package/dist/schemas/vendors.d.ts.map +1 -0
  61. package/dist/schemas/vendors.js +245 -0
  62. package/dist/services/commission.d.ts +81 -0
  63. package/dist/services/commission.d.ts.map +1 -0
  64. package/dist/services/commission.js +98 -0
  65. package/dist/services/contract-price.d.ts +64 -0
  66. package/dist/services/contract-price.d.ts.map +1 -0
  67. package/dist/services/contract-price.js +57 -0
  68. package/dist/services/dispute.d.ts +156 -0
  69. package/dist/services/dispute.d.ts.map +1 -0
  70. package/dist/services/dispute.js +77 -0
  71. package/dist/services/payout.d.ts +126 -0
  72. package/dist/services/payout.d.ts.map +1 -0
  73. package/dist/services/payout.js +130 -0
  74. package/dist/services/return.d.ts +181 -0
  75. package/dist/services/return.d.ts.map +1 -0
  76. package/dist/services/return.js +80 -0
  77. package/dist/services/review.d.ts +70 -0
  78. package/dist/services/review.d.ts.map +1 -0
  79. package/dist/services/review.js +60 -0
  80. package/dist/services/rfq.d.ts +122 -0
  81. package/dist/services/rfq.d.ts.map +1 -0
  82. package/dist/services/rfq.js +60 -0
  83. package/dist/services/sub-order.d.ts +336 -0
  84. package/dist/services/sub-order.d.ts.map +1 -0
  85. package/dist/services/sub-order.js +121 -0
  86. package/dist/services/vendor.d.ts +528 -0
  87. package/dist/services/vendor.d.ts.map +1 -0
  88. package/dist/services/vendor.js +119 -0
  89. package/dist/types.d.ts +67 -0
  90. package/dist/types.d.ts.map +1 -0
  91. package/dist/types.js +13 -0
  92. package/package.json +43 -0
  93. package/src/analytics-models.ts +75 -0
  94. package/src/hooks.ts +215 -0
  95. package/src/index.ts +124 -0
  96. package/src/mcp-tools.ts +210 -0
  97. package/src/routes/b2b.ts +179 -0
  98. package/src/routes/commission.ts +95 -0
  99. package/src/routes/disputes-returns-reviews.ts +209 -0
  100. package/src/routes/payouts.ts +49 -0
  101. package/src/routes/sub-orders.ts +54 -0
  102. package/src/routes/util.ts +42 -0
  103. package/src/routes/vendor-portal.ts +277 -0
  104. package/src/routes/vendors.ts +201 -0
  105. package/src/schema.ts +260 -0
  106. package/src/schemas/b2b.ts +238 -0
  107. package/src/schemas/commission.ts +129 -0
  108. package/src/schemas/disputes-returns-reviews.ts +311 -0
  109. package/src/schemas/payouts.ts +90 -0
  110. package/src/schemas/sub-orders.ts +77 -0
  111. package/src/schemas/vendor-portal.ts +344 -0
  112. package/src/schemas/vendors.ts +281 -0
  113. package/src/services/commission.ts +120 -0
  114. package/src/services/contract-price.ts +80 -0
  115. package/src/services/dispute.ts +92 -0
  116. package/src/services/payout.ts +154 -0
  117. package/src/services/return.ts +92 -0
  118. package/src/services/review.ts +76 -0
  119. package/src/services/rfq.ts +82 -0
  120. package/src/services/sub-order.ts +136 -0
  121. package/src/services/vendor.ts +151 -0
  122. 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
+ ];
@@ -0,0 +1,4 @@
1
+ import type { PluginHookRegistration } from "@unifiedcommerce/core";
2
+ import type { MarketplacePluginOptions } from "./types";
3
+ export declare function buildHooks(options: MarketplacePluginOptions): PluginHookRegistration[];
4
+ //# sourceMappingURL=hooks.d.ts.map
@@ -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"}