@profullstack/coinpay 0.6.11 → 0.9.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.
@@ -0,0 +1,445 @@
1
+ /**
2
+ * Finances — the merchant's money in one place.
3
+ *
4
+ * Read-side wrappers over the CoinPay API for everything that has a dollar
5
+ * sign on it: linked bank and card accounts (SimpleFIN / Plaid), the ledger,
6
+ * crypto payments, card payments, escrows, invoices and payouts. Plus
7
+ * `buildFinanceSnapshot`, a pure function that turns those responses into the
8
+ * headline numbers a dashboard wants (earnings, commission paid, refunds,
9
+ * cashflow, outstanding invoices, escrow held).
10
+ *
11
+ * Every call needs the merchant session token (`coinpay login`), not a
12
+ * business API key: the bank-data routes deliberately refuse API keys.
13
+ */
14
+
15
+ const CRYPTO_SUCCESS = new Set(['confirmed', 'completed', 'forwarded', 'forwarding']);
16
+ const CRYPTO_FAILED = new Set(['failed', 'expired', 'forwarding_failed', 'settle_failed', 'settlement_failed']);
17
+ const CRYPTO_PENDING = new Set(['pending', 'detected', 'confirming']);
18
+ const CARD_SUCCESS = new Set(['completed', 'succeeded']);
19
+ const CARD_FAILED = new Set(['failed', 'canceled', 'cancelled', 'requires_payment_method']);
20
+ const CARD_REFUNDED = new Set(['refunded', 'partially_refunded']);
21
+
22
+ function query(params) {
23
+ const search = new URLSearchParams();
24
+ for (const [key, value] of Object.entries(params)) {
25
+ if (value === undefined || value === null || value === '') continue;
26
+ search.set(key, String(value));
27
+ }
28
+ const s = search.toString();
29
+ return s ? `?${s}` : '';
30
+ }
31
+
32
+ /** Map a day window onto the analytics `period` values the API understands. */
33
+ export function periodForDays(days) {
34
+ if (!Number.isFinite(days) || days <= 0) return 'all';
35
+ if (days <= 7) return '7d';
36
+ if (days <= 30) return '30d';
37
+ if (days <= 90) return '90d';
38
+ if (days <= 365) return '1y';
39
+ return 'all';
40
+ }
41
+
42
+ // ── Bank & card accounts (SimpleFIN / Plaid) ──
43
+
44
+ /** Balance sheet + cashflow headline. `days` sets the cashflow window. */
45
+ export async function getFinanceSummary(client, { days = 30, includeHidden = false } = {}) {
46
+ const data = await client.request(`/finances/summary${query({ days, hidden: includeHidden ? 1 : undefined })}`);
47
+ return data.summary;
48
+ }
49
+
50
+ /** Every linked account with its current balance. */
51
+ export async function listFinanceAccounts(client, { includeHidden = false } = {}) {
52
+ const data = await client.request(`/finances/accounts${query({ hidden: includeHidden ? 1 : undefined })}`);
53
+ return data.accounts || [];
54
+ }
55
+
56
+ /** The ledger, newest first. Returns `{ rows, total, limit, offset }`. */
57
+ export async function listFinanceTransactions(client, filters = {}) {
58
+ return client.request(
59
+ `/finances/transactions${query({
60
+ account: filters.accountId,
61
+ search: filters.search,
62
+ category: filters.category,
63
+ start: filters.startDate,
64
+ end: filters.endDate,
65
+ pending: filters.includePending === false ? 0 : undefined,
66
+ limit: filters.limit,
67
+ offset: filters.offset,
68
+ })}`,
69
+ );
70
+ }
71
+
72
+ /** Linked institutions and their last sync outcome. */
73
+ export async function listFinanceConnections(client) {
74
+ return client.request('/finances/connections');
75
+ }
76
+
77
+ /**
78
+ * Pull fresh balances and transactions from the bank bridge. This spends
79
+ * request budget (SimpleFIN allows ~24/day per connection), so it is a
80
+ * deliberate action, never something a screen does on its own.
81
+ */
82
+ export async function syncFinances(client, { days, connectionId } = {}) {
83
+ return client.request('/finances/sync', {
84
+ method: 'POST',
85
+ body: JSON.stringify({ days, connectionId }),
86
+ });
87
+ }
88
+
89
+ // ── Payments, cards, payouts ──
90
+
91
+ /** Merchant dashboard headline (plan, commission rate, recent payments). */
92
+ export async function getDashboardStats(client, { businessId } = {}) {
93
+ return client.request(`/dashboard/stats${query({ business_id: businessId })}`);
94
+ }
95
+
96
+ /** Crypto + card analytics for a period (`7d`, `30d`, `90d`, `1y`, `all`). */
97
+ export async function getFinanceAnalytics(client, { period = '30d', businessId } = {}) {
98
+ const data = await client.request(`/stripe/analytics${query({ period, business_id: businessId })}`);
99
+ return data.analytics;
100
+ }
101
+
102
+ /** Crypto payments across every business the merchant can see. */
103
+ export async function listCryptoPayments(client, { limit = 100, offset = 0, status, businessId, dateFrom, dateTo } = {}) {
104
+ return client.request(`/payments${query({ limit, offset, status, business_id: businessId, date_from: dateFrom, date_to: dateTo })}`);
105
+ }
106
+
107
+ /** Card (Stripe) charges across every business the merchant can see. */
108
+ export async function listCardTransactions(client, { limit = 100, offset = 0, status, businessId, dateFrom, dateTo } = {}) {
109
+ return client.request(`/stripe/transactions${query({ limit, offset, status, business_id: businessId, date_from: dateFrom, date_to: dateTo })}`);
110
+ }
111
+
112
+ /** Card payouts to the merchant's bank. */
113
+ export async function listCardPayouts(client, { limit = 50, offset = 0, status } = {}) {
114
+ return client.request(`/stripe/payouts${query({ limit, offset, status })}`);
115
+ }
116
+
117
+ // ── Snapshot ──
118
+
119
+ function num(value) {
120
+ const n = typeof value === 'number' ? value : parseFloat(value ?? '');
121
+ return Number.isFinite(n) ? n : 0;
122
+ }
123
+
124
+ function within(dateLike, since) {
125
+ if (!since) return true;
126
+ const t = new Date(dateLike || 0).getTime();
127
+ return Number.isFinite(t) && t >= since.getTime();
128
+ }
129
+
130
+ function round2(n) {
131
+ return Math.round(n * 100) / 100;
132
+ }
133
+
134
+ /** USD value of a crypto payment's fee, pro-rated from the crypto amounts. */
135
+ export function cryptoFeeUsd(payment) {
136
+ const fee = num(payment.fee_amount);
137
+ const crypto = num(payment.amount_crypto ?? payment.crypto_amount);
138
+ const usd = num(payment.amount_usd ?? payment.amount);
139
+ if (fee > 0 && crypto > 0 && usd > 0) return (fee / crypto) * usd;
140
+ return 0;
141
+ }
142
+
143
+ /**
144
+ * Turn raw API responses into the numbers a dashboard shows.
145
+ *
146
+ * Pure: no I/O, so it is unit-testable and the TUI can re-run it on every
147
+ * refresh without touching the network. Any input may be missing (a route
148
+ * that failed) and the corresponding section is then empty rather than the
149
+ * whole snapshot failing.
150
+ */
151
+ export function buildFinanceSnapshot(raw = {}, { days = 30, now = new Date() } = {}) {
152
+ const since = Number.isFinite(days) && days > 0 ? new Date(now.getTime() - days * 86400000) : null;
153
+ const errors = { ...(raw.errors || {}) };
154
+
155
+ // ── Crypto payments ──
156
+ const payments = raw.payments?.payments || [];
157
+ const windowPayments = payments.filter((p) => within(p.created_at, since));
158
+ const cryptoOk = windowPayments.filter((p) => CRYPTO_SUCCESS.has(String(p.status).toLowerCase()));
159
+ const cryptoFailed = windowPayments.filter((p) => CRYPTO_FAILED.has(String(p.status).toLowerCase()));
160
+ const cryptoPending = windowPayments.filter((p) => CRYPTO_PENDING.has(String(p.status).toLowerCase()));
161
+ const crypto = {
162
+ volumeUsd: round2(cryptoOk.reduce((s, p) => s + num(p.amount_usd), 0)),
163
+ feesUsd: round2(cryptoOk.reduce((s, p) => s + cryptoFeeUsd(p), 0)),
164
+ successful: cryptoOk.length,
165
+ failed: cryptoFailed.length,
166
+ pending: cryptoPending.length,
167
+ total: windowPayments.length,
168
+ byChain: {},
169
+ // The list is a page; the server-side analytics below cover the full window.
170
+ partial: Boolean(raw.payments?.pagination?.has_more),
171
+ };
172
+ for (const p of cryptoOk) {
173
+ const chain = p.currency || 'unknown';
174
+ crypto.byChain[chain] = round2((crypto.byChain[chain] || 0) + num(p.amount_usd));
175
+ }
176
+
177
+ // ── Card payments ──
178
+ const cardTx = raw.cardTransactions?.transactions || [];
179
+ const windowCards = cardTx.filter((t) => within(t.created_at, since));
180
+ const cardOk = windowCards.filter((t) => CARD_SUCCESS.has(String(t.status).toLowerCase()));
181
+ const cardFailed = windowCards.filter((t) => CARD_FAILED.has(String(t.status).toLowerCase()));
182
+ const cardRefunded = windowCards.filter((t) => CARD_REFUNDED.has(String(t.status).toLowerCase()));
183
+ const cents = (t, key) => num(t[key]) / 100;
184
+ const card = {
185
+ volumeUsd: round2(cardOk.reduce((s, t) => s + cents(t, 'amount_cents'), 0)),
186
+ platformFeesUsd: round2(cardOk.reduce((s, t) => s + cents(t, 'platform_fee_amount'), 0)),
187
+ stripeFeesUsd: round2(cardOk.reduce((s, t) => s + cents(t, 'stripe_fee_amount'), 0)),
188
+ netUsd: round2(cardOk.reduce((s, t) => s + cents(t, 'net_to_merchant'), 0)),
189
+ refundedUsd: round2(cardRefunded.reduce((s, t) => s + cents(t, 'amount_cents'), 0)),
190
+ successful: cardOk.length,
191
+ failed: cardFailed.length,
192
+ refunded: cardRefunded.length,
193
+ total: windowCards.length,
194
+ partial: Boolean(raw.cardTransactions?.pagination?.has_more),
195
+ };
196
+
197
+ // ── Server-side analytics (complete over the window, unlike the pages above) ──
198
+ const analytics = raw.analytics || null;
199
+ const series = (analytics?.series?.points || []).map((pt) => ({
200
+ label: pt.label,
201
+ volumeUsd: num(pt.total_volume_usd),
202
+ cryptoUsd: num(pt.crypto_volume_usd),
203
+ cardUsd: num(pt.card_volume_usd),
204
+ commissionUsd: num(pt.total_commission_usd),
205
+ count: num(pt.total_count),
206
+ }));
207
+
208
+ const grossVolumeUsd = analytics
209
+ ? num(analytics.combined?.total_volume_usd)
210
+ : round2(crypto.volumeUsd + card.volumeUsd);
211
+ const commissionUsd = analytics
212
+ ? num(analytics.combined?.total_fees_usd)
213
+ : round2(crypto.feesUsd + card.platformFeesUsd);
214
+
215
+ // ── Escrow ──
216
+ const escrows = raw.escrows?.escrows || raw.escrows?.data || (Array.isArray(raw.escrows) ? raw.escrows : []);
217
+ const escrowStatus = (e) => String(e.status || '').toLowerCase();
218
+ const escrowRefunded = escrows.filter((e) => escrowStatus(e) === 'refunded' && within(e.settled_at || e.refunded_at || e.updated_at, since));
219
+ const escrowHeld = escrows.filter((e) => ['funded', 'disputed', 'pending_release'].includes(escrowStatus(e)));
220
+ const escrowReleased = escrows.filter((e) => ['released', 'settled', 'completed'].includes(escrowStatus(e)) && within(e.settled_at || e.released_at || e.updated_at, since));
221
+ const escrowFeeUsd = (e) => {
222
+ if (!e.fee_tx_hash) return 0;
223
+ const fee = num(e.fee_amount);
224
+ const amount = num(e.amount);
225
+ return fee > 0 && amount > 0 ? (fee / amount) * num(e.amount_usd) : 0;
226
+ };
227
+ const escrow = {
228
+ heldUsd: round2(escrowHeld.reduce((s, e) => s + num(e.amount_usd), 0)),
229
+ held: escrowHeld.length,
230
+ releasedUsd: round2(escrowReleased.reduce((s, e) => s + num(e.amount_usd), 0)),
231
+ released: escrowReleased.length,
232
+ refundedUsd: round2(escrowRefunded.reduce((s, e) => s + num(e.amount_usd), 0)),
233
+ refunded: escrowRefunded.length,
234
+ feesUsd: round2(escrowReleased.reduce((s, e) => s + escrowFeeUsd(e), 0)),
235
+ total: escrows.length,
236
+ };
237
+
238
+ // ── Invoices ──
239
+ const invoices = raw.invoices?.invoices || raw.invoices?.data || (Array.isArray(raw.invoices) ? raw.invoices : []);
240
+ const invStatus = (i) => String(i.status || '').toLowerCase();
241
+ const isOverdue = (i) => ['sent', 'overdue'].includes(invStatus(i)) && i.due_date && new Date(i.due_date).getTime() < now.getTime();
242
+ const invoiceTotals = { draft: 0, outstanding: 0, overdue: 0, paid: 0, cancelled: 0 };
243
+ const invoiceCounts = { draft: 0, outstanding: 0, overdue: 0, paid: 0, cancelled: 0 };
244
+ for (const inv of invoices) {
245
+ const amount = num(inv.amount);
246
+ const status = invStatus(inv);
247
+ let bucket;
248
+ if (status === 'draft') bucket = 'draft';
249
+ else if (status === 'paid') {
250
+ if (!within(inv.paid_at || inv.updated_at, since)) continue;
251
+ bucket = 'paid';
252
+ } else if (status === 'cancelled' || status === 'canceled') bucket = 'cancelled';
253
+ else if (isOverdue(inv)) bucket = 'overdue';
254
+ else bucket = 'outstanding';
255
+ invoiceTotals[bucket] = round2(invoiceTotals[bucket] + amount);
256
+ invoiceCounts[bucket] += 1;
257
+ }
258
+
259
+ // ── Payouts (card rail → bank) ──
260
+ const payouts = raw.payouts?.payouts || [];
261
+ const windowPayouts = payouts.filter((p) => within(p.created_at, since));
262
+ const payout = {
263
+ paidUsd: round2(windowPayouts.filter((p) => String(p.status).toLowerCase() === 'paid').reduce((s, p) => s + num(p.amount_cents) / 100, 0)),
264
+ pendingUsd: round2(windowPayouts.filter((p) => ['pending', 'in_transit'].includes(String(p.status).toLowerCase())).reduce((s, p) => s + num(p.amount_cents) / 100, 0)),
265
+ count: windowPayouts.length,
266
+ };
267
+
268
+ // ── Bank & cards ──
269
+ const summary = raw.summary || null;
270
+ const accounts = (raw.accounts || []).slice().sort((a, b) => Math.abs(num(b.display_balance ?? b.balance)) - Math.abs(num(a.display_balance ?? a.balance)));
271
+ const primary = summary?.totals?.find((t) => t.currency === summary.primaryCurrency) || summary?.totals?.[0] || null;
272
+ const bank = {
273
+ currency: summary?.primaryCurrency || primary?.currency || 'USD',
274
+ assets: round2(num(primary?.assets)),
275
+ liabilities: round2(num(primary?.liabilities)),
276
+ net: round2(num(primary?.net)),
277
+ accountCount: summary?.accountCount ?? accounts.length,
278
+ hiddenCount: summary?.hiddenCount ?? 0,
279
+ transactionCount: summary?.transactionCount ?? 0,
280
+ cashflow: {
281
+ moneyIn: round2(num(summary?.cashflow?.moneyIn)),
282
+ moneyOut: round2(num(summary?.cashflow?.moneyOut)),
283
+ net: round2(num(summary?.cashflow?.net)),
284
+ transactions: summary?.cashflow?.transactions ?? 0,
285
+ },
286
+ byKind: summary?.byKind || [],
287
+ byInstitution: (summary?.byInstitution || []).slice().sort((a, b) => (b.assets + b.liabilities) - (a.assets + a.liabilities)),
288
+ topCategories: summary?.topCategories || [],
289
+ newestTransaction: summary?.newestTransaction || null,
290
+ accounts,
291
+ creditCards: accounts.filter((a) => (a.effective_kind || a.kind) === 'credit'),
292
+ connections: raw.connections?.connections || [],
293
+ plaidEnabled: Boolean(raw.connections?.plaidEnabled),
294
+ ledger: raw.transactions?.rows || [],
295
+ ledgerTotal: raw.transactions?.total ?? 0,
296
+ };
297
+
298
+ const refundsUsd = round2(card.refundedUsd + escrow.refundedUsd);
299
+ const stats = raw.stats || null;
300
+
301
+ // Debt against income. Computed server-side, because it needs six months of
302
+ // rows and the ledger this snapshot carries is one page of the window.
303
+ // Absent when the summary route failed, and the screen says so rather than
304
+ // drawing zeros.
305
+ const position = summary?.position || null;
306
+
307
+ return {
308
+ generatedAt: now.toISOString(),
309
+ windowDays: days,
310
+ plan: stats?.plan || null,
311
+ businesses: stats?.businesses || [],
312
+ earnings: {
313
+ grossVolumeUsd: round2(grossVolumeUsd),
314
+ cryptoVolumeUsd: analytics ? num(analytics.crypto?.total_volume_usd) : crypto.volumeUsd,
315
+ cardVolumeUsd: analytics ? num(analytics.card?.total_volume_usd) : card.volumeUsd,
316
+ commissionUsd: round2(commissionUsd),
317
+ stripeFeesUsd: card.stripeFeesUsd,
318
+ refundsUsd,
319
+ netUsd: round2(grossVolumeUsd - commissionUsd - card.stripeFeesUsd - refundsUsd),
320
+ transactions: analytics ? num(analytics.combined?.successful_transactions) : crypto.successful + card.successful,
321
+ failed: analytics ? num(analytics.combined?.failed_transactions) : crypto.failed + card.failed,
322
+ failureRate: analytics ? num(analytics.combined?.failure_rate) : 0,
323
+ },
324
+ series,
325
+ crypto,
326
+ card,
327
+ escrow,
328
+ invoices: { totals: invoiceTotals, counts: invoiceCounts, rows: invoices },
329
+ payout,
330
+ bank,
331
+ position,
332
+ recent: {
333
+ payments: payments.slice(0, 50),
334
+ cards: cardTx.slice(0, 50),
335
+ escrows: escrows.slice(0, 50),
336
+ },
337
+ errors,
338
+ };
339
+ }
340
+
341
+ /**
342
+ * Fetch everything and build a snapshot. Routes fail independently: a 500
343
+ * from one of them lands in `snapshot.errors[name]` and its section is empty.
344
+ */
345
+ export async function collectFinanceSnapshot(client, { days = 30, limit = 100, businessId } = {}) {
346
+ const since = Number.isFinite(days) && days > 0 ? new Date(Date.now() - days * 86400000).toISOString() : undefined;
347
+ const sources = {
348
+ summary: () => getFinanceSummary(client, { days }),
349
+ accounts: () => listFinanceAccounts(client),
350
+ transactions: () => listFinanceTransactions(client, { limit, startDate: since }),
351
+ connections: () => listFinanceConnections(client),
352
+ stats: () => getDashboardStats(client, { businessId }),
353
+ analytics: () => getFinanceAnalytics(client, { period: periodForDays(days), businessId }),
354
+ payments: () => listCryptoPayments(client, { limit, businessId, dateFrom: since }),
355
+ cardTransactions: () => listCardTransactions(client, { limit, businessId, dateFrom: since }),
356
+ // Raw route on purpose: the SDK's listEscrows camel-cases fields and the
357
+ // snapshot reads the API's snake_case shape like every other source.
358
+ escrows: () => client.request(`/escrow${query({ limit })}`),
359
+ invoices: () => client.request('/invoices'),
360
+ payouts: () => listCardPayouts(client, { limit }),
361
+ };
362
+
363
+ const names = Object.keys(sources);
364
+ const settled = await Promise.allSettled(names.map((name) => sources[name]()));
365
+ const raw = { errors: {} };
366
+ settled.forEach((result, i) => {
367
+ if (result.status === 'fulfilled') raw[names[i]] = result.value;
368
+ else raw.errors[names[i]] = result.reason?.message || String(result.reason);
369
+ });
370
+
371
+ // One failing source is a hole in the picture; every source failing means
372
+ // the API or the session is gone, and a page of zeros would be a lie.
373
+ if (Object.keys(raw.errors).length === names.length) {
374
+ const first = raw.errors[names[0]];
375
+ throw new Error(`Could not load finances: ${first}`);
376
+ }
377
+
378
+ return buildFinanceSnapshot(raw, { days });
379
+ }
380
+
381
+ /**
382
+ * Subscribe to the merchant's live payment stream (server-sent events).
383
+ *
384
+ * Takes the API base URL and the merchant session token explicitly (the
385
+ * client keeps its key private). `onEvent` receives parsed events (`connected`, `heartbeat`, `payment_*`).
386
+ * Returns a function that closes the stream. Reconnects itself with backoff
387
+ * until closed, so a dropped connection is a gap, not the end.
388
+ */
389
+ export function subscribeToPayments({ baseUrl, token, onEvent, onStatus, businessId } = {}) {
390
+ if (!baseUrl || !token) throw new Error('subscribeToPayments needs baseUrl and token');
391
+ baseUrl = baseUrl.replace(/\/$/, '');
392
+ let closed = false;
393
+ let controller = null;
394
+ let attempt = 0;
395
+
396
+ const status = (s, detail) => { try { onStatus?.(s, detail); } catch { /* listener error */ } };
397
+
398
+ async function connect() {
399
+ while (!closed) {
400
+ controller = new AbortController();
401
+ try {
402
+ status('connecting');
403
+ const res = await fetch(`${baseUrl}/realtime/payments${query({ businessId })}`, {
404
+ headers: { Authorization: `Bearer ${token}`, Accept: 'text/event-stream' },
405
+ signal: controller.signal,
406
+ });
407
+ if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
408
+ attempt = 0;
409
+ status('connected');
410
+ const reader = res.body.getReader();
411
+ const decoder = new TextDecoder();
412
+ let buffer = '';
413
+ for (;;) {
414
+ const { value, done } = await reader.read();
415
+ if (done) break;
416
+ buffer += decoder.decode(value, { stream: true });
417
+ let idx;
418
+ while ((idx = buffer.indexOf('\n\n')) !== -1) {
419
+ const chunk = buffer.slice(0, idx);
420
+ buffer = buffer.slice(idx + 2);
421
+ const data = chunk.split('\n').filter((l) => l.startsWith('data:')).map((l) => l.slice(5).trim()).join('\n');
422
+ if (!data) continue;
423
+ try { onEvent?.(JSON.parse(data)); } catch { /* not JSON */ }
424
+ }
425
+ }
426
+ if (!closed) status('disconnected');
427
+ } catch (err) {
428
+ if (closed) return;
429
+ status('error', err?.message || String(err));
430
+ }
431
+ if (closed) return;
432
+ attempt += 1;
433
+ const delay = Math.min(30000, 1000 * 2 ** Math.min(attempt, 5));
434
+ await new Promise((r) => setTimeout(r, delay));
435
+ }
436
+ }
437
+
438
+ void connect();
439
+
440
+ return () => {
441
+ closed = true;
442
+ try { controller?.abort(); } catch { /* already gone */ }
443
+ status('closed');
444
+ };
445
+ }
package/src/index.d.ts CHANGED
@@ -12,9 +12,22 @@ export type {
12
12
  PaymentParams,
13
13
  ListPaymentsParams,
14
14
  WaitForPaymentOptions,
15
- CreateBusinessParams
15
+ CreateBusinessParams,
16
+ SupportedCoinsParams,
17
+ SupportedCoin,
18
+ SupportedToken,
19
+ SupportedCoinsResponse,
20
+ TokensResponse,
16
21
  } from './client.js';
