@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.
Files changed (68) hide show
  1. package/bin/index.js +63 -0
  2. package/bin/mcp.js +63 -0
  3. package/dist/index.js +63 -0
  4. package/microservices/microservice-ads/package.json +27 -0
  5. package/microservices/microservice-ads/src/cli/index.ts +605 -0
  6. package/microservices/microservice-ads/src/db/campaigns.ts +797 -0
  7. package/microservices/microservice-ads/src/db/database.ts +93 -0
  8. package/microservices/microservice-ads/src/db/migrations.ts +60 -0
  9. package/microservices/microservice-ads/src/index.ts +39 -0
  10. package/microservices/microservice-ads/src/mcp/index.ts +480 -0
  11. package/microservices/microservice-contracts/package.json +27 -0
  12. package/microservices/microservice-contracts/src/cli/index.ts +770 -0
  13. package/microservices/microservice-contracts/src/db/contracts.ts +925 -0
  14. package/microservices/microservice-contracts/src/db/database.ts +93 -0
  15. package/microservices/microservice-contracts/src/db/migrations.ts +141 -0
  16. package/microservices/microservice-contracts/src/index.ts +43 -0
  17. package/microservices/microservice-contracts/src/mcp/index.ts +617 -0
  18. package/microservices/microservice-domains/package.json +27 -0
  19. package/microservices/microservice-domains/src/cli/index.ts +691 -0
  20. package/microservices/microservice-domains/src/db/database.ts +93 -0
  21. package/microservices/microservice-domains/src/db/domains.ts +1164 -0
  22. package/microservices/microservice-domains/src/db/migrations.ts +60 -0
  23. package/microservices/microservice-domains/src/index.ts +65 -0
  24. package/microservices/microservice-domains/src/mcp/index.ts +536 -0
  25. package/microservices/microservice-hiring/package.json +27 -0
  26. package/microservices/microservice-hiring/src/cli/index.ts +741 -0
  27. package/microservices/microservice-hiring/src/db/database.ts +93 -0
  28. package/microservices/microservice-hiring/src/db/hiring.ts +1085 -0
  29. package/microservices/microservice-hiring/src/db/migrations.ts +89 -0
  30. package/microservices/microservice-hiring/src/index.ts +80 -0
  31. package/microservices/microservice-hiring/src/lib/scoring.ts +206 -0
  32. package/microservices/microservice-hiring/src/mcp/index.ts +709 -0
  33. package/microservices/microservice-payments/package.json +27 -0
  34. package/microservices/microservice-payments/src/cli/index.ts +609 -0
  35. package/microservices/microservice-payments/src/db/database.ts +93 -0
  36. package/microservices/microservice-payments/src/db/migrations.ts +81 -0
  37. package/microservices/microservice-payments/src/db/payments.ts +1204 -0
  38. package/microservices/microservice-payments/src/index.ts +51 -0
  39. package/microservices/microservice-payments/src/mcp/index.ts +683 -0
  40. package/microservices/microservice-payroll/package.json +27 -0
  41. package/microservices/microservice-payroll/src/cli/index.ts +643 -0
  42. package/microservices/microservice-payroll/src/db/database.ts +93 -0
  43. package/microservices/microservice-payroll/src/db/migrations.ts +95 -0
  44. package/microservices/microservice-payroll/src/db/payroll.ts +1377 -0
  45. package/microservices/microservice-payroll/src/index.ts +48 -0
  46. package/microservices/microservice-payroll/src/mcp/index.ts +666 -0
  47. package/microservices/microservice-shipping/package.json +27 -0
  48. package/microservices/microservice-shipping/src/cli/index.ts +606 -0
  49. package/microservices/microservice-shipping/src/db/database.ts +93 -0
  50. package/microservices/microservice-shipping/src/db/migrations.ts +69 -0
  51. package/microservices/microservice-shipping/src/db/shipping.ts +1093 -0
  52. package/microservices/microservice-shipping/src/index.ts +53 -0
  53. package/microservices/microservice-shipping/src/mcp/index.ts +533 -0
  54. package/microservices/microservice-social/package.json +27 -0
  55. package/microservices/microservice-social/src/cli/index.ts +689 -0
  56. package/microservices/microservice-social/src/db/database.ts +93 -0
  57. package/microservices/microservice-social/src/db/migrations.ts +88 -0
  58. package/microservices/microservice-social/src/db/social.ts +1046 -0
  59. package/microservices/microservice-social/src/index.ts +46 -0
  60. package/microservices/microservice-social/src/mcp/index.ts +655 -0
  61. package/microservices/microservice-subscriptions/package.json +27 -0
  62. package/microservices/microservice-subscriptions/src/cli/index.ts +715 -0
  63. package/microservices/microservice-subscriptions/src/db/database.ts +93 -0
  64. package/microservices/microservice-subscriptions/src/db/migrations.ts +125 -0
  65. package/microservices/microservice-subscriptions/src/db/subscriptions.ts +1256 -0
  66. package/microservices/microservice-subscriptions/src/index.ts +41 -0
  67. package/microservices/microservice-subscriptions/src/mcp/index.ts +631 -0
  68. 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);