@porulle/plugin-marketplace 0.1.0

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 (118) 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 +97 -0
  11. package/dist/routes/b2b.d.ts +9 -0
  12. package/dist/routes/b2b.d.ts.map +1 -0
  13. package/dist/routes/b2b.js +156 -0
  14. package/dist/routes/commission.d.ts +6 -0
  15. package/dist/routes/commission.d.ts.map +1 -0
  16. package/dist/routes/commission.js +85 -0
  17. package/dist/routes/disputes-returns-reviews.d.ts +10 -0
  18. package/dist/routes/disputes-returns-reviews.d.ts.map +1 -0
  19. package/dist/routes/disputes-returns-reviews.js +179 -0
  20. package/dist/routes/payouts.d.ts +6 -0
  21. package/dist/routes/payouts.d.ts.map +1 -0
  22. package/dist/routes/payouts.js +40 -0
  23. package/dist/routes/sub-orders.d.ts +6 -0
  24. package/dist/routes/sub-orders.d.ts.map +1 -0
  25. package/dist/routes/sub-orders.js +44 -0
  26. package/dist/routes/util.d.ts +23 -0
  27. package/dist/routes/util.d.ts.map +1 -0
  28. package/dist/routes/util.js +41 -0
  29. package/dist/routes/vendor-portal.d.ts +14 -0
  30. package/dist/routes/vendor-portal.d.ts.map +1 -0
  31. package/dist/routes/vendor-portal.js +255 -0
  32. package/dist/routes/vendors.d.ts +11 -0
  33. package/dist/routes/vendors.d.ts.map +1 -0
  34. package/dist/routes/vendors.js +185 -0
  35. package/dist/schema.d.ts +3255 -0
  36. package/dist/schema.d.ts.map +1 -0
  37. package/dist/schema.js +225 -0
  38. package/dist/schemas/b2b.d.ts +1009 -0
  39. package/dist/schemas/b2b.d.ts.map +1 -0
  40. package/dist/schemas/b2b.js +208 -0
  41. package/dist/schemas/commission.d.ts +532 -0
  42. package/dist/schemas/commission.d.ts.map +1 -0
  43. package/dist/schemas/commission.js +113 -0
  44. package/dist/schemas/disputes-returns-reviews.d.ts +1405 -0
  45. package/dist/schemas/disputes-returns-reviews.d.ts.map +1 -0
  46. package/dist/schemas/disputes-returns-reviews.js +270 -0
  47. package/dist/schemas/payouts.d.ts +375 -0
  48. package/dist/schemas/payouts.d.ts.map +1 -0
  49. package/dist/schemas/payouts.js +78 -0
  50. package/dist/schemas/sub-orders.d.ts +303 -0
  51. package/dist/schemas/sub-orders.d.ts.map +1 -0
  52. package/dist/schemas/sub-orders.js +67 -0
  53. package/dist/schemas/vendor-portal.d.ts +1785 -0
  54. package/dist/schemas/vendor-portal.d.ts.map +1 -0
  55. package/dist/schemas/vendor-portal.js +294 -0
  56. package/dist/schemas/vendors.d.ts +1348 -0
  57. package/dist/schemas/vendors.d.ts.map +1 -0
  58. package/dist/schemas/vendors.js +245 -0
  59. package/dist/services/commission.d.ts +81 -0
  60. package/dist/services/commission.d.ts.map +1 -0
  61. package/dist/services/commission.js +98 -0
  62. package/dist/services/contract-price.d.ts +64 -0
  63. package/dist/services/contract-price.d.ts.map +1 -0
  64. package/dist/services/contract-price.js +57 -0
  65. package/dist/services/dispute.d.ts +156 -0
  66. package/dist/services/dispute.d.ts.map +1 -0
  67. package/dist/services/dispute.js +77 -0
  68. package/dist/services/payout.d.ts +126 -0
  69. package/dist/services/payout.d.ts.map +1 -0
  70. package/dist/services/payout.js +130 -0
  71. package/dist/services/return.d.ts +181 -0
  72. package/dist/services/return.d.ts.map +1 -0
  73. package/dist/services/return.js +80 -0
  74. package/dist/services/review.d.ts +70 -0
  75. package/dist/services/review.d.ts.map +1 -0
  76. package/dist/services/review.js +60 -0
  77. package/dist/services/rfq.d.ts +122 -0
  78. package/dist/services/rfq.d.ts.map +1 -0
  79. package/dist/services/rfq.js +60 -0
  80. package/dist/services/sub-order.d.ts +336 -0
  81. package/dist/services/sub-order.d.ts.map +1 -0
  82. package/dist/services/sub-order.js +121 -0
  83. package/dist/services/vendor.d.ts +528 -0
  84. package/dist/services/vendor.d.ts.map +1 -0
  85. package/dist/services/vendor.js +119 -0
  86. package/dist/types.d.ts +67 -0
  87. package/dist/types.d.ts.map +1 -0
  88. package/dist/types.js +13 -0
  89. package/package.json +58 -0
  90. package/src/analytics-models.ts +75 -0
  91. package/src/hooks.ts +215 -0
  92. package/src/index.ts +116 -0
  93. package/src/routes/b2b.ts +179 -0
  94. package/src/routes/commission.ts +95 -0
  95. package/src/routes/disputes-returns-reviews.ts +209 -0
  96. package/src/routes/payouts.ts +49 -0
  97. package/src/routes/sub-orders.ts +54 -0
  98. package/src/routes/util.ts +42 -0
  99. package/src/routes/vendor-portal.ts +277 -0
  100. package/src/routes/vendors.ts +201 -0
  101. package/src/schema.ts +260 -0
  102. package/src/schemas/b2b.ts +238 -0
  103. package/src/schemas/commission.ts +129 -0
  104. package/src/schemas/disputes-returns-reviews.ts +311 -0
  105. package/src/schemas/payouts.ts +90 -0
  106. package/src/schemas/sub-orders.ts +77 -0
  107. package/src/schemas/vendor-portal.ts +344 -0
  108. package/src/schemas/vendors.ts +281 -0
  109. package/src/services/commission.ts +120 -0
  110. package/src/services/contract-price.ts +80 -0
  111. package/src/services/dispute.ts +92 -0
  112. package/src/services/payout.ts +154 -0
  113. package/src/services/return.ts +92 -0
  114. package/src/services/review.ts +76 -0
  115. package/src/services/rfq.ts +82 -0
  116. package/src/services/sub-order.ts +136 -0
  117. package/src/services/vendor.ts +151 -0
  118. package/src/types.ts +164 -0