17
22
 
23
+ export {
24
+ getTokens,
25
+ getSupportedCoins,
26
+ } from './tokens.js';
27
+ export type {
28
+ TokenDiscoveryParams,
29
+ } from './tokens.js';
30
+
18
31
  // Payment exports
19
32
  export {
20
33
  createPayment,
@@ -31,6 +44,27 @@ export type {
31
44
  ListPaymentsFnParams
32
45
  } from './payments.js';
33
46
 
47
+ // Invoice exports
48
+ export {
49
+ createInvoice,
50
+ getInvoice,
51
+ listInvoices,
52
+ updateInvoice,
53
+ deleteInvoice,
54
+ publishInvoice,
55
+ sendInvoice,
56
+ getInvoicePaymentData,
57
+ InvoiceStatus,
58
+ } from './invoices.js';
59
+ export type {
60
+ Invoice,
61
+ InvoiceStatusType,
62
+ CreateInvoiceParams,
63
+ ListInvoicesParams,
64
+ UpdateInvoiceParams,
65
+ ListInvoicesResult,
66
+ } from './invoices.js';
67
+
34
68
  // Webhook exports
35
69
  export {
36
70
  verifyWebhookSignature,
@@ -113,3 +147,5 @@ export type {
113
147
 
114
148
  import { CoinPayClient } from './client.js';
115
149
  export default CoinPayClient;
150
+
151
+ export * from './finances.js';
package/src/index.js CHANGED
@@ -46,6 +46,11 @@ import {
46
46
  PaymentStatus,
47
47
  FiatCurrency,
48
48
  } from './payments.js';
49
+ import {
50
+ getTokens,
51
+ getSupportedCoins,
52
+ getSupportedCoins as getSupportedPaymentCoins,
53
+ } from './tokens.js';
49
54
  import {
50
55
  verifyWebhookSignature,
51
56
  generateWebhookSignature,
@@ -66,6 +71,19 @@ import {
66
71
  authenticateEscrow,
67
72
  } from './escrow.js';
68
73
 
74
+ // Invoice exports
75
+ import {
76
+ createInvoice,
77
+ getInvoice,
78
+ listInvoices,
79
+ updateInvoice,
80
+ deleteInvoice,
81
+ publishInvoice,
82
+ sendInvoice,
83
+ getInvoicePaymentData,
84
+ InvoiceStatus,
85
+ } from './invoices.js';
86
+
69
87
  // Wallet exports
70
88
  import {
71
89
  WalletClient,
@@ -156,12 +174,30 @@ import {
156
174
  verifyX402Payment,
157
175
  settleX402Payment,
158
176
  buildPaymentRequired,
177
+ expectedForProof,
159
178
  convertUsdToAssetAmount,
160
179
  PAYMENT_METHODS,
161
180
  USDC_CONTRACTS,
162
181
  CHAIN_IDS,
163
182
  } from './x402.js';
164
183
 
184
+ import {
185
+ getFinanceSummary,
186
+ listFinanceAccounts,
187
+ listFinanceTransactions,
188
+ listFinanceConnections,
189
+ syncFinances,
190
+ getDashboardStats,
191
+ getFinanceAnalytics,
192
+ listCryptoPayments,
193
+ listCardTransactions,
194
+ listCardPayouts,
195
+ buildFinanceSnapshot,
196
+ collectFinanceSnapshot,
197
+ subscribeToPayments,
198
+ periodForDays,
199
+ } from './finances.js';
200
+
165
201
  export {
166
202
  // Client
167
203
  CoinPayClient,
@@ -170,6 +206,9 @@ export {
170
206
  createPayment,
171
207
  getPayment,
172
208
  listPayments,
209
+ getTokens,
210
+ getSupportedCoins,
211
+ getSupportedPaymentCoins,
173
212
 
174
213
  // Escrow functions
175
214
  createEscrow,
@@ -182,6 +221,17 @@ export {
182
221
  waitForEscrow,
183
222
  authenticateEscrow,
184
223
 
224
+ // Invoice functions
225
+ createInvoice,
226
+ getInvoice,
227
+ listInvoices,
228
+ updateInvoice,
229
+ deleteInvoice,
230
+ publishInvoice,
231
+ sendInvoice,
232
+ getInvoicePaymentData,
233
+ InvoiceStatus,
234
+
185
235
  // Wallet
186
236
  WalletClient,
187
237
  WalletChain,
@@ -253,6 +303,22 @@ export {
253
303
  // Lightning
254
304
  LightningClient,
255
305
 
306
+ // Finances (bank feeds, earnings, commission, refunds)
307
+ getFinanceSummary,
308
+ listFinanceAccounts,
309
+ listFinanceTransactions,
310
+ listFinanceConnections,
311
+ syncFinances,
312
+ getDashboardStats,
313
+ getFinanceAnalytics,
314
+ listCryptoPayments,
315
+ listCardTransactions,
316
+ listCardPayouts,
317
+ buildFinanceSnapshot,
318
+ collectFinanceSnapshot,
319
+ subscribeToPayments,
320
+ periodForDays,
321
+
256
322
  // Auth
257
323
  registerMerchant,
258
324
  loginMerchant,
@@ -263,6 +329,7 @@ export {
263
329
  verifyX402Payment,
264
330
  settleX402Payment,
265
331
  buildPaymentRequired,
332
+ expectedForProof,
266
333
  convertUsdToAssetAmount,
267
334
  PAYMENT_METHODS,
268
335
  USDC_CONTRACTS,
@@ -0,0 +1,78 @@
1
+ import type { CoinPayClient } from './client.js';
2
+
3
+ export const InvoiceStatus: {
4
+ readonly DRAFT: 'draft';
5
+ readonly SENT: 'sent';
6
+ readonly PAID: 'paid';
7
+ readonly OVERDUE: 'overdue';
8
+ readonly CANCELLED: 'cancelled';
9
+ };
10
+
11
+ export type InvoiceStatusType = (typeof InvoiceStatus)[keyof typeof InvoiceStatus];
12
+
13
+ export interface Invoice {
14
+ id: string;
15
+ invoiceNumber?: string;
16
+ businessId?: string;
17
+ clientId?: string;
18
+ currency?: string;
19
+ amount?: number;
20
+ cryptoCurrency?: string;
21
+ cryptoAmount?: string | number;
22
+ status?: InvoiceStatusType;
23
+ dueDate?: string;
24
+ notes?: string;
25
+ walletId?: string;
26
+ merchantWalletAddress?: string;
27
+ paymentAddress?: string;
28
+ stripeCheckoutUrl?: string;
29
+ paidAt?: string;
30
+ sentAt?: string;
31
+ createdAt?: string;
32
+ updatedAt?: string;
33
+ }
34
+
35
+ export interface CreateInvoiceParams {
36
+ businessId?: string;
37
+ clientId?: string;
38
+ currency: string;
39
+ amount: number;
40
+ cryptoCurrency?: string;
41
+ dueDate?: string;
42
+ notes?: string;
43
+ walletId?: string;
44
+ merchantWalletAddress?: string;
45
+ }
46
+
47
+ export interface ListInvoicesParams {
48
+ businessId?: string;
49
+ status?: InvoiceStatusType;
50
+ clientId?: string;
51
+ dateFrom?: string;
52
+ dateTo?: string;
53
+ }
54
+
55
+ export interface UpdateInvoiceParams {
56
+ amount?: number;
57
+ currency?: string;
58
+ cryptoCurrency?: string;
59
+ dueDate?: string;
60
+ notes?: string;
61
+ clientId?: string;
62
+ walletId?: string;
63
+ merchantWalletAddress?: string;
64
+ }
65
+
66
+ export interface ListInvoicesResult {
67
+ invoices: Invoice[];
68
+ total?: number;
69
+ }
70
+
71
+ export function createInvoice(client: CoinPayClient, options: CreateInvoiceParams): Promise<Invoice>;
72
+ export function listInvoices(client: CoinPayClient, filters?: ListInvoicesParams): Promise<ListInvoicesResult>;
73
+ export function getInvoice(client: CoinPayClient, id: string): Promise<Invoice>;
74
+ export function updateInvoice(client: CoinPayClient, id: string, updates: UpdateInvoiceParams): Promise<Invoice>;
75
+ export function deleteInvoice(client: CoinPayClient, id: string): Promise<Record<string, unknown>>;
76
+ export function publishInvoice(client: CoinPayClient, id: string): Promise<Record<string, unknown>>;
77
+ export function sendInvoice(client: CoinPayClient, id: string): Promise<Record<string, unknown>>;
78
+ export function getInvoicePaymentData(client: CoinPayClient, id: string): Promise<Record<string, unknown>>;