pay-lobster 1.0.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 (120) hide show
  1. package/README.md +401 -0
  2. package/README.md.bak +401 -0
  3. package/dist/agent.d.ts +132 -0
  4. package/dist/agent.d.ts.map +1 -0
  5. package/dist/agent.js +224 -0
  6. package/dist/agent.js.map +1 -0
  7. package/dist/analytics.d.ts +120 -0
  8. package/dist/analytics.d.ts.map +1 -0
  9. package/dist/analytics.js +345 -0
  10. package/dist/analytics.js.map +1 -0
  11. package/dist/approvals.d.ts +168 -0
  12. package/dist/approvals.d.ts.map +1 -0
  13. package/dist/approvals.js +406 -0
  14. package/dist/approvals.js.map +1 -0
  15. package/dist/circle-client.d.ts +152 -0
  16. package/dist/circle-client.d.ts.map +1 -0
  17. package/dist/circle-client.js +266 -0
  18. package/dist/circle-client.js.map +1 -0
  19. package/dist/commission.d.ts +191 -0
  20. package/dist/commission.d.ts.map +1 -0
  21. package/dist/commission.js +475 -0
  22. package/dist/commission.js.map +1 -0
  23. package/dist/condition-builder.d.ts +98 -0
  24. package/dist/condition-builder.d.ts.map +1 -0
  25. package/dist/condition-builder.js +193 -0
  26. package/dist/condition-builder.js.map +1 -0
  27. package/dist/contacts.d.ts +179 -0
  28. package/dist/contacts.d.ts.map +1 -0
  29. package/dist/contacts.js +445 -0
  30. package/dist/contacts.js.map +1 -0
  31. package/dist/easy.d.ts +22 -0
  32. package/dist/easy.d.ts.map +1 -0
  33. package/dist/easy.js +40 -0
  34. package/dist/easy.js.map +1 -0
  35. package/dist/erc8004/constants.d.ts +152 -0
  36. package/dist/erc8004/constants.d.ts.map +1 -0
  37. package/dist/erc8004/constants.js +114 -0
  38. package/dist/erc8004/constants.js.map +1 -0
  39. package/dist/erc8004/discovery.d.ts +84 -0
  40. package/dist/erc8004/discovery.d.ts.map +1 -0
  41. package/dist/erc8004/discovery.js +217 -0
  42. package/dist/erc8004/discovery.js.map +1 -0
  43. package/dist/erc8004/identity.d.ts +91 -0
  44. package/dist/erc8004/identity.d.ts.map +1 -0
  45. package/dist/erc8004/identity.js +250 -0
  46. package/dist/erc8004/identity.js.map +1 -0
  47. package/dist/erc8004/index.d.ts +147 -0
  48. package/dist/erc8004/index.d.ts.map +1 -0
  49. package/dist/erc8004/index.js +225 -0
  50. package/dist/erc8004/index.js.map +1 -0
  51. package/dist/erc8004/reputation.d.ts +133 -0
  52. package/dist/erc8004/reputation.d.ts.map +1 -0
  53. package/dist/erc8004/reputation.js +277 -0
  54. package/dist/erc8004/reputation.js.map +1 -0
  55. package/dist/escrow-templates.d.ts +38 -0
  56. package/dist/escrow-templates.d.ts.map +1 -0
  57. package/dist/escrow-templates.js +419 -0
  58. package/dist/escrow-templates.js.map +1 -0
  59. package/dist/escrow.d.ts +320 -0
  60. package/dist/escrow.d.ts.map +1 -0
  61. package/dist/escrow.js +854 -0
  62. package/dist/escrow.js.map +1 -0
  63. package/dist/index.d.ts +11 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +33 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/invoices.d.ts +212 -0
  68. package/dist/invoices.d.ts.map +1 -0
  69. package/dist/invoices.js +393 -0
  70. package/dist/invoices.js.map +1 -0
  71. package/dist/notifications.d.ts +141 -0
  72. package/dist/notifications.d.ts.map +1 -0
  73. package/dist/notifications.js +350 -0
  74. package/dist/notifications.js.map +1 -0
  75. package/dist/tips.d.ts +171 -0
  76. package/dist/tips.d.ts.map +1 -0
  77. package/dist/tips.js +390 -0
  78. package/dist/tips.js.map +1 -0
  79. package/dist/types.d.ts +100 -0
  80. package/dist/types.d.ts.map +1 -0
  81. package/dist/types.js +6 -0
  82. package/dist/types.js.map +1 -0
  83. package/dist/x402-client.d.ts +127 -0
  84. package/dist/x402-client.d.ts.map +1 -0
  85. package/dist/x402-client.js +350 -0
  86. package/dist/x402-client.js.map +1 -0
  87. package/dist/x402-server.d.ts +133 -0
  88. package/dist/x402-server.d.ts.map +1 -0
  89. package/dist/x402-server.js +330 -0
  90. package/dist/x402-server.js.map +1 -0
  91. package/lib/agent.ts +273 -0
  92. package/lib/analytics.ts +474 -0
  93. package/lib/analytics.ts.bak +474 -0
  94. package/lib/approvals.ts +585 -0
  95. package/lib/approvals.ts.bak +585 -0
  96. package/lib/circle-client.ts +376 -0
  97. package/lib/circle-client.ts.bak +376 -0
  98. package/lib/commission.ts +680 -0
  99. package/lib/commission.ts.bak +680 -0
  100. package/lib/condition-builder.ts +223 -0
  101. package/lib/condition-builder.ts.bak +223 -0
  102. package/lib/contacts.ts +615 -0
  103. package/lib/contacts.ts.bak +615 -0
  104. package/lib/easy.ts +46 -0
  105. package/lib/easy.ts.bak +352 -0
  106. package/lib/erc8004/constants.ts +175 -0
  107. package/lib/erc8004/discovery.ts +299 -0
  108. package/lib/erc8004/identity.ts +327 -0
  109. package/lib/erc8004/index.ts +285 -0
  110. package/lib/erc8004/reputation.ts +368 -0
  111. package/lib/escrow-templates.ts +462 -0
  112. package/lib/escrow.ts +1216 -0
  113. package/lib/index.ts +13 -0
  114. package/lib/invoices.ts +588 -0
  115. package/lib/notifications.ts +484 -0
  116. package/lib/tips.ts +570 -0
  117. package/lib/types.ts +108 -0
  118. package/lib/x402-client.ts +471 -0
  119. package/lib/x402-server.ts +462 -0
  120. package/package.json +58 -0