@@ -0,0 +1,179 @@
1
+ import { router } from "@porulle/core";
2
+ import { OpenDisputeBodySchema, RespondDisputeBodySchema, ResolveDisputeBodySchema, RequestReturnBodySchema, ShipBackReturnBodySchema, CreateReviewBodySchema, ModerateReviewBodySchema, } from "../schemas/disputes-returns-reviews.js";
3
+ import { stripUndefined } from "./util.js";
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 "@porulle/core";
2
+ import type { PayoutService } from "../services/payout.js";
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,eAAe,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAG3D,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE;IAC1C,MAAM,EAAE,aAAa,CAAC;CACvB,GAAG,uBAAuB,EAAE,CAyC5B"}
@@ -0,0 +1,40 @@
1
+ import { router } from "@porulle/core";
2
+ import { stripUndefined } from "./util.js";
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 "@porulle/core";
2
+ import type { SubOrderService } from "../services/sub-order.js";
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,eAAe,CAAC;AAE7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAKhE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE;IAC5C,QAAQ,EAAE,eAAe,CAAC;CAC3B,GAAG,uBAAuB,EAAE,CA2C5B"}
@@ -0,0 +1,44 @@
1
+ import { router } from "@porulle/core";
2
+ import { UpdateSubOrderStatusBodySchema } from "../schemas/sub-orders.js";
3
+ import { stripUndefined } from "./util.js";
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 "@porulle/core";
2
+ import type { VendorService } from "../services/vendor.js";
3
+ import type { SubOrderService } from "../services/sub-order.js";
4
+ import type { PayoutService } from "../services/payout.js";
5
+ import type { ReviewService } from "../services/review.js";
6
+ import type { ReturnService } from "../services/return.js";
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,eAAe,CAAC;AAE7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAqB3D,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"}
@@ -0,0 +1,255 @@
1
+ import { router } from "@porulle/core";
2
+ import { vendorEntities } from "../schema.js";
3
+ import { eq } from "@porulle/core/drizzle";
4
+ import { UpdateVendorProfileBodySchema, UploadVendorDocumentBodySchema, ShipSubOrderBodySchema, CancelSubOrderBodySchema, RespondToReviewBodySchema, ApproveReturnBodySchema, RejectReturnBodySchema, } from "../schemas/vendor-portal.js";
5
+ import { stripUndefined, stripVendorSecrets } from "./util.js";
6
+ /** Helper to extract vendorId from actor and throw if missing. */
7
+ function requireVendorId(actor) {
8
+ const vendorId = actor?.vendorId;
9
+ if (!vendorId)
10
+ throw new Error("Forbidden");
11
+ return vendorId;
12
+ }
13
+ export function buildVendorPortalRoutes(services) {
14
+ const r = router("Marketplace - Vendor Portal", "/marketplace/vendor/me");
15
+ // ─── Get my vendor profile ─────────────────────────────────────────────────
16
+ r.get("/")
17
+ .summary("Get my vendor profile")
18
+ .auth()
19
+ .handler(async ({ actor }) => {
20
+ const vendorId = requireVendorId(actor);
21
+ const vendor = await services.vendor.getById(vendorId);
22
+ if (!vendor)
23
+ throw new Error("Vendor not found");
24
+ return stripVendorSecrets(vendor);
25
+ });
26
+ // ─── Update my vendor profile ──────────────────────────────────────────────
27
+ r.patch("/")
28
+ .summary("Update my vendor profile")
29
+ .auth()
30
+ .input(UpdateVendorProfileBodySchema)
31
+ .handler(async ({ actor, input }) => {
32
+ const vendorId = requireVendorId(actor);
33
+ const body = input;
34
+ const updated = await services.vendor.update(vendorId, stripUndefined(body));
35
+ if (!updated)
36
+ throw new Error("Vendor not found");
37
+ return stripVendorSecrets(updated);
38
+ });
39
+ // ─── Upload document ───────────────────────────────────────────────────────
40
+ r.post("/documents")
41
+ .summary("Upload a document for my vendor profile")
42
+ .auth()
43
+ .input(UploadVendorDocumentBodySchema)
44
+ .handler(async ({ actor, input }) => {
45
+ const vendorId = requireVendorId(actor);
46
+ const body = input;
47
+ return services.vendor.uploadDocument(vendorId, {
48
+ type: body.type,
49
+ fileUrl: body.fileUrl,
50
+ });
51
+ });
52
+ // ─── List my documents ─────────────────────────────────────────────────────
53
+ r.get("/documents")
54
+ .summary("List my vendor documents")
55
+ .auth()
56
+ .handler(async ({ actor }) => {
57
+ const vendorId = requireVendorId(actor);
58
+ return services.vendor.listDocuments(vendorId);
59
+ });
60
+ // ─── List my products ──────────────────────────────────────────────────────
61
+ r.get("/products")
62
+ .summary("List my products")
63
+ .auth()
64
+ .handler(async ({ actor, db }) => {
65
+ const vendorId = requireVendorId(actor);
66
+ const drizzle = db;
67
+ const entities = await drizzle
68
+ .select()
69
+ .from(vendorEntities)
70
+ .where(eq(vendorEntities.vendorId, vendorId));
71
+ return entities;
72
+ });
73
+ // ─── List my sub-orders ────────────────────────────────────────────────────
74
+ r.get("/orders")
75
+ .summary("List my sub-orders")
76
+ .auth()
77
+ .handler(async ({ actor, query }) => {
78
+ const vendorId = requireVendorId(actor);
79
+ const status = query.status;
80
+ return services.subOrder.listByVendor(vendorId, stripUndefined({ status }));
81
+ });
82
+ // ─── Get single sub-order ──────────────────────────────────────────────────
83
+ r.get("/orders/{subOrderId}")
84
+ .summary("Get a single sub-order")
85
+ .auth()
86
+ .handler(async ({ actor, params }) => {
87
+ const vendorId = requireVendorId(actor);
88
+ const subOrder = await services.subOrder.getById(params.subOrderId);
89
+ if (!subOrder)
90
+ throw new Error("Sub-order not found");
91
+ if (subOrder.vendorId !== vendorId)
92
+ throw new Error("Forbidden");
93
+ return subOrder;
94
+ });
95
+ // ─── Confirm sub-order ─────────────────────────────────────────────────────
96
+ r.post("/orders/{subOrderId}/confirm")
97
+ .summary("Confirm a sub-order")
98
+ .auth()
99
+ .handler(async ({ actor, params }) => {
100
+ const vendorId = requireVendorId(actor);
101
+ const subOrder = await services.subOrder.getById(params.subOrderId);
102
+ if (!subOrder)
103
+ throw new Error("Sub-order not found");
104
+ if (subOrder.vendorId !== vendorId)
105
+ throw new Error("Forbidden");
106
+ return services.subOrder.confirm(params.subOrderId);
107
+ });
108
+ // ─── Ship sub-order ────────────────────────────────────────────────────────
109
+ r.post("/orders/{subOrderId}/ship")
110
+ .summary("Ship a sub-order")
111
+ .auth()
112
+ .input(ShipSubOrderBodySchema)
113
+ .handler(async ({ actor, params, input }) => {
114
+ const vendorId = requireVendorId(actor);
115
+ const body = input;
116
+ const subOrder = await services.subOrder.getById(params.subOrderId);
117
+ if (!subOrder)
118
+ throw new Error("Sub-order not found");
119
+ if (subOrder.vendorId !== vendorId)
120
+ throw new Error("Forbidden");
121
+ return services.subOrder.ship(params.subOrderId, {
122
+ trackingNumber: body.trackingNumber,
123
+ carrier: body.carrier,
124
+ });
125
+ });
126
+ // ─── Deliver sub-order ─────────────────────────────────────────────────────
127
+ r.post("/orders/{subOrderId}/deliver")
128
+ .summary("Mark a sub-order as delivered")
129
+ .auth()
130
+ .handler(async ({ actor, params }) => {
131
+ const vendorId = requireVendorId(actor);
132
+ const subOrder = await services.subOrder.getById(params.subOrderId);
133
+ if (!subOrder)
134
+ throw new Error("Sub-order not found");
135
+ if (subOrder.vendorId !== vendorId)
136
+ throw new Error("Forbidden");
137
+ return services.subOrder.deliver(params.subOrderId);
138
+ });
139
+ // ─── Cancel sub-order ──────────────────────────────────────────────────────
140
+ r.post("/orders/{subOrderId}/cancel")
141
+ .summary("Cancel a sub-order")
142
+ .auth()
143
+ .input(CancelSubOrderBodySchema)
144
+ .handler(async ({ actor, params, input }) => {
145
+ const vendorId = requireVendorId(actor);
146
+ const body = input;
147
+ const subOrder = await services.subOrder.getById(params.subOrderId);
148
+ if (!subOrder)
149
+ throw new Error("Sub-order not found");
150
+ if (subOrder.vendorId !== vendorId)
151
+ throw new Error("Forbidden");
152
+ return services.subOrder.cancel(params.subOrderId, body.reason);
153
+ });
154
+ // ─── List my payouts ───────────────────────────────────────────────────────
155
+ r.get("/payouts")
156
+ .summary("List my payouts")
157
+ .auth()
158
+ .handler(async ({ actor }) => {
159
+ const vendorId = requireVendorId(actor);
160
+ return services.payout.listPayouts({ vendorId });
161
+ });
162
+ // ─── My balance ────────────────────────────────────────────────────────────
163
+ r.get("/balance")
164
+ .summary("Get my balance")
165
+ .auth()
166
+ .handler(async ({ actor }) => {
167
+ const vendorId = requireVendorId(actor);
168
+ const balance = await services.payout.getBalance(vendorId);
169
+ const ledger = await services.payout.getLedger(vendorId);
170
+ return { balanceCents: balance, ledger };
171
+ });
172
+ // ─── My analytics ──────────────────────────────────────────────────────────
173
+ r.get("/analytics")
174
+ .summary("Get my analytics")
175
+ .auth()
176
+ .handler(async ({ actor }) => {
177
+ const vendorId = requireVendorId(actor);
178
+ const rating = await services.review.getAggregateRating(vendorId);
179
+ const balance = await services.payout.getBalance(vendorId);
180
+ return { rating, balanceCents: balance };
181
+ });
182
+ // ─── My reviews ────────────────────────────────────────────────────────────
183
+ r.get("/reviews")
184
+ .summary("List my reviews")
185
+ .auth()
186
+ .handler(async ({ actor }) => {
187
+ const vendorId = requireVendorId(actor);
188
+ return services.review.getForVendor(vendorId, true);
189
+ });
190
+ // ─── Respond to review ─────────────────────────────────────────────────────
191
+ r.post("/reviews/{id}/respond")
192
+ .summary("Respond to a review")
193
+ .auth()
194
+ .input(RespondToReviewBodySchema)
195
+ .handler(async ({ actor, params, input }) => {
196
+ const vendorId = requireVendorId(actor);
197
+ const body = input;
198
+ // IDOR prevention: verify the review belongs to this vendor
199
+ const vendorReviews = await services.review.getForVendor(vendorId, true);
200
+ const ownsReview = vendorReviews.some((rev) => rev.id === params.id);
201
+ if (!ownsReview)
202
+ throw new Error("Forbidden: review does not belong to this vendor");
203
+ const updated = await services.review.respond(params.id, body.response);
204
+ if (!updated)
205
+ throw new Error("Review not found");
206
+ return updated;
207
+ });
208
+ // ─── My returns ────────────────────────────────────────────────────────────
209
+ r.get("/returns")
210
+ .summary("List my returns")
211
+ .auth()
212
+ .handler(async ({ actor }) => {
213
+ const vendorId = requireVendorId(actor);
214
+ return services.return.listByVendor(vendorId);
215
+ });
216
+ // ─── Approve return ────────────────────────────────────────────────────────
217
+ r.post("/returns/{id}/approve")
218
+ .summary("Approve a return request")
219
+ .auth()
220
+ .input(ApproveReturnBodySchema)
221
+ .handler(async ({ actor, params, input }) => {
222
+ const vendorId = requireVendorId(actor);
223
+ const ret = await services.return.getById(params.id);
224
+ if (!ret)
225
+ throw new Error("Return not found");
226
+ const subOrder = await services.subOrder.getById(ret.subOrderId);
227
+ if (!subOrder || subOrder.vendorId !== vendorId)
228
+ throw new Error("Forbidden");
229
+ const body = input;
230
+ const updated = await services.return.vendorApprove(params.id, body.refundAmountCents);
231
+ if (!updated)
232
+ throw new Error("Return not found");
233
+ return updated;
234
+ });
235
+ // ─── Reject return ─────────────────────────────────────────────────────────
236
+ r.post("/returns/{id}/reject")
237
+ .summary("Reject a return request")
238
+ .auth()
239
+ .input(RejectReturnBodySchema)
240
+ .handler(async ({ actor, params, input }) => {
241
+ const vendorId = requireVendorId(actor);
242
+ const ret = await services.return.getById(params.id);
243
+ if (!ret)
244
+ throw new Error("Return not found");
245
+ const subOrder = await services.subOrder.getById(ret.subOrderId);
246
+ if (!subOrder || subOrder.vendorId !== vendorId)
247
+ throw new Error("Forbidden");
248
+ const body = input;
249
+ const updated = await services.return.vendorReject(params.id, body.notes);
250
+ if (!updated)
251
+ throw new Error("Return not found");
252
+ return updated;
253
+ });
254
+ return r.routes();
255
+ }
@@ -0,0 +1,11 @@
1
+ import type { PluginRouteRegistration } from "@porulle/core";
2
+ import type { VendorService } from "../services/vendor.js";
3
+ import type { PayoutService } from "../services/payout.js";
4
+ import type { ReviewService } from "../services/review.js";
5
+ import type { MarketplacePluginOptions } from "../types.js";
6
+ export declare function buildVendorRoutes(services: {
7
+ vendor: VendorService;
8
+ payout: PayoutService;
9
+ review: ReviewService;
10
+ }, options?: MarketplacePluginOptions): PluginRouteRegistration[];
11
+ //# sourceMappingURL=vendors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vendors.d.ts","sourceRoot":"","sources":["../../src/routes/vendors.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAE7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAU5D,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE;IAC1C,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;CACvB,EAAE,OAAO,CAAC,EAAE,wBAAwB,GAAG,uBAAuB,EAAE,CAoLhE"}