@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,156 @@
|
|
|
1
|
+
import { router } from "@unifiedcommerce/core";
|
|
2
|
+
import { CreateRFQBodySchema, RespondRFQBodySchema, AwardRFQBodySchema, CreateContractPriceBodySchema, UpdateContractPriceBodySchema, } from "../schemas/b2b";
|
|
3
|
+
import { stripUndefined } from "./util";
|
|
4
|
+
export function buildB2BRoutes(services, options) {
|
|
5
|
+
const allRoutes = [];
|
|
6
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
7
|
+
// RFQ (Request for Quote)
|
|
8
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
9
|
+
if (services.rfq) {
|
|
10
|
+
const rfqSvc = services.rfq;
|
|
11
|
+
const rfq = router("Marketplace - B2B", "/marketplace/rfq");
|
|
12
|
+
rfq.post("/")
|
|
13
|
+
.summary("Create a Request for Quote")
|
|
14
|
+
.auth()
|
|
15
|
+
.input(CreateRFQBodySchema)
|
|
16
|
+
.handler(async ({ input }) => {
|
|
17
|
+
const body = input;
|
|
18
|
+
return rfqSvc.create(stripUndefined({
|
|
19
|
+
buyerId: body.buyerId,
|
|
20
|
+
title: body.title,
|
|
21
|
+
description: body.description,
|
|
22
|
+
categorySlug: body.categorySlug,
|
|
23
|
+
quantity: body.quantity,
|
|
24
|
+
budgetCents: body.budgetCents,
|
|
25
|
+
currency: body.currency,
|
|
26
|
+
deadlineAt: body.deadlineAt ? new Date(body.deadlineAt) : undefined,
|
|
27
|
+
metadata: body.metadata,
|
|
28
|
+
}));
|
|
29
|
+
});
|
|
30
|
+
rfq.get("/")
|
|
31
|
+
.summary("List Requests for Quote")
|
|
32
|
+
.auth()
|
|
33
|
+
.handler(async ({ query }) => {
|
|
34
|
+
return rfqSvc.list(stripUndefined({
|
|
35
|
+
status: query.status,
|
|
36
|
+
categorySlug: query.categorySlug,
|
|
37
|
+
}));
|
|
38
|
+
});
|
|
39
|
+
rfq.get("/{id}")
|
|
40
|
+
.summary("Get RFQ detail")
|
|
41
|
+
.auth()
|
|
42
|
+
.handler(async ({ params }) => {
|
|
43
|
+
const item = await rfqSvc.getById(params.id);
|
|
44
|
+
if (!item)
|
|
45
|
+
throw new Error("RFQ not found");
|
|
46
|
+
const responses = await rfqSvc.getResponses(item.id);
|
|
47
|
+
return { ...item, responses };
|
|
48
|
+
});
|
|
49
|
+
rfq.post("/{id}/respond")
|
|
50
|
+
.summary("Submit a vendor response to an RFQ")
|
|
51
|
+
.auth()
|
|
52
|
+
.input(RespondRFQBodySchema)
|
|
53
|
+
.handler(async ({ params, input }) => {
|
|
54
|
+
const body = input;
|
|
55
|
+
const item = await rfqSvc.getById(params.id);
|
|
56
|
+
if (!item)
|
|
57
|
+
throw new Error("RFQ not found");
|
|
58
|
+
return rfqSvc.respond(item.id, stripUndefined({
|
|
59
|
+
vendorId: body.vendorId,
|
|
60
|
+
unitPriceCents: body.unitPriceCents,
|
|
61
|
+
totalPriceCents: body.totalPriceCents,
|
|
62
|
+
leadTimeDays: body.leadTimeDays,
|
|
63
|
+
notes: body.notes,
|
|
64
|
+
}));
|
|
65
|
+
});
|
|
66
|
+
rfq.post("/{id}/award")
|
|
67
|
+
.summary("Award an RFQ to a vendor")
|
|
68
|
+
.auth()
|
|
69
|
+
.permission("marketplace:admin")
|
|
70
|
+
.input(AwardRFQBodySchema)
|
|
71
|
+
.handler(async ({ params, input }) => {
|
|
72
|
+
const body = input;
|
|
73
|
+
const updated = await rfqSvc.award(params.id, body.vendorId);
|
|
74
|
+
if (!updated)
|
|
75
|
+
throw new Error("RFQ not found");
|
|
76
|
+
return updated;
|
|
77
|
+
});
|
|
78
|
+
rfq.post("/{id}/close")
|
|
79
|
+
.summary("Close an RFQ")
|
|
80
|
+
.auth()
|
|
81
|
+
.permission("marketplace:admin")
|
|
82
|
+
.handler(async ({ params }) => {
|
|
83
|
+
const updated = await rfqSvc.close(params.id);
|
|
84
|
+
if (!updated)
|
|
85
|
+
throw new Error("RFQ not found");
|
|
86
|
+
return updated;
|
|
87
|
+
});
|
|
88
|
+
allRoutes.push(...rfq.routes());
|
|
89
|
+
}
|
|
90
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
91
|
+
// CONTRACT PRICES
|
|
92
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
93
|
+
if (services.contractPrice) {
|
|
94
|
+
const cpSvc = services.contractPrice;
|
|
95
|
+
const cp = router("Marketplace - B2B", "/marketplace/contract-prices");
|
|
96
|
+
cp.get("/")
|
|
97
|
+
.summary("List contract prices")
|
|
98
|
+
.auth()
|
|
99
|
+
.handler(async ({ query }) => {
|
|
100
|
+
return cpSvc.list(stripUndefined({
|
|
101
|
+
vendorId: query.vendorId,
|
|
102
|
+
buyerId: query.buyerId,
|
|
103
|
+
}));
|
|
104
|
+
});
|
|
105
|
+
cp.post("/")
|
|
106
|
+
.summary("Create a contract price")
|
|
107
|
+
.auth()
|
|
108
|
+
.permission("marketplace:admin")
|
|
109
|
+
.input(CreateContractPriceBodySchema)
|
|
110
|
+
.handler(async ({ input }) => {
|
|
111
|
+
const body = input;
|
|
112
|
+
return cpSvc.create(stripUndefined({
|
|
113
|
+
vendorId: body.vendorId,
|
|
114
|
+
buyerId: body.buyerId,
|
|
115
|
+
entityId: body.entityId,
|
|
116
|
+
variantId: body.variantId,
|
|
117
|
+
priceCents: body.priceCents,
|
|
118
|
+
minQuantity: body.minQuantity,
|
|
119
|
+
currency: body.currency,
|
|
120
|
+
validFrom: body.validFrom ? new Date(body.validFrom) : undefined,
|
|
121
|
+
validUntil: body.validUntil ? new Date(body.validUntil) : undefined,
|
|
122
|
+
}));
|
|
123
|
+
});
|
|
124
|
+
cp.patch("/{id}")
|
|
125
|
+
.summary("Update a contract price")
|
|
126
|
+
.auth()
|
|
127
|
+
.permission("marketplace:admin")
|
|
128
|
+
.input(UpdateContractPriceBodySchema)
|
|
129
|
+
.handler(async ({ params, input }) => {
|
|
130
|
+
const body = input;
|
|
131
|
+
const updateData = {};
|
|
132
|
+
if (body.priceCents !== undefined)
|
|
133
|
+
updateData.priceCents = body.priceCents;
|
|
134
|
+
if (body.minQuantity !== undefined)
|
|
135
|
+
updateData.minQuantity = body.minQuantity;
|
|
136
|
+
if (body.validFrom !== undefined)
|
|
137
|
+
updateData.validFrom = body.validFrom ? new Date(body.validFrom) : null;
|
|
138
|
+
if (body.validUntil !== undefined)
|
|
139
|
+
updateData.validUntil = body.validUntil ? new Date(body.validUntil) : null;
|
|
140
|
+
const updated = await cpSvc.update(params.id, updateData);
|
|
141
|
+
if (!updated)
|
|
142
|
+
throw new Error("Contract price not found");
|
|
143
|
+
return updated;
|
|
144
|
+
});
|
|
145
|
+
cp.delete("/{id}")
|
|
146
|
+
.summary("Delete a contract price")
|
|
147
|
+
.auth()
|
|
148
|
+
.permission("marketplace:admin")
|
|
149
|
+
.handler(async ({ params }) => {
|
|
150
|
+
await cpSvc.delete(params.id);
|
|
151
|
+
return { deleted: true };
|
|
152
|
+
});
|
|
153
|
+
allRoutes.push(...cp.routes());
|
|
154
|
+
}
|
|
155
|
+
return allRoutes;
|
|
156
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PluginRouteRegistration } from "@unifiedcommerce/core";
|
|
2
|
+
import type { CommissionService } from "../services/commission";
|
|
3
|
+
export declare function buildCommissionRoutes(services: {
|
|
4
|
+
commission: CommissionService;
|
|
5
|
+
}): PluginRouteRegistration[];
|
|
6
|
+
//# sourceMappingURL=commission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commission.d.ts","sourceRoot":"","sources":["../../src/routes/commission.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAQhE,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE;IAC9C,UAAU,EAAE,iBAAiB,CAAC;CAC/B,GAAG,uBAAuB,EAAE,CAiF5B"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { router } from "@unifiedcommerce/core";
|
|
2
|
+
import { CreateCommissionRuleBodySchema, UpdateCommissionRuleBodySchema, PreviewCommissionBodySchema, } from "../schemas/commission";
|
|
3
|
+
import { stripUndefined } from "./util";
|
|
4
|
+
export function buildCommissionRoutes(services) {
|
|
5
|
+
const r = router("Marketplace - Commission", "/marketplace/commission-rules");
|
|
6
|
+
// ─── List commission rules ─────────────────────────────────────────────────
|
|
7
|
+
r.get("/")
|
|
8
|
+
.summary("List all commission rules")
|
|
9
|
+
.permission("marketplace:admin")
|
|
10
|
+
.handler(async () => {
|
|
11
|
+
return services.commission.listRules();
|
|
12
|
+
});
|
|
13
|
+
// ─── Create commission rule ────────────────────────────────────────────────
|
|
14
|
+
r.post("/")
|
|
15
|
+
.summary("Create a commission rule")
|
|
16
|
+
.permission("marketplace:admin")
|
|
17
|
+
.input(CreateCommissionRuleBodySchema)
|
|
18
|
+
.handler(async ({ input }) => {
|
|
19
|
+
const body = input;
|
|
20
|
+
return services.commission.createRule(stripUndefined({
|
|
21
|
+
name: body.name,
|
|
22
|
+
type: body.type,
|
|
23
|
+
rateBps: body.rateBps,
|
|
24
|
+
categorySlug: body.categorySlug,
|
|
25
|
+
vendorId: body.vendorId,
|
|
26
|
+
vendorTier: body.vendorTier,
|
|
27
|
+
minVolumeCents: body.minVolumeCents,
|
|
28
|
+
maxVolumeCents: body.maxVolumeCents,
|
|
29
|
+
validFrom: body.validFrom ? new Date(body.validFrom) : undefined,
|
|
30
|
+
validUntil: body.validUntil ? new Date(body.validUntil) : undefined,
|
|
31
|
+
priority: body.priority,
|
|
32
|
+
}));
|
|
33
|
+
});
|
|
34
|
+
// ─── Update commission rule ────────────────────────────────────────────────
|
|
35
|
+
r.patch("/{id}")
|
|
36
|
+
.summary("Update a commission rule")
|
|
37
|
+
.permission("marketplace:admin")
|
|
38
|
+
.input(UpdateCommissionRuleBodySchema)
|
|
39
|
+
.handler(async ({ params, input }) => {
|
|
40
|
+
const body = input;
|
|
41
|
+
const updateData = {};
|
|
42
|
+
if (body.name !== undefined)
|
|
43
|
+
updateData.name = body.name;
|
|
44
|
+
if (body.rateBps !== undefined)
|
|
45
|
+
updateData.rateBps = body.rateBps;
|
|
46
|
+
if (body.categorySlug !== undefined)
|
|
47
|
+
updateData.categorySlug = body.categorySlug;
|
|
48
|
+
if (body.vendorTier !== undefined)
|
|
49
|
+
updateData.vendorTier = body.vendorTier;
|
|
50
|
+
if (body.minVolumeCents !== undefined)
|
|
51
|
+
updateData.minVolumeCents = body.minVolumeCents;
|
|
52
|
+
if (body.maxVolumeCents !== undefined)
|
|
53
|
+
updateData.maxVolumeCents = body.maxVolumeCents;
|
|
54
|
+
if (body.validFrom !== undefined)
|
|
55
|
+
updateData.validFrom = body.validFrom ? new Date(body.validFrom) : null;
|
|
56
|
+
if (body.validUntil !== undefined)
|
|
57
|
+
updateData.validUntil = body.validUntil ? new Date(body.validUntil) : null;
|
|
58
|
+
if (body.priority !== undefined)
|
|
59
|
+
updateData.priority = body.priority;
|
|
60
|
+
if (body.isActive !== undefined)
|
|
61
|
+
updateData.isActive = body.isActive;
|
|
62
|
+
const updated = await services.commission.updateRule(params.id, updateData);
|
|
63
|
+
if (!updated)
|
|
64
|
+
throw new Error("Commission rule not found");
|
|
65
|
+
return updated;
|
|
66
|
+
});
|
|
67
|
+
// ─── Delete commission rule ────────────────────────────────────────────────
|
|
68
|
+
r.delete("/{id}")
|
|
69
|
+
.summary("Delete a commission rule")
|
|
70
|
+
.permission("marketplace:admin")
|
|
71
|
+
.handler(async ({ params }) => {
|
|
72
|
+
await services.commission.deleteRule(params.id);
|
|
73
|
+
return { deleted: true };
|
|
74
|
+
});
|
|
75
|
+
// ─── Preview commission rate ───────────────────────────────────────────────
|
|
76
|
+
r.post("/preview")
|
|
77
|
+
.summary("Preview the commission rate for a vendor")
|
|
78
|
+
.permission("marketplace:admin")
|
|
79
|
+
.input(PreviewCommissionBodySchema)
|
|
80
|
+
.handler(async ({ input }) => {
|
|
81
|
+
const body = input;
|
|
82
|
+
return services.commission.previewRate(body.vendorId, body.categorySlug, body.volumeCents);
|
|
83
|
+
});
|
|
84
|
+
return r.routes();
|
|
85
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PluginRouteRegistration } from "@unifiedcommerce/core";
|
|
2
|
+
import type { DisputeService } from "../services/dispute";
|
|
3
|
+
import type { ReturnService } from "../services/return";
|
|
4
|
+
import type { ReviewService } from "../services/review";
|
|
5
|
+
export declare function buildDisputesReturnsReviewsRoutes(services: {
|
|
6
|
+
dispute: DisputeService;
|
|
7
|
+
return: ReturnService;
|
|
8
|
+
review: ReviewService;
|
|
9
|
+
}): PluginRouteRegistration[];
|
|
10
|
+
//# sourceMappingURL=disputes-returns-reviews.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disputes-returns-reviews.d.ts","sourceRoot":"","sources":["../../src/routes/disputes-returns-reviews.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAaxD,wBAAgB,iCAAiC,CAAC,QAAQ,EAAE;IAC1D,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;CACvB,GAAG,uBAAuB,EAAE,CA0L5B"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { router } from "@unifiedcommerce/core";
|
|
2
|
+
import { OpenDisputeBodySchema, RespondDisputeBodySchema, ResolveDisputeBodySchema, RequestReturnBodySchema, ShipBackReturnBodySchema, CreateReviewBodySchema, ModerateReviewBodySchema, } from "../schemas/disputes-returns-reviews";
|
|
3
|
+
import { stripUndefined } from "./util";
|
|
4
|
+
export function buildDisputesReturnsReviewsRoutes(services) {
|
|
5
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
6
|
+
// DISPUTES
|
|
7
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
8
|
+
const disputes = router("Marketplace - Disputes", "/marketplace/disputes");
|
|
9
|
+
disputes.post("/")
|
|
10
|
+
.summary("Open a dispute")
|
|
11
|
+
.auth()
|
|
12
|
+
.input(OpenDisputeBodySchema)
|
|
13
|
+
.handler(async ({ input }) => {
|
|
14
|
+
const body = input;
|
|
15
|
+
return services.dispute.open(stripUndefined({
|
|
16
|
+
subOrderId: body.subOrderId,
|
|
17
|
+
openedBy: body.openedBy,
|
|
18
|
+
reason: body.reason,
|
|
19
|
+
description: body.description,
|
|
20
|
+
}));
|
|
21
|
+
});
|
|
22
|
+
disputes.get("/")
|
|
23
|
+
.summary("List disputes")
|
|
24
|
+
.auth()
|
|
25
|
+
.handler(async ({ query }) => {
|
|
26
|
+
return services.dispute.list(stripUndefined({
|
|
27
|
+
status: query.status,
|
|
28
|
+
subOrderId: query.subOrderId,
|
|
29
|
+
}));
|
|
30
|
+
});
|
|
31
|
+
disputes.get("/{id}")
|
|
32
|
+
.summary("Get dispute by ID")
|
|
33
|
+
.auth()
|
|
34
|
+
.handler(async ({ params }) => {
|
|
35
|
+
const dispute = await services.dispute.getById(params.id);
|
|
36
|
+
if (!dispute)
|
|
37
|
+
throw new Error("Dispute not found");
|
|
38
|
+
return dispute;
|
|
39
|
+
});
|
|
40
|
+
disputes.post("/{id}/respond")
|
|
41
|
+
.summary("Respond to a dispute")
|
|
42
|
+
.auth()
|
|
43
|
+
.input(RespondDisputeBodySchema)
|
|
44
|
+
.handler(async ({ params, input }) => {
|
|
45
|
+
const body = input;
|
|
46
|
+
return services.dispute.respond(params.id, stripUndefined({
|
|
47
|
+
party: body.party,
|
|
48
|
+
note: body.note,
|
|
49
|
+
url: body.url,
|
|
50
|
+
}));
|
|
51
|
+
});
|
|
52
|
+
disputes.post("/{id}/escalate")
|
|
53
|
+
.summary("Escalate a dispute")
|
|
54
|
+
.auth()
|
|
55
|
+
.permission("marketplace:admin")
|
|
56
|
+
.handler(async ({ params }) => {
|
|
57
|
+
const updated = await services.dispute.escalate(params.id);
|
|
58
|
+
if (!updated)
|
|
59
|
+
throw new Error("Dispute not found");
|
|
60
|
+
return updated;
|
|
61
|
+
});
|
|
62
|
+
disputes.post("/{id}/resolve")
|
|
63
|
+
.summary("Resolve a dispute")
|
|
64
|
+
.auth()
|
|
65
|
+
.permission("marketplace:admin")
|
|
66
|
+
.input(ResolveDisputeBodySchema)
|
|
67
|
+
.handler(async ({ params, input }) => {
|
|
68
|
+
const body = input;
|
|
69
|
+
const updated = await services.dispute.resolve(params.id, stripUndefined({
|
|
70
|
+
resolution: body.resolution,
|
|
71
|
+
notes: body.notes,
|
|
72
|
+
refundAmountCents: body.refundAmountCents,
|
|
73
|
+
resolvedBy: body.resolvedBy,
|
|
74
|
+
}));
|
|
75
|
+
if (!updated)
|
|
76
|
+
throw new Error("Dispute not found");
|
|
77
|
+
return updated;
|
|
78
|
+
});
|
|
79
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
80
|
+
// RETURNS
|
|
81
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
82
|
+
const returns = router("Marketplace - Returns", "/marketplace/returns");
|
|
83
|
+
returns.post("/")
|
|
84
|
+
.summary("Request a return")
|
|
85
|
+
.auth()
|
|
86
|
+
.input(RequestReturnBodySchema)
|
|
87
|
+
.handler(async ({ input }) => {
|
|
88
|
+
const body = input;
|
|
89
|
+
return services.return.request(stripUndefined({
|
|
90
|
+
subOrderId: body.subOrderId,
|
|
91
|
+
customerId: body.customerId,
|
|
92
|
+
reason: body.reason,
|
|
93
|
+
description: body.description,
|
|
94
|
+
lineItems: body.lineItems,
|
|
95
|
+
}));
|
|
96
|
+
});
|
|
97
|
+
returns.get("/")
|
|
98
|
+
.summary("List returns")
|
|
99
|
+
.auth()
|
|
100
|
+
.handler(async ({ query }) => {
|
|
101
|
+
return services.return.list(stripUndefined({
|
|
102
|
+
subOrderId: query.subOrderId,
|
|
103
|
+
status: query.status,
|
|
104
|
+
}));
|
|
105
|
+
});
|
|
106
|
+
returns.get("/{id}")
|
|
107
|
+
.summary("Get return by ID")
|
|
108
|
+
.auth()
|
|
109
|
+
.handler(async ({ params }) => {
|
|
110
|
+
const ret = await services.return.getById(params.id);
|
|
111
|
+
if (!ret)
|
|
112
|
+
throw new Error("Return not found");
|
|
113
|
+
return ret;
|
|
114
|
+
});
|
|
115
|
+
returns.post("/{id}/ship-back")
|
|
116
|
+
.summary("Ship back a return")
|
|
117
|
+
.auth()
|
|
118
|
+
.input(ShipBackReturnBodySchema)
|
|
119
|
+
.handler(async ({ params, input }) => {
|
|
120
|
+
const body = input;
|
|
121
|
+
const updated = await services.return.shipBack(params.id, body.trackingNumber);
|
|
122
|
+
if (!updated)
|
|
123
|
+
throw new Error("Return not found");
|
|
124
|
+
return updated;
|
|
125
|
+
});
|
|
126
|
+
returns.post("/{id}/receive")
|
|
127
|
+
.summary("Mark a return as received")
|
|
128
|
+
.auth()
|
|
129
|
+
.handler(async ({ params }) => {
|
|
130
|
+
const updated = await services.return.receive(params.id);
|
|
131
|
+
if (!updated)
|
|
132
|
+
throw new Error("Return not found");
|
|
133
|
+
return updated;
|
|
134
|
+
});
|
|
135
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
136
|
+
// REVIEWS
|
|
137
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
138
|
+
const vendorReviews = router("Marketplace - Reviews", "/marketplace/vendors");
|
|
139
|
+
vendorReviews.post("/{id}/reviews")
|
|
140
|
+
.summary("Create a vendor review")
|
|
141
|
+
.auth()
|
|
142
|
+
.input(CreateReviewBodySchema)
|
|
143
|
+
.handler(async ({ params, input }) => {
|
|
144
|
+
const body = input;
|
|
145
|
+
return services.review.create(stripUndefined({
|
|
146
|
+
vendorId: params.id,
|
|
147
|
+
customerId: body.customerId,
|
|
148
|
+
orderId: body.orderId,
|
|
149
|
+
rating: body.rating,
|
|
150
|
+
title: body.title,
|
|
151
|
+
body: body.body,
|
|
152
|
+
}));
|
|
153
|
+
});
|
|
154
|
+
vendorReviews.get("/{id}/reviews")
|
|
155
|
+
.summary("List reviews for a vendor")
|
|
156
|
+
.auth()
|
|
157
|
+
.handler(async ({ params }) => {
|
|
158
|
+
return services.review.getForVendor(params.id);
|
|
159
|
+
});
|
|
160
|
+
const reviewsMod = router("Marketplace - Reviews", "/marketplace/reviews");
|
|
161
|
+
reviewsMod.patch("/{id}")
|
|
162
|
+
.summary("Moderate a review (update status)")
|
|
163
|
+
.auth()
|
|
164
|
+
.permission("marketplace:admin")
|
|
165
|
+
.input(ModerateReviewBodySchema)
|
|
166
|
+
.handler(async ({ params, input }) => {
|
|
167
|
+
const body = input;
|
|
168
|
+
const updated = await services.review.moderate(params.id, body.status);
|
|
169
|
+
if (!updated)
|
|
170
|
+
throw new Error("Review not found");
|
|
171
|
+
return updated;
|
|
172
|
+
});
|
|
173
|
+
return [
|
|
174
|
+
...disputes.routes(),
|
|
175
|
+
...returns.routes(),
|
|
176
|
+
...vendorReviews.routes(),
|
|
177
|
+
...reviewsMod.routes(),
|
|
178
|
+
];
|
|
179
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PluginRouteRegistration } from "@unifiedcommerce/core";
|
|
2
|
+
import type { PayoutService } from "../services/payout";
|
|
3
|
+
export declare function buildPayoutRoutes(services: {
|
|
4
|
+
payout: PayoutService;
|
|
5
|
+
}): PluginRouteRegistration[];
|
|
6
|
+
//# sourceMappingURL=payouts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payouts.d.ts","sourceRoot":"","sources":["../../src/routes/payouts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGxD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE;IAC1C,MAAM,EAAE,aAAa,CAAC;CACvB,GAAG,uBAAuB,EAAE,CAyC5B"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { router } from "@unifiedcommerce/core";
|
|
2
|
+
import { stripUndefined } from "./util";
|
|
3
|
+
export function buildPayoutRoutes(services) {
|
|
4
|
+
const r = router("Marketplace - Payouts", "/marketplace/payouts");
|
|
5
|
+
// ─── List payouts ──────────────────────────────────────────────────────────
|
|
6
|
+
r.get("/")
|
|
7
|
+
.summary("List payouts")
|
|
8
|
+
.permission("marketplace:admin")
|
|
9
|
+
.handler(async ({ query }) => {
|
|
10
|
+
return services.payout.listPayouts(stripUndefined({
|
|
11
|
+
vendorId: query.vendorId,
|
|
12
|
+
status: query.status,
|
|
13
|
+
}));
|
|
14
|
+
});
|
|
15
|
+
// ─── Run payout cycle ──────────────────────────────────────────────────────
|
|
16
|
+
r.post("/run")
|
|
17
|
+
.summary("Run a payout cycle")
|
|
18
|
+
.permission("marketplace:admin")
|
|
19
|
+
.handler(async () => {
|
|
20
|
+
return services.payout.runPayoutCycle();
|
|
21
|
+
});
|
|
22
|
+
// ─── Retry payout ──────────────────────────────────────────────────────────
|
|
23
|
+
r.post("/{id}/retry")
|
|
24
|
+
.summary("Retry a failed payout")
|
|
25
|
+
.permission("marketplace:admin")
|
|
26
|
+
.handler(async ({ params }) => {
|
|
27
|
+
return services.payout.retryPayout(params.id);
|
|
28
|
+
});
|
|
29
|
+
// ─── Get payout by id ──────────────────────────────────────────────────────
|
|
30
|
+
r.get("/{id}")
|
|
31
|
+
.summary("Get payout by ID")
|
|
32
|
+
.permission("marketplace:admin")
|
|
33
|
+
.handler(async ({ params }) => {
|
|
34
|
+
const payout = await services.payout.getPayoutById(params.id);
|
|
35
|
+
if (!payout)
|
|
36
|
+
throw new Error("Payout not found");
|
|
37
|
+
return payout;
|
|
38
|
+
});
|
|
39
|
+
return r.routes();
|
|
40
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PluginRouteRegistration } from "@unifiedcommerce/core";
|
|
2
|
+
import type { SubOrderService } from "../services/sub-order";
|
|
3
|
+
export declare function buildSubOrderRoutes(services: {
|
|
4
|
+
subOrder: SubOrderService;
|
|
5
|
+
}): PluginRouteRegistration[];
|
|
6
|
+
//# sourceMappingURL=sub-orders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sub-orders.d.ts","sourceRoot":"","sources":["../../src/routes/sub-orders.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAK7D,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE;IAC5C,QAAQ,EAAE,eAAe,CAAC;CAC3B,GAAG,uBAAuB,EAAE,CA2C5B"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { router } from "@unifiedcommerce/core";
|
|
2
|
+
import { UpdateSubOrderStatusBodySchema } from "../schemas/sub-orders";
|
|
3
|
+
import { stripUndefined } from "./util";
|
|
4
|
+
export function buildSubOrderRoutes(services) {
|
|
5
|
+
const r = router("Marketplace - Sub-Orders", "/marketplace/sub-orders");
|
|
6
|
+
// ─── List sub-orders ───────────────────────────────────────────────────────
|
|
7
|
+
r.get("/")
|
|
8
|
+
.summary("List sub-orders")
|
|
9
|
+
.permission("marketplace:admin")
|
|
10
|
+
.handler(async ({ query }) => {
|
|
11
|
+
return services.subOrder.list(stripUndefined({
|
|
12
|
+
orderId: query.orderId,
|
|
13
|
+
vendorId: query.vendorId,
|
|
14
|
+
status: query.status,
|
|
15
|
+
}));
|
|
16
|
+
});
|
|
17
|
+
// ─── Get sub-order by id ───────────────────────────────────────────────────
|
|
18
|
+
r.get("/{id}")
|
|
19
|
+
.summary("Get sub-order by ID")
|
|
20
|
+
.permission("marketplace:admin")
|
|
21
|
+
.handler(async ({ params }) => {
|
|
22
|
+
const subOrder = await services.subOrder.getById(params.id);
|
|
23
|
+
if (!subOrder)
|
|
24
|
+
throw new Error("Sub-order not found");
|
|
25
|
+
return subOrder;
|
|
26
|
+
});
|
|
27
|
+
// ─── Force status change ───────────────────────────────────────────────────
|
|
28
|
+
r.patch("/{id}/status")
|
|
29
|
+
.summary("Force a sub-order status change")
|
|
30
|
+
.permission("marketplace:admin")
|
|
31
|
+
.input(UpdateSubOrderStatusBodySchema)
|
|
32
|
+
.handler(async ({ params, input }) => {
|
|
33
|
+
const body = input;
|
|
34
|
+
const subOrder = await services.subOrder.getById(params.id);
|
|
35
|
+
if (!subOrder)
|
|
36
|
+
throw new Error("Sub-order not found");
|
|
37
|
+
// Use cancel() for cancelled status to trigger side effects
|
|
38
|
+
// (inventory release + ledger reversal)
|
|
39
|
+
return body.status === "cancelled"
|
|
40
|
+
? services.subOrder.cancel(params.id, "Admin force cancel")
|
|
41
|
+
: services.subOrder.forceStatus(params.id, body.status);
|
|
42
|
+
});
|
|
43
|
+
return r.routes();
|
|
44
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes keys whose value is `undefined` from an object, returning a
|
|
3
|
+
* cleaned copy that satisfies `exactOptionalPropertyTypes`.
|
|
4
|
+
*
|
|
5
|
+
* Zod's `.optional()` produces `T | undefined` in inferred types, which
|
|
6
|
+
* can't be assigned to optional properties under `exactOptionalPropertyTypes`.
|
|
7
|
+
* This helper strips those `undefined` entries at runtime and casts the
|
|
8
|
+
* result so TypeScript accepts it.
|
|
9
|
+
*/
|
|
10
|
+
export declare function stripUndefined<T>(obj: T): T extends Record<string, any> ? {
|
|
11
|
+
[K in keyof T]: Exclude<T[K], undefined>;
|
|
12
|
+
} : T;
|
|
13
|
+
/** Keys that must never be exposed in API responses. */
|
|
14
|
+
declare const VENDOR_SECRET_KEYS: readonly ["storeAccessToken", "storeConsumerSecret", "storeWebhookSecret", "bankAccount"];
|
|
15
|
+
/**
|
|
16
|
+
* Returns a copy of a vendor record with sensitive fields removed.
|
|
17
|
+
*
|
|
18
|
+
* Accepts any object with string keys so it works with both single vendor
|
|
19
|
+
* rows and Drizzle `InferSelectModel` types without importing the schema.
|
|
20
|
+
*/
|
|
21
|
+
export declare function stripVendorSecrets<T extends Record<string, unknown>>(vendor: T): Omit<T, (typeof VENDOR_SECRET_KEYS)[number]>;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/routes/util.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,wBAAgB,cAAc,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC;CAAE,GAAG,CAAC,CAS1H;AAED,wDAAwD;AACxD,QAAA,MAAM,kBAAkB,2FAKd,CAAC;AAEX;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC,CAM7H"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes keys whose value is `undefined` from an object, returning a
|
|
3
|
+
* cleaned copy that satisfies `exactOptionalPropertyTypes`.
|
|
4
|
+
*
|
|
5
|
+
* Zod's `.optional()` produces `T | undefined` in inferred types, which
|
|
6
|
+
* can't be assigned to optional properties under `exactOptionalPropertyTypes`.
|
|
7
|
+
* This helper strips those `undefined` entries at runtime and casts the
|
|
8
|
+
* result so TypeScript accepts it.
|
|
9
|
+
*/
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
export function stripUndefined(obj) {
|
|
12
|
+
if (obj == null || typeof obj !== "object")
|
|
13
|
+
return obj;
|
|
14
|
+
const result = {};
|
|
15
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
16
|
+
if (value !== undefined) {
|
|
17
|
+
result[key] = value;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
/** Keys that must never be exposed in API responses. */
|
|
23
|
+
const VENDOR_SECRET_KEYS = [
|
|
24
|
+
"storeAccessToken",
|
|
25
|
+
"storeConsumerSecret",
|
|
26
|
+
"storeWebhookSecret",
|
|
27
|
+
"bankAccount",
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* Returns a copy of a vendor record with sensitive fields removed.
|
|
31
|
+
*
|
|
32
|
+
* Accepts any object with string keys so it works with both single vendor
|
|
33
|
+
* rows and Drizzle `InferSelectModel` types without importing the schema.
|
|
34
|
+
*/
|
|
35
|
+
export function stripVendorSecrets(vendor) {
|
|
36
|
+
const copy = { ...vendor };
|
|
37
|
+
for (const key of VENDOR_SECRET_KEYS) {
|
|
38
|
+
delete copy[key];
|
|
39
|
+
}
|
|
40
|
+
return copy;
|
|
41
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PluginRouteRegistration } from "@unifiedcommerce/core";
|
|
2
|
+
import type { VendorService } from "../services/vendor";
|
|
3
|
+
import type { SubOrderService } from "../services/sub-order";
|
|
4
|
+
import type { PayoutService } from "../services/payout";
|
|
5
|
+
import type { ReviewService } from "../services/review";
|
|
6
|
+
import type { ReturnService } from "../services/return";
|
|
7
|
+
export declare function buildVendorPortalRoutes(services: {
|
|
8
|
+
vendor: VendorService;
|
|
9
|
+
subOrder: SubOrderService;
|
|
10
|
+
payout: PayoutService;
|
|
11
|
+
review: ReviewService;
|
|
12
|
+
return: ReturnService;
|
|
13
|
+
}): PluginRouteRegistration[];
|
|
14
|
+
//# sourceMappingURL=vendor-portal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vendor-portal.d.ts","sourceRoot":"","sources":["../../src/routes/vendor-portal.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAqBxD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE;IAChD,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,eAAe,CAAC;IAC1B,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;CACvB,GAAG,uBAAuB,EAAE,CAkP5B"}
|