package/lib/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Pay Lobster - Payment Infrastructure for AI Agents
3
+ * The Stripe for autonomous agents on Base
4
+ *
5
+ * @packageDocumentation
6
+ */
7
+
8
+ export { LobsterAgent } from './agent';
9
+ export { createLobsterAgent, quickStart } from './easy';
10
+ export * from './types';
11
+
12
+ // Default export for convenience
13
+ export { LobsterAgent as default } from './agent';
@@ -0,0 +1,588 @@
1
+ /**
2
+ * Invoice & Payment Request System
3
+ *
4
+ * Generate, track, and manage USDC invoices and payment requests.
5
+ */
6
+
7
+ import crypto from 'crypto';
8
+ import fs from 'fs/promises';
9
+ import path from 'path';
10
+
11
+ export interface Invoice {
12
+ id: string;
13
+ number: string;
14
+ status: 'draft' | 'sent' | 'viewed' | 'paid' | 'cancelled' | 'overdue';
15
+
16
+ // Parties
17
+ from: {
18
+ name: string;
19
+ email?: string;
20
+ walletAddress: string;
21
+ };
22
+ to: {
23
+ name: string;
24
+ email?: string;
25
+ walletAddress?: string;
26
+ };
27
+
28
+ // Amounts
29
+ items: InvoiceItem[];
30
+ subtotal: string;
31
+ tax?: string;
32
+ taxRate?: number;
33
+ total: string;
34
+ currency: 'USDC';
35
+
36
+ // Payment
37
+ paymentChain: string;
38
+ paymentAddress: string;
39
+ paymentLink?: string;
40
+ x402PaymentUrl?: string; // x402-enabled payment URL
41
+ txHash?: string;
42
+ paidAt?: string;
43
+ paidAmount?: string;
44
+
45
+ // Metadata
46
+ memo?: string;
47
+ dueDate?: string;
48
+ createdAt: string;
49
+ updatedAt: string;
50
+ sentAt?: string;
51
+ viewedAt?: string;
52
+ }
53
+
54
+ export interface InvoiceItem {
55
+ description: string;
56
+ quantity: number;
57
+ unitPrice: string;
58
+ total: string;
59
+ }
60
+
61
+ export interface PaymentRequest {
62
+ id: string;
63
+ type: 'one-time' | 'recurring';
64
+ amount: string;
65
+ description: string;
66
+ fromName: string;
67
+ toAddress: string;
68
+ chain: string;
69
+ status: 'pending' | 'paid' | 'cancelled' | 'expired';
70
+ expiresAt?: string;
71
+ paymentLink: string;
72
+ createdAt: string;
73
+ paidAt?: string;
74
+ txHash?: string;
75
+ }
76
+
77
+ export interface RecurringPayment {
78
+ id: string;
79
+ name: string;
80
+ amount: string;
81
+ toAddress: string;
82
+ toName: string;
83
+ chain: string;
84
+ frequency: 'daily' | 'weekly' | 'biweekly' | 'monthly' | 'quarterly' | 'yearly';
85
+ nextPaymentDate: string;
86
+ startDate: string;
87
+ endDate?: string;
88
+ status: 'active' | 'paused' | 'cancelled' | 'completed';
89
+ walletId: string;
90
+ payments: { date: string; txHash: string; amount: string }[];
91
+ createdAt: string;
92
+ }
93
+
94
+ const DATA_DIR = process.env.USDC_DATA_DIR || './data';
95
+
96
+ /**
97
+ * Invoice Manager
98
+ */
99
+ export class InvoiceManager {
100
+ private dataPath: string;
101
+
102
+ constructor(dataDir = DATA_DIR) {
103
+ this.dataPath = path.join(dataDir, 'invoices.json');
104
+ }
105
+
106
+ private async loadInvoices(): Promise<Invoice[]> {
107
+ try {
108
+ const data = await fs.readFile(this.dataPath, 'utf-8');
109
+ return JSON.parse(data);
110
+ } catch {
111
+ return [];
112
+ }
113
+ }
114
+
115
+ private async saveInvoices(invoices: Invoice[]): Promise<void> {
116
+ await fs.mkdir(path.dirname(this.dataPath), { recursive: true });
117
+ await fs.writeFile(this.dataPath, JSON.stringify(invoices, null, 2));
118
+ }
119
+
120
+ /**
121
+ * Create a new invoice
122
+ */
123
+ async create(params: {
124
+ from: Invoice['from'];
125
+ to: Invoice['to'];
126
+ items: { description: string; quantity: number; unitPrice: string }[];
127
+ taxRate?: number;
128
+ memo?: string;
129
+ dueDate?: string;
130
+ chain?: string;
131
+ }): Promise<Invoice> {
132
+ const invoices = await this.loadInvoices();
133
+ const invoiceNumber = `INV-${Date.now().toString(36).toUpperCase()}`;
134
+
135
+ const items: InvoiceItem[] = params.items.map(item => ({
136
+ ...item,
137
+ total: (item.quantity * parseFloat(item.unitPrice)).toFixed(2),
138
+ }));
139
+
140
+ const subtotal = items.reduce((sum, item) => sum + parseFloat(item.total), 0);
141
+ const tax = params.taxRate ? subtotal * (params.taxRate / 100) : 0;
142
+ const total = subtotal + tax;
143
+
144
+ const invoice: Invoice = {
145
+ id: crypto.randomUUID(),
146
+ number: invoiceNumber,
147
+ status: 'draft',
148
+ from: params.from,
149
+ to: params.to,
150
+ items,
151
+ subtotal: subtotal.toFixed(2),
152
+ tax: tax > 0 ? tax.toFixed(2) : undefined,
153
+ taxRate: params.taxRate,
154
+ total: total.toFixed(2),
155
+ currency: 'USDC',
156
+ paymentChain: params.chain || 'ETH-SEPOLIA',
157
+ paymentAddress: params.from.walletAddress,
158
+ memo: params.memo,
159
+ dueDate: params.dueDate,
160
+ createdAt: new Date().toISOString(),
161
+ updatedAt: new Date().toISOString(),
162
+ };
163
+
164
+ // Generate payment link
165
+ invoice.paymentLink = this.generatePaymentLink(invoice);
166
+
167
+ // Generate x402 payment URL
168
+ invoice.x402PaymentUrl = this.generateX402PaymentUrl(invoice);
169
+
170
+ invoices.push(invoice);
171
+ await this.saveInvoices(invoices);
172
+
173
+ return invoice;
174
+ }
175
+
176
+ /**
177
+ * Get invoice by ID or number
178
+ */
179
+ async get(idOrNumber: string): Promise<Invoice | null> {
180
+ const invoices = await this.loadInvoices();
181
+ return invoices.find(inv =>
182
+ inv.id === idOrNumber || inv.number === idOrNumber
183
+ ) || null;
184
+ }
185
+
186
+ /**
187
+ * List invoices with optional filters
188
+ */
189
+ async list(filters?: {
190
+ status?: Invoice['status'];
191
+ toName?: string;
192
+ fromDate?: string;
193
+ toDate?: string;
194
+ }): Promise<Invoice[]> {
195
+ let invoices = await this.loadInvoices();
196
+
197
+ if (filters?.status) {
198
+ invoices = invoices.filter(inv => inv.status === filters.status);
199
+ }
200
+ if (filters?.toName) {
201
+ invoices = invoices.filter(inv =>
202
+ inv.to.name.toLowerCase().includes(filters.toName!.toLowerCase())
203
+ );
204
+ }
205
+ if (filters?.fromDate) {
206
+ invoices = invoices.filter(inv => inv.createdAt >= filters.fromDate!);
207
+ }
208
+ if (filters?.toDate) {
209
+ invoices = invoices.filter(inv => inv.createdAt <= filters.toDate!);
210
+ }
211
+
212
+ return invoices.sort((a, b) =>
213
+ new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
214
+ );
215
+ }
216
+
217
+ /**
218
+ * Mark invoice as sent
219
+ */
220
+ async markSent(id: string): Promise<Invoice | null> {
221
+ const invoices = await this.loadInvoices();
222
+ const invoice = invoices.find(inv => inv.id === id);
223
+
224
+ if (invoice) {
225
+ invoice.status = 'sent';
226
+ invoice.sentAt = new Date().toISOString();
227
+ invoice.updatedAt = new Date().toISOString();
228
+ await this.saveInvoices(invoices);
229
+ }
230
+
231
+ return invoice || null;
232
+ }
233
+
234
+ /**
235
+ * Mark invoice as paid
236
+ */
237
+ async markPaid(id: string, txHash: string, amount?: string): Promise<Invoice | null> {
238
+ const invoices = await this.loadInvoices();
239
+ const invoice = invoices.find(inv => inv.id === id);
240
+
241
+ if (invoice) {
242
+ invoice.status = 'paid';
243
+ invoice.txHash = txHash;
244
+ invoice.paidAt = new Date().toISOString();
245
+ invoice.paidAmount = amount || invoice.total;
246
+ invoice.updatedAt = new Date().toISOString();
247
+ await this.saveInvoices(invoices);
248
+ }
249
+
250
+ return invoice || null;
251
+ }
252
+
253
+ /**
254
+ * Cancel invoice
255
+ */
256
+ async cancel(id: string): Promise<Invoice | null> {
257
+ const invoices = await this.loadInvoices();
258
+ const invoice = invoices.find(inv => inv.id === id);
259
+
260
+ if (invoice && invoice.status !== 'paid') {
261
+ invoice.status = 'cancelled';
262
+ invoice.updatedAt = new Date().toISOString();
263
+ await this.saveInvoices(invoices);
264
+ }
265
+
266
+ return invoice || null;
267
+ }
268
+
269
+ /**
270
+ * Generate payment link for invoice
271
+ */
272
+ private generatePaymentLink(invoice: Invoice): string {
273
+ // EIP-681 style payment request (simplified)
274
+ const params = new URLSearchParams({
275
+ to: invoice.paymentAddress,
276
+ value: invoice.total,
277
+ chain: invoice.paymentChain,
278
+ ref: invoice.number,
279
+ });
280
+ return `usdc://pay?${params.toString()}`;
281
+ }
282
+
283
+ /**
284
+ * Generate x402-enabled payment URL for invoice
285
+ * Returns a URL that triggers 402 Payment Required
286
+ */
287
+ private generateX402PaymentUrl(invoice: Invoice): string {
288
+ // This would point to your x402-enabled invoice payment endpoint
289
+ const baseUrl = process.env.X402_BASE_URL || 'https://api.lobster-pay.com';
290
+ return `${baseUrl}/invoices/${invoice.id}/pay`;
291
+ }
292
+
293
+ /**
294
+ * Add x402 payment method to invoice
295
+ * Creates a payment-gated endpoint for this invoice
296
+ */
297
+ async enableX402Payment(
298
+ invoiceId: string,
299
+ options?: {
300
+ baseUrl?: string;
301
+ expiryHours?: number;
302
+ }
303
+ ): Promise<string> {
304
+ const invoice = await this.get(invoiceId);
305
+ if (!invoice) {
306
+ throw new Error('Invoice not found');
307
+ }
308
+
309
+ const baseUrl = options?.baseUrl || process.env.X402_BASE_URL || 'https://api.lobster-pay.com';
310
+ const x402Url = `${baseUrl}/invoices/${invoice.id}/pay`;
311
+
312
+ invoice.x402PaymentUrl = x402Url;
313
+ invoice.updatedAt = new Date().toISOString();
314
+
315
+ const invoices = await this.loadInvoices();
316
+ const index = invoices.findIndex(inv => inv.id === invoiceId);
317
+ if (index !== -1) {
318
+ invoices[index] = invoice;
319
+ await this.saveInvoices(invoices);
320
+ }
321
+
322
+ return x402Url;
323
+ }
324
+
325
+ /**
326
+ * Format invoice as text for messaging
327
+ */
328
+ formatInvoiceText(invoice: Invoice): string {
329
+ let text = `📄 **Invoice ${invoice.number}**\n\n`;
330
+ text += `To: ${invoice.to.name}\n`;
331
+ text += `Status: ${invoice.status.toUpperCase()}\n\n`;
332
+
333
+ text += `**Items:**\n`;
334
+ for (const item of invoice.items) {
335
+ text += `• ${item.description} (x${item.quantity}) — $${item.total}\n`;
336
+ }
337
+
338
+ text += `\nSubtotal: $${invoice.subtotal} USDC\n`;
339
+ if (invoice.tax) {
340
+ text += `Tax (${invoice.taxRate}%): $${invoice.tax} USDC\n`;
341
+ }
342
+ text += `**Total: $${invoice.total} USDC**\n\n`;
343
+
344
+ if (invoice.dueDate) {
345
+ text += `Due: ${new Date(invoice.dueDate).toLocaleDateString()}\n`;
346
+ }
347
+
348
+ text += `\nPay to: \`${invoice.paymentAddress}\`\n`;
349
+ text += `Chain: ${invoice.paymentChain}\n`;
350
+
351
+ if (invoice.memo) {
352
+ text += `\nMemo: ${invoice.memo}\n`;
353
+ }
354
+
355
+ return text;
356
+ }
357
+
358
+ /**
359
+ * Check for overdue invoices
360
+ */
361
+ async checkOverdue(): Promise<Invoice[]> {
362
+ const invoices = await this.loadInvoices();
363
+ const now = new Date();
364
+ const overdue: Invoice[] = [];
365
+
366
+ for (const invoice of invoices) {
367
+ if (
368
+ invoice.status === 'sent' &&
369
+ invoice.dueDate &&
370
+ new Date(invoice.dueDate) < now
371
+ ) {
372
+ invoice.status = 'overdue';
373
+ invoice.updatedAt = now.toISOString();
374
+ overdue.push(invoice);
375
+ }
376
+ }
377
+
378
+ if (overdue.length > 0) {
379
+ await this.saveInvoices(invoices);
380
+ }
381
+
382
+ return overdue;
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Recurring Payment Manager
388
+ */
389
+ export class RecurringPaymentManager {
390
+ private dataPath: string;
391
+
392
+ constructor(dataDir = DATA_DIR) {
393
+ this.dataPath = path.join(dataDir, 'recurring.json');
394
+ }
395
+
396
+ private async loadPayments(): Promise<RecurringPayment[]> {
397
+ try {
398
+ const data = await fs.readFile(this.dataPath, 'utf-8');
399
+ return JSON.parse(data);
400
+ } catch {
401
+ return [];
402
+ }
403
+ }
404
+
405
+ private async savePayments(payments: RecurringPayment[]): Promise<void> {
406
+ await fs.mkdir(path.dirname(this.dataPath), { recursive: true });
407
+ await fs.writeFile(this.dataPath, JSON.stringify(payments, null, 2));
408
+ }
409
+
410
+ /**
411
+ * Schedule a recurring payment
412
+ */
413
+ async schedule(params: {
414
+ name: string;
415
+ amount: string;
416
+ toAddress: string;
417
+ toName: string;
418
+ chain: string;
419
+ frequency: RecurringPayment['frequency'];
420
+ startDate?: string;
421
+ endDate?: string;
422
+ walletId: string;
423
+ }): Promise<RecurringPayment> {
424
+ const payments = await this.loadPayments();
425
+
426
+ const startDate = params.startDate || new Date().toISOString().split('T')[0];
427
+ const nextPaymentDate = this.calculateNextDate(startDate, params.frequency);
428
+
429
+ const payment: RecurringPayment = {
430
+ id: crypto.randomUUID(),
431
+ name: params.name,
432
+ amount: params.amount,
433
+ toAddress: params.toAddress,
434
+ toName: params.toName,
435
+ chain: params.chain,
436
+ frequency: params.frequency,
437
+ startDate,
438
+ endDate: params.endDate,
439
+ nextPaymentDate,
440
+ status: 'active',
441
+ walletId: params.walletId,
442
+ payments: [],
443
+ createdAt: new Date().toISOString(),
444
+ };
445
+
446
+ payments.push(payment);
447
+ await this.savePayments(payments);
448
+
449
+ return payment;
450
+ }
451
+
452
+ /**
453
+ * Get all due payments
454
+ */
455
+ async getDuePayments(): Promise<RecurringPayment[]> {
456
+ const payments = await this.loadPayments();
457
+ const today = new Date().toISOString().split('T')[0];
458
+
459
+ return payments.filter(p =>
460
+ p.status === 'active' &&
461
+ p.nextPaymentDate <= today &&
462
+ (!p.endDate || p.endDate >= today)
463
+ );
464
+ }
465
+
466
+ /**
467
+ * Record a payment execution
468
+ */
469
+ async recordExecution(id: string, txHash: string): Promise<RecurringPayment | null> {
470
+ const payments = await this.loadPayments();
471
+ const payment = payments.find(p => p.id === id);
472
+
473
+ if (payment) {
474
+ payment.payments.push({
475
+ date: new Date().toISOString(),
476
+ txHash,
477
+ amount: payment.amount,
478
+ });
479
+
480
+ // Calculate next payment date
481
+ payment.nextPaymentDate = this.calculateNextDate(
482
+ payment.nextPaymentDate,
483
+ payment.frequency
484
+ );
485
+
486
+ // Check if completed
487
+ if (payment.endDate && payment.nextPaymentDate > payment.endDate) {
488
+ payment.status = 'completed';
489
+ }
490
+
491
+ await this.savePayments(payments);
492
+ }
493
+
494
+ return payment || null;
495
+ }
496
+
497
+ /**
498
+ * Pause recurring payment
499
+ */
500
+ async pause(id: string): Promise<RecurringPayment | null> {
501
+ const payments = await this.loadPayments();
502
+ const payment = payments.find(p => p.id === id);
503
+
504
+ if (payment && payment.status === 'active') {
505
+ payment.status = 'paused';
506
+ await this.savePayments(payments);
507
+ }
508
+
509
+ return payment || null;
510
+ }
511
+
512
+ /**
513
+ * Resume recurring payment
514
+ */
515
+ async resume(id: string): Promise<RecurringPayment | null> {
516
+ const payments = await this.loadPayments();
517
+ const payment = payments.find(p => p.id === id);
518
+
519
+ if (payment && payment.status === 'paused') {
520
+ payment.status = 'active';
521
+ // Recalculate next payment from today
522
+ payment.nextPaymentDate = this.calculateNextDate(
523
+ new Date().toISOString().split('T')[0],
524
+ payment.frequency
525
+ );
526
+ await this.savePayments(payments);
527
+ }
528
+
529
+ return payment || null;
530
+ }
531
+
532
+ /**
533
+ * Cancel recurring payment
534
+ */
535
+ async cancel(id: string): Promise<RecurringPayment | null> {
536
+ const payments = await this.loadPayments();
537
+ const payment = payments.find(p => p.id === id);
538
+
539
+ if (payment) {
540
+ payment.status = 'cancelled';
541
+ await this.savePayments(payments);
542
+ }
543
+
544
+ return payment || null;
545
+ }
546
+
547
+ /**
548
+ * List all recurring payments
549
+ */
550
+ async list(status?: RecurringPayment['status']): Promise<RecurringPayment[]> {
551
+ const payments = await this.loadPayments();
552
+ return status
553
+ ? payments.filter(p => p.status === status)
554
+ : payments;
555
+ }
556
+
557
+ /**
558
+ * Calculate next payment date based on frequency
559
+ */
560
+ private calculateNextDate(fromDate: string, frequency: RecurringPayment['frequency']): string {
561
+ const date = new Date(fromDate);
562
+
563
+ switch (frequency) {
564
+ case 'daily':
565
+ date.setDate(date.getDate() + 1);
566
+ break;
567
+ case 'weekly':
568
+ date.setDate(date.getDate() + 7);
569
+ break;
570
+ case 'biweekly':
571
+ date.setDate(date.getDate() + 14);
572
+ break;
573
+ case 'monthly':
574
+ date.setMonth(date.getMonth() + 1);
575
+ break;
576
+ case 'quarterly':
577
+ date.setMonth(date.getMonth() + 3);
578
+ break;
579
+ case 'yearly':
580
+ date.setFullYear(date.getFullYear() + 1);
581
+ break;
582
+ }
583
+
584
+ return date.toISOString().split('T')[0];
585
+ }
586
+ }
587
+
588
+ export default { InvoiceManager, RecurringPaymentManager };