@unifiedcommerce/plugin-marketplace 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +479 -0
  2. package/dist/analytics-models.d.ts +13 -0
  3. package/dist/analytics-models.d.ts.map +1 -0
  4. package/dist/analytics-models.js +69 -0
  5. package/dist/hooks.d.ts +4 -0
  6. package/dist/hooks.d.ts.map +1 -0
  7. package/dist/hooks.js +187 -0
  8. package/dist/index.d.ts +4 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +105 -0
  11. package/dist/mcp-tools.d.ts +21 -0
  12. package/dist/mcp-tools.d.ts.map +1 -0
  13. package/dist/mcp-tools.js +183 -0
  14. package/dist/routes/b2b.d.ts +9 -0
  15. package/dist/routes/b2b.d.ts.map +1 -0
  16. package/dist/routes/b2b.js +156 -0
  17. package/dist/routes/commission.d.ts +6 -0
  18. package/dist/routes/commission.d.ts.map +1 -0
  19. package/dist/routes/commission.js +85 -0
  20. package/dist/routes/disputes-returns-reviews.d.ts +10 -0
  21. package/dist/routes/disputes-returns-reviews.d.ts.map +1 -0
  22. package/dist/routes/disputes-returns-reviews.js +179 -0
  23. package/dist/routes/payouts.d.ts +6 -0
  24. package/dist/routes/payouts.d.ts.map +1 -0
  25. package/dist/routes/payouts.js +40 -0
  26. package/dist/routes/sub-orders.d.ts +6 -0
  27. package/dist/routes/sub-orders.d.ts.map +1 -0
  28. package/dist/routes/sub-orders.js +44 -0
  29. package/dist/routes/util.d.ts +23 -0
  30. package/dist/routes/util.d.ts.map +1 -0
  31. package/dist/routes/util.js +41 -0
  32. package/dist/routes/vendor-portal.d.ts +14 -0
  33. package/dist/routes/vendor-portal.d.ts.map +1 -0
  34. package/dist/routes/vendor-portal.js +255 -0
  35. package/dist/routes/vendors.d.ts +11 -0
  36. package/dist/routes/vendors.d.ts.map +1 -0
  37. package/dist/routes/vendors.js +185 -0
  38. package/dist/schema.d.ts +3255 -0
  39. package/dist/schema.d.ts.map +1 -0
  40. package/dist/schema.js +225 -0
  41. package/dist/schemas/b2b.d.ts +1009 -0
  42. package/dist/schemas/b2b.d.ts.map +1 -0
  43. package/dist/schemas/b2b.js +208 -0
  44. package/dist/schemas/commission.d.ts +532 -0
  45. package/dist/schemas/commission.d.ts.map +1 -0
  46. package/dist/schemas/commission.js +113 -0
  47. package/dist/schemas/disputes-returns-reviews.d.ts +1405 -0
  48. package/dist/schemas/disputes-returns-reviews.d.ts.map +1 -0
  49. package/dist/schemas/disputes-returns-reviews.js +270 -0
  50. package/dist/schemas/payouts.d.ts +375 -0
  51. package/dist/schemas/payouts.d.ts.map +1 -0
  52. package/dist/schemas/payouts.js +78 -0
  53. package/dist/schemas/sub-orders.d.ts +303 -0
  54. package/dist/schemas/sub-orders.d.ts.map +1 -0
  55. package/dist/schemas/sub-orders.js +67 -0
  56. package/dist/schemas/vendor-portal.d.ts +1785 -0
  57. package/dist/schemas/vendor-portal.d.ts.map +1 -0
  58. package/dist/schemas/vendor-portal.js +294 -0
  59. package/dist/schemas/vendors.d.ts +1348 -0
  60. package/dist/schemas/vendors.d.ts.map +1 -0
  61. package/dist/schemas/vendors.js +245 -0
  62. package/dist/services/commission.d.ts +81 -0
  63. package/dist/services/commission.d.ts.map +1 -0
  64. package/dist/services/commission.js +98 -0
  65. package/dist/services/contract-price.d.ts +64 -0
  66. package/dist/services/contract-price.d.ts.map +1 -0
  67. package/dist/services/contract-price.js +57 -0
  68. package/dist/services/dispute.d.ts +156 -0
  69. package/dist/services/dispute.d.ts.map +1 -0
  70. package/dist/services/dispute.js +77 -0
  71. package/dist/services/payout.d.ts +126 -0
  72. package/dist/services/payout.d.ts.map +1 -0
  73. package/dist/services/payout.js +130 -0
  74. package/dist/services/return.d.ts +181 -0
  75. package/dist/services/return.d.ts.map +1 -0
  76. package/dist/services/return.js +80 -0
  77. package/dist/services/review.d.ts +70 -0
  78. package/dist/services/review.d.ts.map +1 -0
  79. package/dist/services/review.js +60 -0
  80. package/dist/services/rfq.d.ts +122 -0
  81. package/dist/services/rfq.d.ts.map +1 -0
  82. package/dist/services/rfq.js +60 -0
  83. package/dist/services/sub-order.d.ts +336 -0
  84. package/dist/services/sub-order.d.ts.map +1 -0
  85. package/dist/services/sub-order.js +121 -0
  86. package/dist/services/vendor.d.ts +528 -0
  87. package/dist/services/vendor.d.ts.map +1 -0
  88. package/dist/services/vendor.js +119 -0
  89. package/dist/types.d.ts +67 -0
  90. package/dist/types.d.ts.map +1 -0
  91. package/dist/types.js +13 -0
  92. package/package.json +43 -0
  93. package/src/analytics-models.ts +75 -0
  94. package/src/hooks.ts +215 -0
  95. package/src/index.ts +124 -0
  96. package/src/mcp-tools.ts +210 -0
  97. package/src/routes/b2b.ts +179 -0
  98. package/src/routes/commission.ts +95 -0
  99. package/src/routes/disputes-returns-reviews.ts +209 -0
  100. package/src/routes/payouts.ts +49 -0
  101. package/src/routes/sub-orders.ts +54 -0
  102. package/src/routes/util.ts +42 -0
  103. package/src/routes/vendor-portal.ts +277 -0
  104. package/src/routes/vendors.ts +201 -0
  105. package/src/schema.ts +260 -0
  106. package/src/schemas/b2b.ts +238 -0
  107. package/src/schemas/commission.ts +129 -0
  108. package/src/schemas/disputes-returns-reviews.ts +311 -0
  109. package/src/schemas/payouts.ts +90 -0
  110. package/src/schemas/sub-orders.ts +77 -0
  111. package/src/schemas/vendor-portal.ts +344 -0
  112. package/src/schemas/vendors.ts +281 -0
  113. package/src/services/commission.ts +120 -0
  114. package/src/services/contract-price.ts +80 -0
  115. package/src/services/dispute.ts +92 -0
  116. package/src/services/payout.ts +154 -0
  117. package/src/services/return.ts +92 -0
  118. package/src/services/review.ts +76 -0
  119. package/src/services/rfq.ts +82 -0
  120. package/src/services/sub-order.ts +136 -0
  121. package/src/services/vendor.ts +151 -0
  122. package/src/types.ts +164 -0
