@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
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { eq, and, desc } from "drizzle-orm";
|
|
2
|
+
import { vendorSubOrders } from "../schema";
|
|
3
|
+
import type { Db, SubOrderStatus } from "../types";
|
|
4
|
+
import { SUB_ORDER_TRANSITIONS } from "../types";
|
|
5
|
+
|
|
6
|
+
export interface SubOrderCancelCallback {
|
|
7
|
+
(subOrder: { id: string; orderId: string; vendorId: string; payoutAmount: number; lineItems: unknown }): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class SubOrderService {
|
|
11
|
+
private onCancel?: SubOrderCancelCallback | undefined;
|
|
12
|
+
|
|
13
|
+
constructor(private db: Db, onCancel?: SubOrderCancelCallback) {
|
|
14
|
+
this.onCancel = onCancel;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async getById(id: string) {
|
|
18
|
+
const [sub] = await this.db.select().from(vendorSubOrders).where(eq(vendorSubOrders.id, id));
|
|
19
|
+
return sub ?? null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async listByOrder(orderId: string) {
|
|
23
|
+
return this.db.select().from(vendorSubOrders)
|
|
24
|
+
.where(eq(vendorSubOrders.orderId, orderId))
|
|
25
|
+
.orderBy(desc(vendorSubOrders.createdAt));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async listByVendor(vendorId: string, filters?: { status?: string }) {
|
|
29
|
+
const conditions = [eq(vendorSubOrders.vendorId, vendorId)];
|
|
30
|
+
if (filters?.status) conditions.push(eq(vendorSubOrders.status, filters.status));
|
|
31
|
+
|
|
32
|
+
return this.db.select().from(vendorSubOrders)
|
|
33
|
+
.where(conditions.length === 1 ? conditions[0]! : and(...conditions))
|
|
34
|
+
.orderBy(desc(vendorSubOrders.createdAt));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async list(filters?: { orderId?: string; vendorId?: string; status?: string }) {
|
|
38
|
+
let query = this.db.select().from(vendorSubOrders).$dynamic();
|
|
39
|
+
const conditions = [];
|
|
40
|
+
if (filters?.orderId) conditions.push(eq(vendorSubOrders.orderId, filters.orderId));
|
|
41
|
+
if (filters?.vendorId) conditions.push(eq(vendorSubOrders.vendorId, filters.vendorId));
|
|
42
|
+
if (filters?.status) conditions.push(eq(vendorSubOrders.status, filters.status));
|
|
43
|
+
if (conditions.length > 0) {
|
|
44
|
+
query = query.where(conditions.length === 1 ? conditions[0]! : and(...conditions));
|
|
45
|
+
}
|
|
46
|
+
return query.orderBy(desc(vendorSubOrders.createdAt));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private assertTransition(current: SubOrderStatus, next: SubOrderStatus) {
|
|
50
|
+
const allowed = SUB_ORDER_TRANSITIONS[current];
|
|
51
|
+
if (!allowed?.includes(next)) {
|
|
52
|
+
throw new Error(`Cannot transition sub-order from "${current}" to "${next}".`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async confirm(id: string) {
|
|
57
|
+
const sub = await this.getById(id);
|
|
58
|
+
if (!sub) throw new Error("Sub-order not found.");
|
|
59
|
+
this.assertTransition(sub.status as SubOrderStatus, "confirmed");
|
|
60
|
+
|
|
61
|
+
const [updated] = await this.db.update(vendorSubOrders).set({
|
|
62
|
+
status: "confirmed",
|
|
63
|
+
confirmedAt: new Date(),
|
|
64
|
+
updatedAt: new Date(),
|
|
65
|
+
}).where(eq(vendorSubOrders.id, id)).returning();
|
|
66
|
+
return updated;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async process(id: string) {
|
|
70
|
+
const sub = await this.getById(id);
|
|
71
|
+
if (!sub) throw new Error("Sub-order not found.");
|
|
72
|
+
this.assertTransition(sub.status as SubOrderStatus, "processing");
|
|
73
|
+
|
|
74
|
+
const [updated] = await this.db.update(vendorSubOrders).set({
|
|
75
|
+
status: "processing",
|
|
76
|
+
updatedAt: new Date(),
|
|
77
|
+
}).where(eq(vendorSubOrders.id, id)).returning();
|
|
78
|
+
return updated;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async ship(id: string, data: { trackingNumber: string; carrier: string }) {
|
|
82
|
+
const sub = await this.getById(id);
|
|
83
|
+
if (!sub) throw new Error("Sub-order not found.");
|
|
84
|
+
this.assertTransition(sub.status as SubOrderStatus, "shipped");
|
|
85
|
+
|
|
86
|
+
const [updated] = await this.db.update(vendorSubOrders).set({
|
|
87
|
+
status: "shipped",
|
|
88
|
+
trackingNumber: data.trackingNumber,
|
|
89
|
+
carrier: data.carrier,
|
|
90
|
+
shippedAt: new Date(),
|
|
91
|
+
updatedAt: new Date(),
|
|
92
|
+
}).where(eq(vendorSubOrders.id, id)).returning();
|
|
93
|
+
return updated;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async deliver(id: string) {
|
|
97
|
+
const sub = await this.getById(id);
|
|
98
|
+
if (!sub) throw new Error("Sub-order not found.");
|
|
99
|
+
this.assertTransition(sub.status as SubOrderStatus, "delivered");
|
|
100
|
+
|
|
101
|
+
const [updated] = await this.db.update(vendorSubOrders).set({
|
|
102
|
+
status: "delivered",
|
|
103
|
+
deliveredAt: new Date(),
|
|
104
|
+
updatedAt: new Date(),
|
|
105
|
+
}).where(eq(vendorSubOrders.id, id)).returning();
|
|
106
|
+
return updated;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async cancel(id: string, reason?: string) {
|
|
110
|
+
const sub = await this.getById(id);
|
|
111
|
+
if (!sub) throw new Error("Sub-order not found.");
|
|
112
|
+
this.assertTransition(sub.status as SubOrderStatus, "cancelled");
|
|
113
|
+
|
|
114
|
+
const [updated] = await this.db.update(vendorSubOrders).set({
|
|
115
|
+
status: "cancelled",
|
|
116
|
+
cancelledAt: new Date(),
|
|
117
|
+
cancellationReason: reason,
|
|
118
|
+
updatedAt: new Date(),
|
|
119
|
+
}).where(eq(vendorSubOrders.id, id)).returning();
|
|
120
|
+
|
|
121
|
+
// Trigger side effects: release inventory + reverse ledger
|
|
122
|
+
if (this.onCancel && sub) {
|
|
123
|
+
await this.onCancel(sub);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return updated;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async forceStatus(id: string, status: SubOrderStatus) {
|
|
130
|
+
const [updated] = await this.db.update(vendorSubOrders).set({
|
|
131
|
+
status,
|
|
132
|
+
updatedAt: new Date(),
|
|
133
|
+
}).where(eq(vendorSubOrders.id, id)).returning();
|
|
134
|
+
return updated ?? null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { eq, and, or, ilike, desc } from "drizzle-orm";
|
|
2
|
+
import { vendors, vendorDocuments } from "../schema";
|
|
3
|
+
import type { Db, VendorStatus, VerificationStatus, DocumentStatus } from "../types";
|
|
4
|
+
|
|
5
|
+
export class VendorService {
|
|
6
|
+
constructor(private db: Db) {}
|
|
7
|
+
|
|
8
|
+
async create(orgId: string, data: {
|
|
9
|
+
name: string;
|
|
10
|
+
slug?: string;
|
|
11
|
+
email?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
commissionRateBps?: number;
|
|
14
|
+
metadata?: Record<string, unknown>;
|
|
15
|
+
}) {
|
|
16
|
+
const [vendor] = await this.db.insert(vendors).values({
|
|
17
|
+
organizationId: orgId,
|
|
18
|
+
name: data.name,
|
|
19
|
+
slug: data.slug ?? data.name.toLowerCase().replace(/[^a-z0-9]+/g, "-"),
|
|
20
|
+
email: data.email,
|
|
21
|
+
description: data.description,
|
|
22
|
+
commissionRateBps: data.commissionRateBps,
|
|
23
|
+
metadata: data.metadata ?? {},
|
|
24
|
+
}).returning();
|
|
25
|
+
return vendor;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async getById(id: string) {
|
|
29
|
+
const [vendor] = await this.db.select().from(vendors).where(eq(vendors.id, id));
|
|
30
|
+
return vendor ?? null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async getBySlug(slug: string) {
|
|
34
|
+
const [vendor] = await this.db.select().from(vendors).where(eq(vendors.slug, slug));
|
|
35
|
+
return vendor ?? null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async list(filters?: { status?: string; tier?: string; search?: string }) {
|
|
39
|
+
let query = this.db.select().from(vendors).$dynamic();
|
|
40
|
+
const conditions = [];
|
|
41
|
+
|
|
42
|
+
if (filters?.status) conditions.push(eq(vendors.status, filters.status));
|
|
43
|
+
if (filters?.tier) conditions.push(eq(vendors.tier, filters.tier));
|
|
44
|
+
if (filters?.search) {
|
|
45
|
+
conditions.push(
|
|
46
|
+
or(ilike(vendors.name, `%${filters.search}%`), ilike(vendors.email, `%${filters.search}%`)),
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (conditions.length > 0) {
|
|
51
|
+
query = query.where(conditions.length === 1 ? conditions[0]! : and(...conditions));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return query.orderBy(desc(vendors.createdAt));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async update(id: string, data: Partial<{
|
|
58
|
+
name: string; slug: string; email: string; description: string;
|
|
59
|
+
logoUrl: string; bannerUrl: string; contactPhone: string;
|
|
60
|
+
businessAddress: Record<string, unknown>; bankAccount: Record<string, unknown>;
|
|
61
|
+
taxId: string; commissionRateBps: number; payoutSchedule: string;
|
|
62
|
+
payoutMinimumCents: number; holdbackDays: number; metadata: Record<string, unknown>;
|
|
63
|
+
approvedCategories: string[] | null; tier: string;
|
|
64
|
+
}>) {
|
|
65
|
+
const [updated] = await this.db.update(vendors).set({
|
|
66
|
+
...data,
|
|
67
|
+
updatedAt: new Date(),
|
|
68
|
+
}).where(eq(vendors.id, id)).returning();
|
|
69
|
+
return updated ?? null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async approve(id: string) {
|
|
73
|
+
const [updated] = await this.db.update(vendors).set({
|
|
74
|
+
status: "approved",
|
|
75
|
+
updatedAt: new Date(),
|
|
76
|
+
}).where(eq(vendors.id, id)).returning();
|
|
77
|
+
return updated ?? null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async reject(id: string, reason: string) {
|
|
81
|
+
const [updated] = await this.db.update(vendors).set({
|
|
82
|
+
status: "pending" as VendorStatus,
|
|
83
|
+
verificationStatus: "rejected" as VerificationStatus,
|
|
84
|
+
rejectionReason: reason,
|
|
85
|
+
updatedAt: new Date(),
|
|
86
|
+
}).where(eq(vendors.id, id)).returning();
|
|
87
|
+
return updated ?? null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async suspend(id: string, reason: string) {
|
|
91
|
+
const [updated] = await this.db.update(vendors).set({
|
|
92
|
+
status: "suspended" as VendorStatus,
|
|
93
|
+
suspensionReason: reason,
|
|
94
|
+
suspendedAt: new Date(),
|
|
95
|
+
updatedAt: new Date(),
|
|
96
|
+
}).where(eq(vendors.id, id)).returning();
|
|
97
|
+
return updated ?? null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async reinstate(id: string) {
|
|
101
|
+
const [updated] = await this.db.update(vendors).set({
|
|
102
|
+
status: "approved" as VendorStatus,
|
|
103
|
+
suspensionReason: null,
|
|
104
|
+
suspendedAt: null,
|
|
105
|
+
updatedAt: new Date(),
|
|
106
|
+
}).where(eq(vendors.id, id)).returning();
|
|
107
|
+
return updated ?? null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ─── Documents ───────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
async uploadDocument(vendorId: string, data: { type: string; fileUrl: string }) {
|
|
113
|
+
const [doc] = await this.db.insert(vendorDocuments).values({
|
|
114
|
+
vendorId,
|
|
115
|
+
type: data.type,
|
|
116
|
+
fileUrl: data.fileUrl,
|
|
117
|
+
}).returning();
|
|
118
|
+
|
|
119
|
+
// Update vendor verification status
|
|
120
|
+
await this.db.update(vendors).set({
|
|
121
|
+
verificationStatus: "documents_submitted" as VerificationStatus,
|
|
122
|
+
updatedAt: new Date(),
|
|
123
|
+
}).where(eq(vendors.id, vendorId));
|
|
124
|
+
|
|
125
|
+
return doc;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async listDocuments(vendorId: string) {
|
|
129
|
+
return this.db.select().from(vendorDocuments)
|
|
130
|
+
.where(eq(vendorDocuments.vendorId, vendorId))
|
|
131
|
+
.orderBy(desc(vendorDocuments.uploadedAt));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async approveDocument(docId: string, notes?: string) {
|
|
135
|
+
const [updated] = await this.db.update(vendorDocuments).set({
|
|
136
|
+
status: "approved" as DocumentStatus,
|
|
137
|
+
reviewerNotes: notes,
|
|
138
|
+
reviewedAt: new Date(),
|
|
139
|
+
}).where(eq(vendorDocuments.id, docId)).returning();
|
|
140
|
+
return updated ?? null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async rejectDocument(docId: string, notes?: string) {
|
|
144
|
+
const [updated] = await this.db.update(vendorDocuments).set({
|
|
145
|
+
status: "rejected" as DocumentStatus,
|
|
146
|
+
reviewerNotes: notes,
|
|
147
|
+
reviewedAt: new Date(),
|
|
148
|
+
}).where(eq(vendorDocuments.id, docId)).returning();
|
|
149
|
+
return updated ?? null;
|
|
150
|
+
}
|
|
151
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// ─── Sub-Order Status ────────────────────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
export type SubOrderStatus =
|
|
4
|
+
| "pending"
|
|
5
|
+
| "confirmed"
|
|
6
|
+
| "processing"
|
|
7
|
+
| "shipped"
|
|
8
|
+
| "delivered"
|
|
9
|
+
| "cancelled";
|
|
10
|
+
|
|
11
|
+
export const SUB_ORDER_TRANSITIONS: Record<SubOrderStatus, SubOrderStatus[]> = {
|
|
12
|
+
pending: ["confirmed", "cancelled"],
|
|
13
|
+
confirmed: ["processing", "cancelled"],
|
|
14
|
+
processing: ["shipped", "cancelled"],
|
|
15
|
+
shipped: ["delivered"],
|
|
16
|
+
delivered: [],
|
|
17
|
+
cancelled: [],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// ─── Vendor ──────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export type VendorStatus = "pending" | "approved" | "suspended";
|
|
23
|
+
export type VerificationStatus = "unverified" | "documents_submitted" | "verified" | "rejected";
|
|
24
|
+
export type VendorTier = "standard" | "silver" | "gold" | "platinum";
|
|
25
|
+
export type PayoutSchedule = "daily" | "weekly" | "biweekly" | "monthly" | "manual";
|
|
26
|
+
export type VendorApprovalMode = "manual" | "auto" | "invitation";
|
|
27
|
+
|
|
28
|
+
// ─── Commission ──────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
export type CommissionRuleType = "category" | "volume_tier" | "vendor_tier" | "promotional";
|
|
31
|
+
|
|
32
|
+
// ─── Disputes ────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
export type DisputeStatus =
|
|
35
|
+
| "open"
|
|
36
|
+
| "vendor_response_pending"
|
|
37
|
+
| "platform_review"
|
|
38
|
+
| "resolved"
|
|
39
|
+
| "escalated"
|
|
40
|
+
| "closed";
|
|
41
|
+
|
|
42
|
+
export type DisputeReason =
|
|
43
|
+
| "item_not_received"
|
|
44
|
+
| "item_not_as_described"
|
|
45
|
+
| "defective"
|
|
46
|
+
| "wrong_item"
|
|
47
|
+
| "other";
|
|
48
|
+
|
|
49
|
+
export type DisputeResolution =
|
|
50
|
+
| "refund_full"
|
|
51
|
+
| "refund_partial"
|
|
52
|
+
| "replacement"
|
|
53
|
+
| "rejected"
|
|
54
|
+
| "vendor_favor"
|
|
55
|
+
| "buyer_favor";
|
|
56
|
+
|
|
57
|
+
// ─── Returns ─────────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
export type ReturnStatus =
|
|
60
|
+
| "requested"
|
|
61
|
+
| "vendor_approved"
|
|
62
|
+
| "vendor_rejected"
|
|
63
|
+
| "shipped_back"
|
|
64
|
+
| "received"
|
|
65
|
+
| "refunded"
|
|
66
|
+
| "closed";
|
|
67
|
+
|
|
68
|
+
export type ReturnReason =
|
|
69
|
+
| "defective"
|
|
70
|
+
| "wrong_item"
|
|
71
|
+
| "not_as_described"
|
|
72
|
+
| "changed_mind"
|
|
73
|
+
| "other";
|
|
74
|
+
|
|
75
|
+
// ─── Balance Ledger ──────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
export type BalanceEntryType =
|
|
78
|
+
| "sale"
|
|
79
|
+
| "commission"
|
|
80
|
+
| "refund_deduction"
|
|
81
|
+
| "adjustment"
|
|
82
|
+
| "payout";
|
|
83
|
+
|
|
84
|
+
// ─── Document ────────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
export type DocumentType = "business_license" | "tax_form" | "bank_proof" | "identity" | "other";
|
|
87
|
+
export type DocumentStatus = "pending" | "approved" | "rejected";
|
|
88
|
+
|
|
89
|
+
// ─── Review ──────────────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
export type ReviewStatus = "pending" | "published" | "hidden" | "flagged";
|
|
92
|
+
|
|
93
|
+
// ─── RFQ ─────────────────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
export type RFQStatus = "open" | "closed" | "awarded" | "cancelled";
|
|
96
|
+
export type RFQResponseStatus = "submitted" | "shortlisted" | "accepted" | "rejected" | "withdrawn";
|
|
97
|
+
|
|
98
|
+
// ─── Plugin Options ──────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
export interface MarketplacePluginOptions {
|
|
101
|
+
defaultCommissionRateBps?: number;
|
|
102
|
+
|
|
103
|
+
vendorApprovalMode?: VendorApprovalMode;
|
|
104
|
+
requiredDocuments?: DocumentType[];
|
|
105
|
+
|
|
106
|
+
defaultPayoutSchedule?: PayoutSchedule;
|
|
107
|
+
defaultPayoutMinimumCents?: number;
|
|
108
|
+
defaultHoldbackDays?: number;
|
|
109
|
+
|
|
110
|
+
vendorResponseDeadlineDays?: number;
|
|
111
|
+
autoEscalateOnMissedDeadline?: boolean;
|
|
112
|
+
|
|
113
|
+
returnWindowDays?: number;
|
|
114
|
+
autoApproveReturnsOnVendorTimeout?: boolean;
|
|
115
|
+
vendorReturnResponseDays?: number;
|
|
116
|
+
|
|
117
|
+
requireVerifiedPurchase?: boolean;
|
|
118
|
+
reviewModerationEnabled?: boolean;
|
|
119
|
+
|
|
120
|
+
b2b?: {
|
|
121
|
+
rfq?: boolean;
|
|
122
|
+
contractPricing?: boolean;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
performanceThresholds?: {
|
|
126
|
+
minRating?: number;
|
|
127
|
+
maxDefectRatePercent?: number;
|
|
128
|
+
maxLateShipmentRatePercent?: number;
|
|
129
|
+
maxCancellationRatePercent?: number;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ─── Db type ─────────────────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Driver-agnostic Drizzle PostgreSQL database type.
|
|
137
|
+
*
|
|
138
|
+
* `PgDatabase` from `drizzle-orm/pg-core` is the base class that all PG drivers
|
|
139
|
+
* extend (postgres-js, pglite, node-postgres). Using it with
|
|
140
|
+
* `Record<string, unknown>` as the schema generic means:
|
|
141
|
+
* - Row types are fully inferred from `pgTable` schema objects (`.from(vendors)`)
|
|
142
|
+
* - No coupling to any specific driver package
|
|
143
|
+
* - Works identically with PGlite in tests and postgres-js in production
|
|
144
|
+
*/
|
|
145
|
+
export type { PluginDb as Db } from "@unifiedcommerce/core";
|
|
146
|
+
|
|
147
|
+
// ─── Route Context ───────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
/** Minimal Hono-compatible context for route handlers */
|
|
150
|
+
export interface RouteContext {
|
|
151
|
+
req: {
|
|
152
|
+
json(): Promise<Record<string, unknown>>;
|
|
153
|
+
param(name: string): string;
|
|
154
|
+
query(name: string): string | undefined;
|
|
155
|
+
};
|
|
156
|
+
json(data: unknown, status?: number): Response;
|
|
157
|
+
get(key: string): unknown;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ─── Error helper ────────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
export function errorMessage(err: unknown): string {
|
|
163
|
+
return err instanceof Error ? err.message : "Internal server error";
|
|
164
|
+
}
|