@hasna/microservices 0.0.3 → 0.0.5
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/bin/index.js +63 -0
- package/bin/mcp.js +63 -0
- package/dist/index.js +63 -0
- package/microservices/microservice-ads/package.json +27 -0
- package/microservices/microservice-ads/src/cli/index.ts +605 -0
- package/microservices/microservice-ads/src/db/campaigns.ts +797 -0
- package/microservices/microservice-ads/src/db/database.ts +93 -0
- package/microservices/microservice-ads/src/db/migrations.ts +60 -0
- package/microservices/microservice-ads/src/index.ts +39 -0
- package/microservices/microservice-ads/src/mcp/index.ts +480 -0
- package/microservices/microservice-contracts/package.json +27 -0
- package/microservices/microservice-contracts/src/cli/index.ts +770 -0
- package/microservices/microservice-contracts/src/db/contracts.ts +925 -0
- package/microservices/microservice-contracts/src/db/database.ts +93 -0
- package/microservices/microservice-contracts/src/db/migrations.ts +141 -0
- package/microservices/microservice-contracts/src/index.ts +43 -0
- package/microservices/microservice-contracts/src/mcp/index.ts +617 -0
- package/microservices/microservice-domains/package.json +27 -0
- package/microservices/microservice-domains/src/cli/index.ts +691 -0
- package/microservices/microservice-domains/src/db/database.ts +93 -0
- package/microservices/microservice-domains/src/db/domains.ts +1164 -0
- package/microservices/microservice-domains/src/db/migrations.ts +60 -0
- package/microservices/microservice-domains/src/index.ts +65 -0
- package/microservices/microservice-domains/src/mcp/index.ts +536 -0
- package/microservices/microservice-hiring/package.json +27 -0
- package/microservices/microservice-hiring/src/cli/index.ts +741 -0
- package/microservices/microservice-hiring/src/db/database.ts +93 -0
- package/microservices/microservice-hiring/src/db/hiring.ts +1085 -0
- package/microservices/microservice-hiring/src/db/migrations.ts +89 -0
- package/microservices/microservice-hiring/src/index.ts +80 -0
- package/microservices/microservice-hiring/src/lib/scoring.ts +206 -0
- package/microservices/microservice-hiring/src/mcp/index.ts +709 -0
- package/microservices/microservice-payments/package.json +27 -0
- package/microservices/microservice-payments/src/cli/index.ts +609 -0
- package/microservices/microservice-payments/src/db/database.ts +93 -0
- package/microservices/microservice-payments/src/db/migrations.ts +81 -0
- package/microservices/microservice-payments/src/db/payments.ts +1204 -0
- package/microservices/microservice-payments/src/index.ts +51 -0
- package/microservices/microservice-payments/src/mcp/index.ts +683 -0
- package/microservices/microservice-payroll/package.json +27 -0
- package/microservices/microservice-payroll/src/cli/index.ts +643 -0
- package/microservices/microservice-payroll/src/db/database.ts +93 -0
- package/microservices/microservice-payroll/src/db/migrations.ts +95 -0
- package/microservices/microservice-payroll/src/db/payroll.ts +1377 -0
- package/microservices/microservice-payroll/src/index.ts +48 -0
- package/microservices/microservice-payroll/src/mcp/index.ts +666 -0
- package/microservices/microservice-shipping/package.json +27 -0
- package/microservices/microservice-shipping/src/cli/index.ts +606 -0
- package/microservices/microservice-shipping/src/db/database.ts +93 -0
- package/microservices/microservice-shipping/src/db/migrations.ts +69 -0
- package/microservices/microservice-shipping/src/db/shipping.ts +1093 -0
- package/microservices/microservice-shipping/src/index.ts +53 -0
- package/microservices/microservice-shipping/src/mcp/index.ts +533 -0
- package/microservices/microservice-social/package.json +27 -0
- package/microservices/microservice-social/src/cli/index.ts +689 -0
- package/microservices/microservice-social/src/db/database.ts +93 -0
- package/microservices/microservice-social/src/db/migrations.ts +88 -0
- package/microservices/microservice-social/src/db/social.ts +1046 -0
- package/microservices/microservice-social/src/index.ts +46 -0
- package/microservices/microservice-social/src/mcp/index.ts +655 -0
- package/microservices/microservice-subscriptions/package.json +27 -0
- package/microservices/microservice-subscriptions/src/cli/index.ts +715 -0
- package/microservices/microservice-subscriptions/src/db/database.ts +93 -0
- package/microservices/microservice-subscriptions/src/db/migrations.ts +125 -0
- package/microservices/microservice-subscriptions/src/db/subscriptions.ts +1256 -0
- package/microservices/microservice-subscriptions/src/index.ts +41 -0
- package/microservices/microservice-subscriptions/src/mcp/index.ts +631 -0
- package/package.json +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hasna/microservice-payments",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Payment processing and tracking microservice with SQLite — manage payments, disputes, and payouts",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"microservice-payments": "./src/cli/index.ts",
|
|
8
|
+
"microservice-payments-mcp": "./src/mcp/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"dev": "bun run ./src/cli/index.ts",
|
|
15
|
+
"test": "bun test"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
19
|
+
"commander": "^12.1.0",
|
|
20
|
+
"zod": "^3.24.0"
|
|
21
|
+
},
|
|
22
|
+
"license": "Apache-2.0",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"registry": "https://registry.npmjs.org",
|
|
25
|
+
"access": "public"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import {
|
|
5
|
+
createPayment,
|
|
6
|
+
getPayment,
|
|
7
|
+
listPayments,
|
|
8
|
+
updatePayment,
|
|
9
|
+
deletePayment,
|
|
10
|
+
refundPayment,
|
|
11
|
+
searchPayments,
|
|
12
|
+
getRevenueReport,
|
|
13
|
+
getRevenueByCustomer,
|
|
14
|
+
reconcileWithInvoice,
|
|
15
|
+
getPaymentStats,
|
|
16
|
+
getBalanceByProvider,
|
|
17
|
+
autoReconcile,
|
|
18
|
+
retryPayment,
|
|
19
|
+
listRetries,
|
|
20
|
+
getRetryStats,
|
|
21
|
+
convertCurrency,
|
|
22
|
+
feeAnalysis,
|
|
23
|
+
declineReport,
|
|
24
|
+
addDisputeEvidence,
|
|
25
|
+
splitPayment,
|
|
26
|
+
revenueForecast,
|
|
27
|
+
findReconciliationGaps,
|
|
28
|
+
type PaymentType,
|
|
29
|
+
type PaymentStatus,
|
|
30
|
+
type PaymentProvider,
|
|
31
|
+
} from "../db/payments.js";
|
|
32
|
+
import {
|
|
33
|
+
listDisputes,
|
|
34
|
+
respondDispute,
|
|
35
|
+
type DisputeStatus,
|
|
36
|
+
} from "../db/payments.js";
|
|
37
|
+
import {
|
|
38
|
+
createPayout,
|
|
39
|
+
listPayouts,
|
|
40
|
+
type PayoutStatus,
|
|
41
|
+
} from "../db/payments.js";
|
|
42
|
+
|
|
43
|
+
const program = new Command();
|
|
44
|
+
|
|
45
|
+
program
|
|
46
|
+
.name("microservice-payments")
|
|
47
|
+
.description("Payment processing and tracking microservice")
|
|
48
|
+
.version("0.0.1");
|
|
49
|
+
|
|
50
|
+
// --- Payments ---
|
|
51
|
+
|
|
52
|
+
const paymentCmd = program
|
|
53
|
+
.command("payment")
|
|
54
|
+
.description("Payment management");
|
|
55
|
+
|
|
56
|
+
paymentCmd
|
|
57
|
+
.command("create")
|
|
58
|
+
.description("Create a new payment")
|
|
59
|
+
.requiredOption("--type <type>", "Payment type (charge/refund/transfer/payout)")
|
|
60
|
+
.requiredOption("--amount <amount>", "Amount")
|
|
61
|
+
.option("--currency <currency>", "Currency code", "USD")
|
|
62
|
+
.option("--status <status>", "Status")
|
|
63
|
+
.option("--customer-name <name>", "Customer name")
|
|
64
|
+
.option("--customer-email <email>", "Customer email")
|
|
65
|
+
.option("--description <desc>", "Description")
|
|
66
|
+
.option("--provider <provider>", "Provider (stripe/square/mercury/manual)")
|
|
67
|
+
.option("--provider-id <id>", "Provider transaction ID")
|
|
68
|
+
.option("--invoice-id <id>", "Invoice ID")
|
|
69
|
+
.option("--json", "Output as JSON", false)
|
|
70
|
+
.action((opts) => {
|
|
71
|
+
const payment = createPayment({
|
|
72
|
+
type: opts.type as PaymentType,
|
|
73
|
+
amount: parseFloat(opts.amount),
|
|
74
|
+
currency: opts.currency,
|
|
75
|
+
status: opts.status as PaymentStatus | undefined,
|
|
76
|
+
customer_name: opts.customerName,
|
|
77
|
+
customer_email: opts.customerEmail,
|
|
78
|
+
description: opts.description,
|
|
79
|
+
provider: opts.provider as PaymentProvider | undefined,
|
|
80
|
+
provider_id: opts.providerId,
|
|
81
|
+
invoice_id: opts.invoiceId,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (opts.json) {
|
|
85
|
+
console.log(JSON.stringify(payment, null, 2));
|
|
86
|
+
} else {
|
|
87
|
+
console.log(`Created payment: ${payment.type} $${payment.amount} ${payment.currency} (${payment.id})`);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
paymentCmd
|
|
92
|
+
.command("list")
|
|
93
|
+
.description("List payments")
|
|
94
|
+
.option("--status <status>", "Filter by status")
|
|
95
|
+
.option("--type <type>", "Filter by type")
|
|
96
|
+
.option("--provider <provider>", "Filter by provider")
|
|
97
|
+
.option("--limit <n>", "Limit results")
|
|
98
|
+
.option("--json", "Output as JSON", false)
|
|
99
|
+
.action((opts) => {
|
|
100
|
+
const payments = listPayments({
|
|
101
|
+
status: opts.status as PaymentStatus | undefined,
|
|
102
|
+
type: opts.type as PaymentType | undefined,
|
|
103
|
+
provider: opts.provider as PaymentProvider | undefined,
|
|
104
|
+
limit: opts.limit ? parseInt(opts.limit) : undefined,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (opts.json) {
|
|
108
|
+
console.log(JSON.stringify(payments, null, 2));
|
|
109
|
+
} else {
|
|
110
|
+
if (payments.length === 0) {
|
|
111
|
+
console.log("No payments found.");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
for (const p of payments) {
|
|
115
|
+
const customer = p.customer_name || p.customer_email || "—";
|
|
116
|
+
console.log(` ${p.type} $${p.amount} ${p.currency} [${p.status}] ${customer} (${p.id})`);
|
|
117
|
+
}
|
|
118
|
+
console.log(`\n${payments.length} payment(s)`);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
paymentCmd
|
|
123
|
+
.command("get")
|
|
124
|
+
.description("Get a payment by ID")
|
|
125
|
+
.argument("<id>", "Payment ID")
|
|
126
|
+
.option("--json", "Output as JSON", false)
|
|
127
|
+
.action((id, opts) => {
|
|
128
|
+
const payment = getPayment(id);
|
|
129
|
+
if (!payment) {
|
|
130
|
+
console.error(`Payment '${id}' not found.`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (opts.json) {
|
|
135
|
+
console.log(JSON.stringify(payment, null, 2));
|
|
136
|
+
} else {
|
|
137
|
+
console.log(`${payment.type} — $${payment.amount} ${payment.currency}`);
|
|
138
|
+
console.log(` Status: ${payment.status}`);
|
|
139
|
+
if (payment.customer_name) console.log(` Customer: ${payment.customer_name}`);
|
|
140
|
+
if (payment.customer_email) console.log(` Email: ${payment.customer_email}`);
|
|
141
|
+
if (payment.description) console.log(` Description: ${payment.description}`);
|
|
142
|
+
if (payment.provider) console.log(` Provider: ${payment.provider}`);
|
|
143
|
+
if (payment.invoice_id) console.log(` Invoice: ${payment.invoice_id}`);
|
|
144
|
+
console.log(` Created: ${payment.created_at}`);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
paymentCmd
|
|
149
|
+
.command("refund")
|
|
150
|
+
.description("Refund a payment")
|
|
151
|
+
.argument("<id>", "Payment ID to refund")
|
|
152
|
+
.option("--amount <amount>", "Partial refund amount")
|
|
153
|
+
.option("--json", "Output as JSON", false)
|
|
154
|
+
.action((id, opts) => {
|
|
155
|
+
const refund = refundPayment(id, opts.amount ? parseFloat(opts.amount) : undefined);
|
|
156
|
+
if (!refund) {
|
|
157
|
+
console.error(`Cannot refund payment '${id}' — not found or not succeeded.`);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (opts.json) {
|
|
162
|
+
console.log(JSON.stringify(refund, null, 2));
|
|
163
|
+
} else {
|
|
164
|
+
console.log(`Refunded $${refund.amount} ${refund.currency} (${refund.id})`);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// --- Payment Retry ---
|
|
169
|
+
|
|
170
|
+
paymentCmd
|
|
171
|
+
.command("retry")
|
|
172
|
+
.description("Retry a failed payment")
|
|
173
|
+
.argument("<id>", "Payment ID to retry")
|
|
174
|
+
.option("--json", "Output as JSON", false)
|
|
175
|
+
.action((id, opts) => {
|
|
176
|
+
const attempt = retryPayment(id);
|
|
177
|
+
if (!attempt) {
|
|
178
|
+
console.error(`Cannot retry payment '${id}' — not found or not failed.`);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (opts.json) {
|
|
183
|
+
console.log(JSON.stringify(attempt, null, 2));
|
|
184
|
+
} else {
|
|
185
|
+
console.log(`Retry attempt #${attempt.attempt} for payment ${id}: ${attempt.status}`);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
paymentCmd
|
|
190
|
+
.command("retries")
|
|
191
|
+
.description("List retry attempts for a payment")
|
|
192
|
+
.argument("<paymentId>", "Payment ID")
|
|
193
|
+
.option("--json", "Output as JSON", false)
|
|
194
|
+
.action((paymentId, opts) => {
|
|
195
|
+
const retries = listRetries(paymentId);
|
|
196
|
+
|
|
197
|
+
if (opts.json) {
|
|
198
|
+
console.log(JSON.stringify(retries, null, 2));
|
|
199
|
+
} else {
|
|
200
|
+
if (retries.length === 0) {
|
|
201
|
+
console.log("No retry attempts found.");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
for (const r of retries) {
|
|
205
|
+
console.log(` Attempt #${r.attempt} [${r.status}] ${r.attempted_at || ""} ${r.error || ""}`);
|
|
206
|
+
}
|
|
207
|
+
console.log(`\n${retries.length} retry attempt(s)`);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// --- Payment Convert ---
|
|
212
|
+
|
|
213
|
+
paymentCmd
|
|
214
|
+
.command("convert")
|
|
215
|
+
.description("Convert currency amount")
|
|
216
|
+
.requiredOption("--from <currency>", "Source currency")
|
|
217
|
+
.requiredOption("--to <currency>", "Target currency")
|
|
218
|
+
.requiredOption("--amount <amount>", "Amount to convert")
|
|
219
|
+
.option("--json", "Output as JSON", false)
|
|
220
|
+
.action((opts) => {
|
|
221
|
+
const result = convertCurrency(parseFloat(opts.amount), opts.from, opts.to);
|
|
222
|
+
if (!result) {
|
|
223
|
+
console.error(`Unsupported currency pair: ${opts.from} -> ${opts.to}`);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (opts.json) {
|
|
228
|
+
console.log(JSON.stringify(result, null, 2));
|
|
229
|
+
} else {
|
|
230
|
+
console.log(`${result.original_amount} ${result.from} = ${result.converted_amount} ${result.to} (rate: ${result.rate})`);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// --- Decline Report ---
|
|
235
|
+
|
|
236
|
+
paymentCmd
|
|
237
|
+
.command("decline-report")
|
|
238
|
+
.description("Show decline analytics for failed payments")
|
|
239
|
+
.option("--provider <provider>", "Filter by provider")
|
|
240
|
+
.option("--json", "Output as JSON", false)
|
|
241
|
+
.action((opts) => {
|
|
242
|
+
const report = declineReport(opts.provider as PaymentProvider | undefined);
|
|
243
|
+
|
|
244
|
+
if (opts.json) {
|
|
245
|
+
console.log(JSON.stringify(report, null, 2));
|
|
246
|
+
} else {
|
|
247
|
+
if (report.entries.length === 0) {
|
|
248
|
+
console.log("No declined payments found.");
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
console.log("Decline Report:");
|
|
252
|
+
for (const e of report.entries) {
|
|
253
|
+
console.log(` [${e.provider}] "${e.description || "No description"}" — ${e.count} decline(s), $${e.total_amount.toFixed(2)}`);
|
|
254
|
+
}
|
|
255
|
+
console.log(`\nTotal: ${report.total_declined} decline(s), $${report.total_amount.toFixed(2)}`);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// --- Payment Split ---
|
|
260
|
+
|
|
261
|
+
paymentCmd
|
|
262
|
+
.command("split")
|
|
263
|
+
.description("Split a payment into marketplace commission splits")
|
|
264
|
+
.argument("<id>", "Payment ID")
|
|
265
|
+
.requiredOption("--splits <json>", "Split percentages as JSON (e.g. '{\"vendor1\":70,\"vendor2\":30}')")
|
|
266
|
+
.option("--json", "Output as JSON", false)
|
|
267
|
+
.action((id, opts) => {
|
|
268
|
+
let splits: Record<string, number>;
|
|
269
|
+
try {
|
|
270
|
+
splits = JSON.parse(opts.splits);
|
|
271
|
+
} catch {
|
|
272
|
+
console.error("Invalid JSON for --splits");
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const payment = splitPayment(id, splits);
|
|
277
|
+
if (!payment) {
|
|
278
|
+
console.error(`Payment '${id}' not found.`);
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (opts.json) {
|
|
283
|
+
console.log(JSON.stringify(payment, null, 2));
|
|
284
|
+
} else {
|
|
285
|
+
console.log(`Payment ${id} split recorded: ${JSON.stringify(splits)}`);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// --- Disputes ---
|
|
290
|
+
|
|
291
|
+
const disputeCmd = program
|
|
292
|
+
.command("dispute")
|
|
293
|
+
.description("Dispute management");
|
|
294
|
+
|
|
295
|
+
disputeCmd
|
|
296
|
+
.command("list")
|
|
297
|
+
.description("List disputes")
|
|
298
|
+
.option("--status <status>", "Filter by status (open/under_review/won/lost)")
|
|
299
|
+
.option("--json", "Output as JSON", false)
|
|
300
|
+
.action((opts) => {
|
|
301
|
+
const disputes = listDisputes(opts.status as DisputeStatus | undefined);
|
|
302
|
+
|
|
303
|
+
if (opts.json) {
|
|
304
|
+
console.log(JSON.stringify(disputes, null, 2));
|
|
305
|
+
} else {
|
|
306
|
+
if (disputes.length === 0) {
|
|
307
|
+
console.log("No disputes found.");
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
for (const d of disputes) {
|
|
311
|
+
const amount = d.amount != null ? ` $${d.amount}` : "";
|
|
312
|
+
console.log(` [${d.status}]${amount} — ${d.reason || "No reason"} (${d.id})`);
|
|
313
|
+
}
|
|
314
|
+
console.log(`\n${disputes.length} dispute(s)`);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
disputeCmd
|
|
319
|
+
.command("respond")
|
|
320
|
+
.description("Respond to a dispute")
|
|
321
|
+
.argument("<id>", "Dispute ID")
|
|
322
|
+
.requiredOption("--status <status>", "New status (open/under_review/won/lost)")
|
|
323
|
+
.option("--json", "Output as JSON", false)
|
|
324
|
+
.action((id, opts) => {
|
|
325
|
+
const dispute = respondDispute(id, { status: opts.status as DisputeStatus });
|
|
326
|
+
if (!dispute) {
|
|
327
|
+
console.error(`Dispute '${id}' not found.`);
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (opts.json) {
|
|
332
|
+
console.log(JSON.stringify(dispute, null, 2));
|
|
333
|
+
} else {
|
|
334
|
+
console.log(`Dispute ${id} updated to: ${dispute.status}`);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
disputeCmd
|
|
339
|
+
.command("add-evidence")
|
|
340
|
+
.description("Add evidence to a dispute")
|
|
341
|
+
.argument("<id>", "Dispute ID")
|
|
342
|
+
.requiredOption("--description <desc>", "Evidence description")
|
|
343
|
+
.option("--file-ref <path>", "File reference path")
|
|
344
|
+
.option("--json", "Output as JSON", false)
|
|
345
|
+
.action((id, opts) => {
|
|
346
|
+
const dispute = addDisputeEvidence(id, opts.description, opts.fileRef);
|
|
347
|
+
if (!dispute) {
|
|
348
|
+
console.error(`Dispute '${id}' not found.`);
|
|
349
|
+
process.exit(1);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (opts.json) {
|
|
353
|
+
console.log(JSON.stringify(dispute, null, 2));
|
|
354
|
+
} else {
|
|
355
|
+
console.log(`Evidence added to dispute ${id}: "${opts.description}"`);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// --- Payouts ---
|
|
360
|
+
|
|
361
|
+
const payoutCmd = program
|
|
362
|
+
.command("payout")
|
|
363
|
+
.description("Payout management");
|
|
364
|
+
|
|
365
|
+
payoutCmd
|
|
366
|
+
.command("list")
|
|
367
|
+
.description("List payouts")
|
|
368
|
+
.option("--status <status>", "Filter by status (pending/in_transit/paid/failed)")
|
|
369
|
+
.option("--json", "Output as JSON", false)
|
|
370
|
+
.action((opts) => {
|
|
371
|
+
const payouts = listPayouts(opts.status as PayoutStatus | undefined);
|
|
372
|
+
|
|
373
|
+
if (opts.json) {
|
|
374
|
+
console.log(JSON.stringify(payouts, null, 2));
|
|
375
|
+
} else {
|
|
376
|
+
if (payouts.length === 0) {
|
|
377
|
+
console.log("No payouts found.");
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
for (const p of payouts) {
|
|
381
|
+
const dest = p.destination || "—";
|
|
382
|
+
console.log(` $${p.amount} ${p.currency} [${p.status}] -> ${dest} (${p.id})`);
|
|
383
|
+
}
|
|
384
|
+
console.log(`\n${payouts.length} payout(s)`);
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
payoutCmd
|
|
389
|
+
.command("initiate")
|
|
390
|
+
.description("Initiate a new payout")
|
|
391
|
+
.requiredOption("--amount <amount>", "Amount")
|
|
392
|
+
.option("--currency <currency>", "Currency code", "USD")
|
|
393
|
+
.option("--destination <dest>", "Destination (bank account, etc.)")
|
|
394
|
+
.option("--json", "Output as JSON", false)
|
|
395
|
+
.action((opts) => {
|
|
396
|
+
const payout = createPayout({
|
|
397
|
+
amount: parseFloat(opts.amount),
|
|
398
|
+
currency: opts.currency,
|
|
399
|
+
destination: opts.destination,
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
if (opts.json) {
|
|
403
|
+
console.log(JSON.stringify(payout, null, 2));
|
|
404
|
+
} else {
|
|
405
|
+
console.log(`Initiated payout: $${payout.amount} ${payout.currency} (${payout.id})`);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// --- Balance ---
|
|
410
|
+
|
|
411
|
+
program
|
|
412
|
+
.command("balance")
|
|
413
|
+
.description("Show balance by provider")
|
|
414
|
+
.option("--json", "Output as JSON", false)
|
|
415
|
+
.action((opts) => {
|
|
416
|
+
const balances = getBalanceByProvider();
|
|
417
|
+
|
|
418
|
+
if (opts.json) {
|
|
419
|
+
console.log(JSON.stringify(balances, null, 2));
|
|
420
|
+
} else {
|
|
421
|
+
if (balances.length === 0) {
|
|
422
|
+
console.log("No payment data.");
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
for (const b of balances) {
|
|
426
|
+
console.log(` ${b.provider}: charges $${b.total_charges}, refunds $${b.total_refunds}, net $${b.net_balance}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// --- Revenue ---
|
|
432
|
+
|
|
433
|
+
const revenueCmd = program
|
|
434
|
+
.command("revenue")
|
|
435
|
+
.description("Revenue reports and forecasting");
|
|
436
|
+
|
|
437
|
+
revenueCmd
|
|
438
|
+
.command("report")
|
|
439
|
+
.description("Revenue report for a period")
|
|
440
|
+
.requiredOption("--period <YYYY-MM>", "Month period (e.g. 2024-01)")
|
|
441
|
+
.option("--json", "Output as JSON", false)
|
|
442
|
+
.action((opts) => {
|
|
443
|
+
const [year, month] = opts.period.split("-").map(Number);
|
|
444
|
+
const startDate = `${year}-${String(month).padStart(2, "0")}-01`;
|
|
445
|
+
const lastDay = new Date(year, month, 0).getDate();
|
|
446
|
+
const endDate = `${year}-${String(month).padStart(2, "0")}-${String(lastDay).padStart(2, "0")} 23:59:59`;
|
|
447
|
+
|
|
448
|
+
const report = getRevenueReport(startDate, endDate);
|
|
449
|
+
|
|
450
|
+
if (opts.json) {
|
|
451
|
+
console.log(JSON.stringify(report, null, 2));
|
|
452
|
+
} else {
|
|
453
|
+
console.log(`Revenue Report: ${opts.period}`);
|
|
454
|
+
console.log(` Total Revenue: $${report.total_revenue.toFixed(2)}`);
|
|
455
|
+
console.log(` Total Refunds: $${report.total_refunds.toFixed(2)}`);
|
|
456
|
+
console.log(` Net Revenue: $${report.net_revenue.toFixed(2)}`);
|
|
457
|
+
console.log(` Payments: ${report.payment_count} Refunds: ${report.refund_count}`);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
revenueCmd
|
|
462
|
+
.command("forecast")
|
|
463
|
+
.description("Project revenue for upcoming months based on recent trends")
|
|
464
|
+
.requiredOption("--months <n>", "Number of months to forecast")
|
|
465
|
+
.option("--json", "Output as JSON", false)
|
|
466
|
+
.action((opts) => {
|
|
467
|
+
const result = revenueForecast(parseInt(opts.months));
|
|
468
|
+
|
|
469
|
+
if (opts.json) {
|
|
470
|
+
console.log(JSON.stringify(result, null, 2));
|
|
471
|
+
} else {
|
|
472
|
+
console.log(`Revenue Forecast (${result.months_projected} months, trend: ${result.trend})`);
|
|
473
|
+
console.log(" Historical:");
|
|
474
|
+
for (const h of result.historical) {
|
|
475
|
+
console.log(` ${h.month}: $${h.revenue.toFixed(2)}`);
|
|
476
|
+
}
|
|
477
|
+
console.log(" Forecast:");
|
|
478
|
+
for (const f of result.forecast) {
|
|
479
|
+
console.log(` ${f.month}: $${f.projected_revenue.toFixed(2)} (projected)`);
|
|
480
|
+
}
|
|
481
|
+
console.log(` Average Monthly Revenue: $${result.average_monthly_revenue.toFixed(2)}`);
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// --- Reconcile ---
|
|
486
|
+
|
|
487
|
+
const reconcileCmd = program
|
|
488
|
+
.command("reconcile")
|
|
489
|
+
.description("Reconciliation commands");
|
|
490
|
+
|
|
491
|
+
reconcileCmd
|
|
492
|
+
.command("link")
|
|
493
|
+
.description("Link a payment to an invoice")
|
|
494
|
+
.requiredOption("--payment <id>", "Payment ID")
|
|
495
|
+
.requiredOption("--invoice <id>", "Invoice ID")
|
|
496
|
+
.option("--json", "Output as JSON", false)
|
|
497
|
+
.action((opts) => {
|
|
498
|
+
const payment = reconcileWithInvoice(opts.payment, opts.invoice);
|
|
499
|
+
if (!payment) {
|
|
500
|
+
console.error(`Payment '${opts.payment}' not found.`);
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (opts.json) {
|
|
505
|
+
console.log(JSON.stringify(payment, null, 2));
|
|
506
|
+
} else {
|
|
507
|
+
console.log(`Linked payment ${payment.id} to invoice ${payment.invoice_id}`);
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
reconcileCmd
|
|
512
|
+
.command("auto")
|
|
513
|
+
.description("Auto-reconcile payments to invoices by amount, email, and date")
|
|
514
|
+
.option("--from <date>", "Start date (YYYY-MM-DD)")
|
|
515
|
+
.option("--to <date>", "End date (YYYY-MM-DD)")
|
|
516
|
+
.option("--json", "Output as JSON", false)
|
|
517
|
+
.action((opts) => {
|
|
518
|
+
const result = autoReconcile(opts.from, opts.to);
|
|
519
|
+
|
|
520
|
+
if (opts.json) {
|
|
521
|
+
console.log(JSON.stringify(result, null, 2));
|
|
522
|
+
} else {
|
|
523
|
+
console.log("Auto-Reconciliation Results:");
|
|
524
|
+
console.log(` Matched: ${result.matched.length}`);
|
|
525
|
+
for (const m of result.matched) {
|
|
526
|
+
console.log(` Payment ${m.payment_id} -> Invoice ${m.invoice_id} (${m.confidence})`);
|
|
527
|
+
}
|
|
528
|
+
console.log(` Unmatched Payments: ${result.unmatched_payments.length}`);
|
|
529
|
+
console.log(` Unmatched Invoices: ${result.unmatched_invoices.length}`);
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
reconcileCmd
|
|
534
|
+
.command("check-gaps")
|
|
535
|
+
.description("Find reconciliation gaps — payments without invoices and vice versa")
|
|
536
|
+
.requiredOption("--from <date>", "Start date (YYYY-MM-DD)")
|
|
537
|
+
.requiredOption("--to <date>", "End date (YYYY-MM-DD)")
|
|
538
|
+
.option("--json", "Output as JSON", false)
|
|
539
|
+
.action((opts) => {
|
|
540
|
+
const gaps = findReconciliationGaps(opts.from, opts.to);
|
|
541
|
+
|
|
542
|
+
if (opts.json) {
|
|
543
|
+
console.log(JSON.stringify(gaps, null, 2));
|
|
544
|
+
} else {
|
|
545
|
+
console.log("Reconciliation Gaps:");
|
|
546
|
+
console.log(` Payments without invoice: ${gaps.payments_without_invoice.length}`);
|
|
547
|
+
for (const p of gaps.payments_without_invoice) {
|
|
548
|
+
console.log(` $${p.amount} ${p.currency} — ${p.customer_email || "no email"} (${p.id})`);
|
|
549
|
+
}
|
|
550
|
+
console.log(` Invoice IDs without succeeded payment: ${gaps.invoice_ids_without_payment.length}`);
|
|
551
|
+
for (const inv of gaps.invoice_ids_without_payment) {
|
|
552
|
+
console.log(` ${inv}`);
|
|
553
|
+
}
|
|
554
|
+
console.log(` Total gaps: ${gaps.gap_count}`);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
// --- Fees ---
|
|
559
|
+
|
|
560
|
+
program
|
|
561
|
+
.command("fees")
|
|
562
|
+
.description("Fee analysis by provider for a given month")
|
|
563
|
+
.requiredOption("--month <YYYY-MM>", "Month period (e.g. 2026-03)")
|
|
564
|
+
.option("--json", "Output as JSON", false)
|
|
565
|
+
.action((opts) => {
|
|
566
|
+
const result = feeAnalysis(opts.month);
|
|
567
|
+
|
|
568
|
+
if (opts.json) {
|
|
569
|
+
console.log(JSON.stringify(result, null, 2));
|
|
570
|
+
} else {
|
|
571
|
+
console.log(`Fee Analysis: ${result.month}`);
|
|
572
|
+
for (const p of result.providers) {
|
|
573
|
+
console.log(` ${p.provider}: gross $${p.gross.toFixed(2)}, fees $${p.fees.toFixed(2)}, net $${p.net.toFixed(2)} (${p.transaction_count} txns)`);
|
|
574
|
+
}
|
|
575
|
+
console.log(` Total: gross $${result.total_gross.toFixed(2)}, fees $${result.total_fees.toFixed(2)}, net $${result.total_net.toFixed(2)}`);
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// --- Stats ---
|
|
580
|
+
|
|
581
|
+
program
|
|
582
|
+
.command("stats")
|
|
583
|
+
.description("Payment statistics")
|
|
584
|
+
.option("--json", "Output as JSON", false)
|
|
585
|
+
.action((opts) => {
|
|
586
|
+
const stats = getPaymentStats();
|
|
587
|
+
|
|
588
|
+
if (opts.json) {
|
|
589
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
590
|
+
} else {
|
|
591
|
+
console.log("Payment Statistics:");
|
|
592
|
+
console.log(` Total Payments: ${stats.total_payments}`);
|
|
593
|
+
console.log(` Charges: ${stats.total_charges}`);
|
|
594
|
+
console.log(` Refunds: ${stats.total_refunds}`);
|
|
595
|
+
console.log(` Transfers: ${stats.total_transfers}`);
|
|
596
|
+
console.log(` Payouts: ${stats.total_payouts}`);
|
|
597
|
+
console.log(` Total Amount: $${stats.total_amount.toFixed(2)}`);
|
|
598
|
+
console.log(" By Status:");
|
|
599
|
+
for (const [s, c] of Object.entries(stats.by_status)) {
|
|
600
|
+
console.log(` ${s}: ${c}`);
|
|
601
|
+
}
|
|
602
|
+
console.log(" By Provider:");
|
|
603
|
+
for (const [p, c] of Object.entries(stats.by_provider)) {
|
|
604
|
+
console.log(` ${p}: ${c}`);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
program.parse(process.argv);
|