@@ -0,0 +1,156 @@
1
+ import type { Db, DisputeResolution, MarketplacePluginOptions } from "../types";
2
+ export declare class DisputeService {
3
+ private db;
4
+ private options;
5
+ constructor(db: Db, options: MarketplacePluginOptions);
6
+ open(data: {
7
+ subOrderId: string;
8
+ openedBy: string;
9
+ reason: string;
10
+ description?: string;
11
+ }): Promise<{
12
+ id: string;
13
+ status: string;
14
+ description: string | null;
15
+ subOrderId: string;
16
+ openedBy: string;
17
+ reason: string;
18
+ resolution: string | null;
19
+ resolutionNotes: string | null;
20
+ refundAmountCents: number | null;
21
+ evidence: {
22
+ party: string;
23
+ type: string;
24
+ url?: string;
25
+ note?: string;
26
+ at: string;
27
+ }[] | null;
28
+ resolvedBy: string | null;
29
+ deadlineAt: Date | null;
30
+ openedAt: Date;
31
+ resolvedAt: Date | null;
32
+ } | undefined>;
33
+ getById(id: string): Promise<{
34
+ id: string;
35
+ subOrderId: string;
36
+ openedBy: string;
37
+ reason: string;
38
+ description: string | null;
39
+ status: string;
40
+ resolution: string | null;
41
+ resolutionNotes: string | null;
42
+ refundAmountCents: number | null;
43
+ evidence: {
44
+ party: string;
45
+ type: string;
46
+ url?: string;
47
+ note?: string;
48
+ at: string;
49
+ }[] | null;
50
+ resolvedBy: string | null;
51
+ deadlineAt: Date | null;
52
+ openedAt: Date;
53
+ resolvedAt: Date | null;
54
+ } | null>;
55
+ list(filters?: {
56
+ status?: string;
57
+ subOrderId?: string;
58
+ }): Promise<{
59
+ id: string;
60
+ subOrderId: string;
61
+ openedBy: string;
62
+ reason: string;
63
+ description: string | null;
64
+ status: string;
65
+ resolution: string | null;
66
+ resolutionNotes: string | null;
67
+ refundAmountCents: number | null;
68
+ evidence: {
69
+ party: string;
70
+ type: string;
71
+ url?: string;
72
+ note?: string;
73
+ at: string;
74
+ }[] | null;
75
+ resolvedBy: string | null;
76
+ deadlineAt: Date | null;
77
+ openedAt: Date;
78
+ resolvedAt: Date | null;
79
+ }[]>;
80
+ respond(id: string, data: {
81
+ party: string;
82
+ note: string;
83
+ url?: string;
84
+ }): Promise<{
85
+ id: string;
86
+ subOrderId: string;
87
+ openedBy: string;
88
+ reason: string;
89
+ description: string | null;
90
+ status: string;
91
+ resolution: string | null;
92
+ resolutionNotes: string | null;
93
+ refundAmountCents: number | null;
94
+ evidence: {
95
+ party: string;
96
+ type: string;
97
+ url?: string;
98
+ note?: string;
99
+ at: string;
100
+ }[] | null;
101
+ resolvedBy: string | null;
102
+ deadlineAt: Date | null;
103
+ openedAt: Date;
104
+ resolvedAt: Date | null;
105
+ } | undefined>;
106
+ escalate(id: string): Promise<{
107
+ id: string;
108
+ subOrderId: string;
109
+ openedBy: string;
110
+ reason: string;
111
+ description: string | null;
112
+ status: string;
113
+ resolution: string | null;
114
+ resolutionNotes: string | null;
115
+ refundAmountCents: number | null;
116
+ evidence: {
117
+ party: string;
118
+ type: string;
119
+ url?: string;
120
+ note?: string;
121
+ at: string;
122
+ }[] | null;
123
+ resolvedBy: string | null;
124
+ deadlineAt: Date | null;
125
+ openedAt: Date;
126
+ resolvedAt: Date | null;
127
+ } | null>;
128
+ resolve(id: string, data: {
129
+ resolution: DisputeResolution;
130
+ notes?: string;
131
+ refundAmountCents?: number;
132
+ resolvedBy: string;
133
+ }): Promise<{
134
+ id: string;
135
+ subOrderId: string;
136
+ openedBy: string;
137
+ reason: string;
138
+ description: string | null;
139
+ status: string;
140
+ resolution: string | null;
141
+ resolutionNotes: string | null;
142
+ refundAmountCents: number | null;
143
+ evidence: {
144
+ party: string;
145
+ type: string;
146
+ url?: string;
147
+ note?: string;
148
+ at: string;
149
+ }[] | null;
150
+ resolvedBy: string | null;
151
+ deadlineAt: Date | null;
152
+ openedAt: Date;
153
+ resolvedAt: Date | null;
154
+ } | null>;
155
+ }
156
+ //# sourceMappingURL=dispute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispute.d.ts","sourceRoot":"","sources":["../../src/services/dispute.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAiB,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAE/F,qBAAa,cAAc;IAEvB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,OAAO;gBADP,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,wBAAwB;IAGrC,IAAI,CAAC,IAAI,EAAE;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB;;;;;;;;;;;;;;;;;;;;;;IAgBK,OAAO,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;IAKlB,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;;;;;;;;;;;;;;;;;;IAWvD,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;;;;;;;;;;;;;;;;;;IAqBvE,QAAQ,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;IAOnB,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;QAC9B,UAAU,EAAE,iBAAiB,CAAC;QAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,UAAU,EAAE,MAAM,CAAC;KACpB;;;;;;;;;;;;;;;;;;;;;;CAWF"}
@@ -0,0 +1,77 @@
1
+ import { eq, and, desc } from "drizzle-orm";
2
+ import { disputes } from "../schema";
3
+ export class DisputeService {
4
+ db;
5
+ options;
6
+ constructor(db, options) {
7
+ this.db = db;
8
+ this.options = options;
9
+ }
10
+ async open(data) {
11
+ const deadlineDays = this.options.vendorResponseDeadlineDays ?? 3;
12
+ const deadline = new Date();
13
+ deadline.setDate(deadline.getDate() + deadlineDays);
14
+ const [dispute] = await this.db.insert(disputes).values({
15
+ subOrderId: data.subOrderId,
16
+ openedBy: data.openedBy,
17
+ reason: data.reason,
18
+ description: data.description,
19
+ status: "vendor_response_pending",
20
+ deadlineAt: deadline,
21
+ }).returning();
22
+ return dispute;
23
+ }
24
+ async getById(id) {
25
+ const [dispute] = await this.db.select().from(disputes).where(eq(disputes.id, id));
26
+ return dispute ?? null;
27
+ }
28
+ async list(filters) {
29
+ let query = this.db.select().from(disputes).$dynamic();
30
+ const conditions = [];
31
+ if (filters?.status)
32
+ conditions.push(eq(disputes.status, filters.status));
33
+ if (filters?.subOrderId)
34
+ conditions.push(eq(disputes.subOrderId, filters.subOrderId));
35
+ if (conditions.length > 0) {
36
+ query = query.where(conditions.length === 1 ? conditions[0] : and(...conditions));
37
+ }
38
+ return query.orderBy(desc(disputes.openedAt));
39
+ }
40
+ async respond(id, data) {
41
+ const dispute = await this.getById(id);
42
+ if (!dispute)
43
+ throw new Error("Dispute not found.");
44
+ const evidence = [...(dispute.evidence ?? [])];
45
+ const entry = {
46
+ party: data.party,
47
+ type: "response",
48
+ note: data.note,
49
+ at: new Date().toISOString(),
50
+ };
51
+ if (data.url)
52
+ entry.url = data.url;
53
+ evidence.push(entry);
54
+ const [updated] = await this.db.update(disputes).set({
55
+ evidence,
56
+ status: "platform_review",
57
+ }).where(eq(disputes.id, id)).returning();
58
+ return updated;
59
+ }
60
+ async escalate(id) {
61
+ const [updated] = await this.db.update(disputes).set({
62
+ status: "escalated",
63
+ }).where(eq(disputes.id, id)).returning();
64
+ return updated ?? null;
65
+ }
66
+ async resolve(id, data) {
67
+ const [updated] = await this.db.update(disputes).set({
68
+ status: "resolved",
69
+ resolution: data.resolution,
70
+ resolutionNotes: data.notes,
71
+ refundAmountCents: data.refundAmountCents,
72
+ resolvedBy: data.resolvedBy,
73
+ resolvedAt: new Date(),
74
+ }).where(eq(disputes.id, id)).returning();
75
+ return updated ?? null;
76
+ }
77
+ }
@@ -0,0 +1,126 @@
1
+ import type { Db, BalanceEntryType, MarketplacePluginOptions } from "../types";
2
+ export declare class PayoutService {
3
+ private db;
4
+ private options;
5
+ constructor(db: Db, options: MarketplacePluginOptions);
6
+ addLedgerEntry(data: {
7
+ vendorId: string;
8
+ type: BalanceEntryType;
9
+ amountCents: number;
10
+ referenceType?: string;
11
+ referenceId?: string;
12
+ description?: string;
13
+ }): Promise<{
14
+ id: string;
15
+ vendorId: string;
16
+ createdAt: Date;
17
+ type: string;
18
+ description: string | null;
19
+ amountCents: number;
20
+ runningBalanceCents: number;
21
+ referenceType: string | null;
22
+ referenceId: string | null;
23
+ } | undefined>;
24
+ getBalance(vendorId: string): Promise<number>;
25
+ getLedger(vendorId: string, limit?: number): Promise<{
26
+ id: string;
27
+ vendorId: string;
28
+ type: string;
29
+ amountCents: number;
30
+ runningBalanceCents: number;
31
+ referenceType: string | null;
32
+ referenceId: string | null;
33
+ description: string | null;
34
+ createdAt: Date;
35
+ }[]>;
36
+ getPayoutById(id: string): Promise<{
37
+ id: string;
38
+ vendorId: string;
39
+ subOrderId: string | null;
40
+ amount: number;
41
+ status: string;
42
+ payoutMethod: string | null;
43
+ externalReference: string | null;
44
+ periodStart: Date | null;
45
+ periodEnd: Date | null;
46
+ grossAmount: number | null;
47
+ deductions: {
48
+ type: string;
49
+ amount: number;
50
+ reference?: string;
51
+ }[] | null;
52
+ netAmount: number | null;
53
+ failedAt: Date | null;
54
+ failureReason: string | null;
55
+ retryCount: number;
56
+ processedAt: Date | null;
57
+ metadata: Record<string, unknown> | null;
58
+ createdAt: Date;
59
+ } | null>;
60
+ listPayouts(filters?: {
61
+ vendorId?: string;
62
+ status?: string;
63
+ }): Promise<{
64
+ id: string;
65
+ vendorId: string;
66
+ subOrderId: string | null;
67
+ amount: number;
68
+ status: string;
69
+ payoutMethod: string | null;
70
+ externalReference: string | null;
71
+ periodStart: Date | null;
72
+ periodEnd: Date | null;
73
+ grossAmount: number | null;
74
+ deductions: {
75
+ type: string;
76
+ amount: number;
77
+ reference?: string;
78
+ }[] | null;
79
+ netAmount: number | null;
80
+ failedAt: Date | null;
81
+ failureReason: string | null;
82
+ retryCount: number;
83
+ processedAt: Date | null;
84
+ metadata: Record<string, unknown> | null;
85
+ createdAt: Date;
86
+ }[]>;
87
+ /**
88
+ * Run payout cycle for eligible vendors.
89
+ * Per RFC §5.5:
90
+ * 1. Find vendors with matching payout_schedule
91
+ * 2. Check balance >= payout_minimum_cents
92
+ * 3. Only include deliveries older than holdback_days
93
+ * 4. Calculate deductions (refunds, adjustments)
94
+ * 5. Create payout record + balance ledger debit
95
+ */
96
+ runPayoutCycle(): Promise<Array<{
97
+ vendorId: string;
98
+ payoutId: string;
99
+ netAmount: number;
100
+ }>>;
101
+ retryPayout(payoutId: string): Promise<{
102
+ id: string;
103
+ vendorId: string;
104
+ subOrderId: string | null;
105
+ amount: number;
106
+ status: string;
107
+ payoutMethod: string | null;
108
+ externalReference: string | null;
109
+ periodStart: Date | null;
110
+ periodEnd: Date | null;
111
+ grossAmount: number | null;
112
+ deductions: {
113
+ type: string;
114
+ amount: number;
115
+ reference?: string;
116
+ }[] | null;
117
+ netAmount: number | null;
118
+ failedAt: Date | null;
119
+ failureReason: string | null;
120
+ retryCount: number;
121
+ processedAt: Date | null;
122
+ metadata: Record<string, unknown> | null;
123
+ createdAt: Date;
124
+ } | undefined>;
125
+ }
126
+ //# sourceMappingURL=payout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payout.d.ts","sourceRoot":"","sources":["../../src/services/payout.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAE/E,qBAAa,aAAa;IAEtB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,OAAO;gBADP,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,wBAAwB;IAKrC,cAAc,CAAC,IAAI,EAAE;QACzB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,gBAAgB,CAAC;QACvB,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB;;;;;;;;;;;IAiBK,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAW7C,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,SAAK;;;;;;;;;;;IAStC,aAAa,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;IAKxB,WAAW,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;;;;;;;;;;;;;;;;;;;;IAWlE;;;;;;;;OAQG;IACG,cAAc,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAoD3F,WAAW,CAAC,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;CAoBnC"}
@@ -0,0 +1,130 @@
1
+ import { eq, and, desc, sql } from "drizzle-orm";
2
+ import { vendorPayouts, vendorBalances, vendors } from "../schema";
3
+ export class PayoutService {
4
+ db;
5
+ options;
6
+ constructor(db, options) {
7
+ this.db = db;
8
+ this.options = options;
9
+ }
10
+ // ─── Balance Ledger ────────────────────────────────────────────────────────
11
+ async addLedgerEntry(data) {
12
+ // Get current balance
13
+ const balance = await this.getBalance(data.vendorId);
14
+ const newBalance = balance + data.amountCents;
15
+ const [entry] = await this.db.insert(vendorBalances).values({
16
+ vendorId: data.vendorId,
17
+ type: data.type,
18
+ amountCents: data.amountCents,
19
+ runningBalanceCents: newBalance,
20
+ referenceType: data.referenceType,
21
+ referenceId: data.referenceId,
22
+ description: data.description,
23
+ }).returning();
24
+ return entry;
25
+ }
26
+ async getBalance(vendorId) {
27
+ const rows = await this.db.select({
28
+ balance: sql `COALESCE((
29
+ SELECT running_balance_cents FROM marketplace_vendor_balances
30
+ WHERE vendor_id = ${vendorId}
31
+ ORDER BY created_at DESC LIMIT 1
32
+ ), 0)`,
33
+ }).from(vendors).where(eq(vendors.id, vendorId));
34
+ return rows[0]?.balance ?? 0;
35
+ }
36
+ async getLedger(vendorId, limit = 50) {
37
+ return this.db.select().from(vendorBalances)
38
+ .where(eq(vendorBalances.vendorId, vendorId))
39
+ .orderBy(desc(vendorBalances.createdAt))
40
+ .limit(limit);
41
+ }
42
+ // ─── Payouts ───────────────────────────────────────────────────────────────
43
+ async getPayoutById(id) {
44
+ const [payout] = await this.db.select().from(vendorPayouts).where(eq(vendorPayouts.id, id));
45
+ return payout ?? null;
46
+ }
47
+ async listPayouts(filters) {
48
+ let query = this.db.select().from(vendorPayouts).$dynamic();
49
+ const conditions = [];
50
+ if (filters?.vendorId)
51
+ conditions.push(eq(vendorPayouts.vendorId, filters.vendorId));
52
+ if (filters?.status)
53
+ conditions.push(eq(vendorPayouts.status, filters.status));
54
+ if (conditions.length > 0) {
55
+ query = query.where(conditions.length === 1 ? conditions[0] : and(...conditions));
56
+ }
57
+ return query.orderBy(desc(vendorPayouts.createdAt));
58
+ }
59
+ /**
60
+ * Run payout cycle for eligible vendors.
61
+ * Per RFC §5.5:
62
+ * 1. Find vendors with matching payout_schedule
63
+ * 2. Check balance >= payout_minimum_cents
64
+ * 3. Only include deliveries older than holdback_days
65
+ * 4. Calculate deductions (refunds, adjustments)
66
+ * 5. Create payout record + balance ledger debit
67
+ */
68
+ async runPayoutCycle() {
69
+ const allVendors = await this.db.select().from(vendors)
70
+ .where(eq(vendors.status, "approved"));
71
+ const results = [];
72
+ for (const vendor of allVendors) {
73
+ const balance = await this.getBalance(vendor.id);
74
+ const minimum = vendor.payoutMinimumCents ?? this.options.defaultPayoutMinimumCents ?? 5000;
75
+ if (balance < minimum)
76
+ continue;
77
+ const grossAmount = balance;
78
+ const deductions = [];
79
+ const netAmount = grossAmount - deductions.reduce((sum, d) => sum + d.amount, 0);
80
+ if (netAmount <= 0)
81
+ continue;
82
+ const [payout] = await this.db.insert(vendorPayouts).values({
83
+ vendorId: vendor.id,
84
+ amount: netAmount,
85
+ status: "processing",
86
+ grossAmount,
87
+ deductions,
88
+ netAmount,
89
+ periodEnd: new Date(),
90
+ }).returning();
91
+ if (!payout)
92
+ continue;
93
+ // Debit vendor balance
94
+ await this.addLedgerEntry({
95
+ vendorId: vendor.id,
96
+ type: "payout",
97
+ amountCents: -netAmount,
98
+ referenceType: "payout",
99
+ referenceId: payout.id,
100
+ description: `Payout #${payout.id.slice(0, 8)}`,
101
+ });
102
+ // Mark as completed (in a real system, this would wait for bank transfer confirmation)
103
+ await this.db.update(vendorPayouts).set({
104
+ status: "completed",
105
+ processedAt: new Date(),
106
+ }).where(eq(vendorPayouts.id, payout.id));
107
+ results.push({ vendorId: vendor.id, payoutId: payout.id, netAmount });
108
+ }
109
+ return results;
110
+ }
111
+ async retryPayout(payoutId) {
112
+ const payout = await this.getPayoutById(payoutId);
113
+ if (!payout)
114
+ throw new Error("Payout not found.");
115
+ if (payout.status !== "failed")
116
+ throw new Error("Only failed payouts can be retried.");
117
+ const [updated] = await this.db.update(vendorPayouts).set({
118
+ status: "processing",
119
+ retryCount: (payout.retryCount ?? 0) + 1,
120
+ failedAt: null,
121
+ failureReason: null,
122
+ }).where(eq(vendorPayouts.id, payoutId)).returning();
123
+ // Mark completed (simplified — real implementation calls payment provider)
124
+ await this.db.update(vendorPayouts).set({
125
+ status: "completed",
126
+ processedAt: new Date(),
127
+ }).where(eq(vendorPayouts.id, payoutId));
128
+ return updated;
129
+ }
130
+ }
@@ -0,0 +1,181 @@
1
+ import type { Db } from "../types";
2
+ export declare class ReturnService {
3
+ private db;
4
+ constructor(db: Db);
5
+ request(data: {
6
+ subOrderId: string;
7
+ customerId?: string;
8
+ reason: string;
9
+ description?: string;
10
+ lineItems?: Array<{
11
+ entityId: string;
12
+ quantity: number;
13
+ reason?: string;
14
+ }>;
15
+ }): Promise<{
16
+ id: string;
17
+ status: string;
18
+ description: string | null;
19
+ lineItems: {
20
+ entityId: string;
21
+ quantity: number;
22
+ reason?: string;
23
+ }[] | null;
24
+ trackingNumber: string | null;
25
+ vendorNotes: string | null;
26
+ subOrderId: string;
27
+ reason: string;
28
+ refundAmountCents: number | null;
29
+ resolvedAt: Date | null;
30
+ customerId: string | null;
31
+ requestedAt: Date;
32
+ } | undefined>;
33
+ getById(id: string): Promise<{
34
+ id: string;
35
+ subOrderId: string;
36
+ customerId: string | null;
37
+ reason: string;
38
+ description: string | null;
39
+ status: string;
40
+ lineItems: {
41
+ entityId: string;
42
+ quantity: number;
43
+ reason?: string;
44
+ }[] | null;
45
+ refundAmountCents: number | null;
46
+ vendorNotes: string | null;
47
+ trackingNumber: string | null;
48
+ requestedAt: Date;
49
+ resolvedAt: Date | null;
50
+ } | null>;
51
+ list(filters?: {
52
+ subOrderId?: string;
53
+ status?: string;
54
+ }): Promise<{
55
+ id: string;
56
+ subOrderId: string;
57
+ customerId: string | null;
58
+ reason: string;
59
+ description: string | null;
60
+ status: string;
61
+ lineItems: {
62
+ entityId: string;
63
+ quantity: number;
64
+ reason?: string;
65
+ }[] | null;
66
+ refundAmountCents: number | null;
67
+ vendorNotes: string | null;
68
+ trackingNumber: string | null;
69
+ requestedAt: Date;
70
+ resolvedAt: Date | null;
71
+ }[]>;
72
+ listByVendor(vendorId: string): Promise<{
73
+ id: string;
74
+ subOrderId: string;
75
+ customerId: string | null;
76
+ reason: string;
77
+ description: string | null;
78
+ status: string;
79
+ lineItems: {
80
+ entityId: string;
81
+ quantity: number;
82
+ reason?: string;
83
+ }[] | null;
84
+ refundAmountCents: number | null;
85
+ vendorNotes: string | null;
86
+ trackingNumber: string | null;
87
+ requestedAt: Date;
88
+ resolvedAt: Date | null;
89
+ }[]>;
90
+ vendorApprove(id: string, refundAmountCents?: number): Promise<{
91
+ id: string;
92
+ subOrderId: string;
93
+ customerId: string | null;
94
+ reason: string;
95
+ description: string | null;
96
+ status: string;
97
+ lineItems: {
98
+ entityId: string;
99
+ quantity: number;
100
+ reason?: string;
101
+ }[] | null;
102
+ refundAmountCents: number | null;
103
+ vendorNotes: string | null;
104
+ trackingNumber: string | null;
105
+ requestedAt: Date;
106
+ resolvedAt: Date | null;
107
+ } | null>;
108
+ vendorReject(id: string, notes?: string): Promise<{
109
+ id: string;
110
+ subOrderId: string;
111
+ customerId: string | null;
112
+ reason: string;
113
+ description: string | null;
114
+ status: string;
115
+ lineItems: {
116
+ entityId: string;
117
+ quantity: number;
118
+ reason?: string;
119
+ }[] | null;
120
+ refundAmountCents: number | null;
121
+ vendorNotes: string | null;
122
+ trackingNumber: string | null;
123
+ requestedAt: Date;
124
+ resolvedAt: Date | null;
125
+ } | null>;
126
+ shipBack(id: string, trackingNumber: string): Promise<{
127
+ id: string;
128
+ subOrderId: string;
129
+ customerId: string | null;
130
+ reason: string;
131
+ description: string | null;
132
+ status: string;
133
+ lineItems: {
134
+ entityId: string;
135
+ quantity: number;
136
+ reason?: string;
137
+ }[] | null;
138
+ refundAmountCents: number | null;
139
+ vendorNotes: string | null;
140
+ trackingNumber: string | null;
141
+ requestedAt: Date;
142
+ resolvedAt: Date | null;
143
+ } | null>;
144
+ receive(id: string): Promise<{
145
+ id: string;
146
+ subOrderId: string;
147
+ customerId: string | null;
148
+ reason: string;
149
+ description: string | null;
150
+ status: string;
151
+ lineItems: {
152
+ entityId: string;
153
+ quantity: number;
154
+ reason?: string;
155
+ }[] | null;
156
+ refundAmountCents: number | null;
157
+ vendorNotes: string | null;
158
+ trackingNumber: string | null;
159
+ requestedAt: Date;
160
+ resolvedAt: Date | null;
161
+ } | null>;
162
+ refund(id: string, amountCents: number): Promise<{
163
+ id: string;
164
+ subOrderId: string;
165
+ customerId: string | null;
166
+ reason: string;
167
+ description: string | null;
168
+ status: string;
169
+ lineItems: {
170
+ entityId: string;
171
+ quantity: number;
172
+ reason?: string;
173
+ }[] | null;
174
+ refundAmountCents: number | null;
175
+ vendorNotes: string | null;
176
+ trackingNumber: string | null;
177
+ requestedAt: Date;
178
+ resolvedAt: Date | null;
179
+ } | null>;
180
+ }
181
+ //# sourceMappingURL=return.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"return.d.ts","sourceRoot":"","sources":["../../src/services/return.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAgB,MAAM,UAAU,CAAC;AAEjD,qBAAa,aAAa;IACZ,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,EAAE;IAEpB,OAAO,CAAC,IAAI,EAAE;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,KAAK,CAAC;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KAC5E;;;;;;;;;;;;;;;;;;IAWK,OAAO,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;IAKlB,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;;;;;;;;;;;;;;IAWvD,YAAY,CAAC,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;IAW7B,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;;;;IAQpD,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;;;;IASvC,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM;;;;;;;;;;;;;;;;;;IAQ3C,OAAO,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;IAOlB,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;;;;;;;;;;;;;;;;;;CAQ7C"}