@profullstack/coinpay 0.6.5 → 0.6.7

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/coinpay.js CHANGED
@@ -208,6 +208,11 @@ ${colors.cyan}Commands:${colors.reset}
208
208
  status <swap-id> Check swap status
209
209
  history Swap history
210
210
 
211
+ ${colors.bright}payout${colors.reset}
212
+ create Create a payout to your Stripe account
213
+ list List payouts
214
+ get <id> Get payout details
215
+
211
216
  ${colors.bright}card${colors.reset}
212
217
  create Create a card payment via Stripe
213
218
  get <id> Get card payment status
@@ -1593,9 +1598,119 @@ async function handleEscrow(subcommand, args, flags) {
1593
1598
  break;
1594
1599
  }
1595
1600
 
1601
+ case 'series': {
1602
+ const seriesCmd = args[0];
1603
+ const seriesId = args[1];
1604
+
1605
+ switch (seriesCmd) {
1606
+ case 'create': {
1607
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1608
+ const ask = (q) => new Promise((r) => rl.question(q, r));
1609
+
1610
+ try {
1611
+ const businessId = flags['business-id'] || await ask('Business ID: ');
1612
+ const paymentMethod = flags['payment-method'] || await ask('Payment method (crypto/card): ');
1613
+ const amount = flags.amount || await ask('Amount (cents for card, satoshis-equiv for crypto): ');
1614
+ const currency = flags.currency || await ask('Currency [USD]: ') || 'USD';
1615
+ const interval = flags.interval || await ask('Interval (weekly/biweekly/monthly): ');
1616
+ const maxPeriods = flags['max-periods'] || await ask('Max periods (blank=infinite): ') || undefined;
1617
+ const customerEmail = flags.email || await ask('Customer email (optional): ') || undefined;
1618
+ const description = flags.description || await ask('Description (optional): ') || undefined;
1619
+
1620
+ const params = {
1621
+ business_id: businessId,
1622
+ payment_method: paymentMethod,
1623
+ amount: parseInt(amount),
1624
+ currency,
1625
+ interval,
1626
+ max_periods: maxPeriods ? parseInt(maxPeriods) : undefined,
1627
+ customer_email: customerEmail,
1628
+ description,
1629
+ };
1630
+
1631
+ if (paymentMethod === 'crypto') {
1632
+ params.coin = flags.coin || await ask('Coin (BTC/ETH/SOL/etc): ');
1633
+ params.beneficiary_address = flags.beneficiary || await ask('Beneficiary address: ');
1634
+ params.depositor_address = flags.depositor || await ask('Depositor address: ');
1635
+ } else {
1636
+ params.stripe_account_id = flags['stripe-account'] || await ask('Stripe account ID: ');
1637
+ }
1638
+
1639
+ rl.close();
1640
+
1641
+ const series = await client.createEscrowSeries(params);
1642
+ print.success(`Escrow series created: ${series.id}`);
1643
+ print.info(` Status: ${series.status}`);
1644
+ print.info(` Interval: ${series.interval}`);
1645
+ print.info(` Amount: ${series.amount} ${series.currency}`);
1646
+ if (flags.json) print.json(series);
1647
+ } catch (e) {
1648
+ rl.close();
1649
+ throw e;
1650
+ }
1651
+ break;
1652
+ }
1653
+
1654
+ case 'list': {
1655
+ const businessId = flags['business-id'];
1656
+ if (!businessId) { print.error('--business-id required'); process.exit(1); }
1657
+ const result = await client.listEscrowSeries(businessId, flags.status);
1658
+ print.info(`Series (${result.series.length}):`);
1659
+ for (const s of result.series) {
1660
+ console.log(` ${s.id} | ${s.status} | ${s.payment_method} | ${s.interval} | ${s.amount} ${s.currency}`);
1661
+ }
1662
+ if (flags.json) print.json(result);
1663
+ break;
1664
+ }
1665
+
1666
+ case 'get': {
1667
+ if (!seriesId) { print.error('Series ID required'); process.exit(1); }
1668
+ const result = await client.getEscrowSeries(seriesId);
1669
+ print.success(`Series ${result.series.id}`);
1670
+ print.info(` Status: ${result.series.status}`);
1671
+ print.info(` Method: ${result.series.payment_method}`);
1672
+ print.info(` Amount: ${result.series.amount} ${result.series.currency}`);
1673
+ print.info(` Interval: ${result.series.interval}`);
1674
+ print.info(` Periods: ${result.series.periods_completed}${result.series.max_periods ? '/' + result.series.max_periods : ''}`);
1675
+ print.info(` Next charge: ${result.series.next_charge_at}`);
1676
+ print.info(` Crypto escrows: ${result.escrows.crypto.length}`);
1677
+ print.info(` Stripe escrows: ${result.escrows.stripe.length}`);
1678
+ if (flags.json) print.json(result);
1679
+ break;
1680
+ }
1681
+
1682
+ case 'pause': {
1683
+ if (!seriesId) { print.error('Series ID required'); process.exit(1); }
1684
+ await client.updateEscrowSeries(seriesId, { status: 'paused' });
1685
+ print.success(`Series ${seriesId} paused`);
1686
+ break;
1687
+ }
1688
+
1689
+ case 'resume': {
1690
+ if (!seriesId) { print.error('Series ID required'); process.exit(1); }
1691
+ await client.updateEscrowSeries(seriesId, { status: 'active' });
1692
+ print.success(`Series ${seriesId} resumed`);
1693
+ break;
1694
+ }
1695
+
1696
+ case 'cancel': {
1697
+ if (!seriesId) { print.error('Series ID required'); process.exit(1); }
1698
+ await client.cancelEscrowSeries(seriesId);
1699
+ print.success(`Series ${seriesId} cancelled`);
1700
+ break;
1701
+ }
1702
+
1703
+ default:
1704
+ print.error(`Unknown series command: ${seriesCmd}`);
1705
+ print.info('Available: create, list, get, pause, resume, cancel');
1706
+ process.exit(1);
1707
+ }
1708
+ break;
1709
+ }
1710
+
1596
1711
  default:
1597
1712
  print.error(`Unknown escrow command: ${subcommand}`);
1598
- print.info('Available: create, get, list, release, refund, dispute, events, auth');
1713
+ print.info('Available: create, get, list, release, refund, dispute, events, auth, series');
1599
1714
  process.exit(1);
1600
1715
  }
1601
1716
  }
@@ -2003,6 +2118,74 @@ async function handleReputation(subcommand, args, flags) {
2003
2118
  }
2004
2119
  }
2005
2120
 
2121
+ /**
2122
+ * Payout commands
2123
+ */
2124
+ async function handlePayout(subcommand, args, flags) {
2125
+ const client = createClient();
2126
+
2127
+ switch (subcommand) {
2128
+ case 'create': {
2129
+ const amount = parseInt(flags.amount);
2130
+ if (!amount || amount <= 0) {
2131
+ print.error('--amount is required (in cents, e.g., 5000 for $50.00)');
2132
+ print.info('Example: coinpay payout create --amount 5000 --description "Weekly payout"');
2133
+ process.exit(1);
2134
+ }
2135
+
2136
+ const result = await client.createPayout({
2137
+ amount,
2138
+ currency: flags.currency || 'usd',
2139
+ description: flags.description,
2140
+ metadata: flags.metadata ? JSON.parse(flags.metadata) : undefined,
2141
+ });
2142
+
2143
+ print.success('Payout created');
2144
+ print.json(result);
2145
+ break;
2146
+ }
2147
+
2148
+ case 'list': {
2149
+ const result = await client.listPayouts({
2150
+ status: flags.status,
2151
+ dateFrom: flags['date-from'] || flags.dateFrom,
2152
+ dateTo: flags['date-to'] || flags.dateTo,
2153
+ limit: flags.limit ? parseInt(flags.limit) : undefined,
2154
+ offset: flags.offset ? parseInt(flags.offset) : undefined,
2155
+ });
2156
+
2157
+ if (result.payouts && result.payouts.length > 0) {
2158
+ console.log(`\n${colors.bright}Payouts${colors.reset} (${result.pagination?.total || result.payouts.length} total)\n`);
2159
+ for (const p of result.payouts) {
2160
+ const statusColor = p.status === 'paid' ? colors.green : p.status === 'failed' ? colors.red : colors.yellow;
2161
+ console.log(` ${p.id} ${statusColor}${p.status}${colors.reset} $${p.amount_usd || ((p.amount_cents || 0) / 100).toFixed(2)} ${new Date(p.created_at).toLocaleDateString()}`);
2162
+ }
2163
+ console.log();
2164
+ } else {
2165
+ print.info('No payouts found');
2166
+ }
2167
+ break;
2168
+ }
2169
+
2170
+ case 'get': {
2171
+ const id = args[0];
2172
+ if (!id) {
2173
+ print.error('Usage: coinpay payout get <id>');
2174
+ process.exit(1);
2175
+ }
2176
+
2177
+ const result = await client.getPayout(id);
2178
+ print.json(result);
2179
+ break;
2180
+ }
2181
+
2182
+ default:
2183
+ print.error(`Unknown payout command: ${subcommand}`);
2184
+ print.info('Available: create, list, get');
2185
+ process.exit(1);
2186
+ }
2187
+ }
2188
+
2006
2189
  /**
2007
2190
  * Card (Stripe) commands
2008
2191
  */
@@ -2258,6 +2441,10 @@ async function main() {
2258
2441
  await handleWebhook(subcommand, args, flags);
2259
2442
  break;
2260
2443
 
2444
+ case 'payout':
2445
+ await handlePayout(subcommand, args, flags);
2446
+ break;
2447
+
2261
2448
  case 'card':
2262
2449
  await handleCard(subcommand, args, flags);
2263
2450
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@profullstack/coinpay",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "CoinPay SDK & CLI — Accept cryptocurrency payments (BTC, ETH, SOL, POL, BCH, USDC) with wallet and swap support",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -285,6 +285,128 @@ export function calculateCardPaymentFees(amountCents, tier = 'free') {
285
285
  };
286
286
  }
287
287
 
288
+ /**
289
+ * Create a card escrow payment
290
+ *
291
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
292
+ * @param {string} businessId - Business ID
293
+ * @param {number} amountUSD - Amount in USD (will be converted to cents)
294
+ * @param {string} description - Payment description
295
+ * @param {Object} [metadata={}] - Custom metadata
296
+ * @returns {Promise<Object>} Escrow payment session with checkout URL
297
+ *
298
+ * @example
299
+ * import { createCardEscrow } from '@profullstack/coinpay/card-payments';
300
+ *
301
+ * const escrow = await createCardEscrow(client, 'business-id', 100, 'Service payment', {
302
+ * orderId: '123',
303
+ * serviceType: 'web-design'
304
+ * });
305
+ *
306
+ * // Redirect customer to: escrow.checkout_url
307
+ */
308
+ export async function createCardEscrow(client, businessId, amountUSD, description, metadata = {}) {
309
+ // Convert USD to cents
310
+ const amountCents = Math.round(amountUSD * 100);
311
+
312
+ return client.request('POST', '/api/stripe/payments/create', {
313
+ businessId,
314
+ amount: amountCents,
315
+ currency: 'usd',
316
+ description,
317
+ metadata,
318
+ escrowMode: true,
319
+ });
320
+ }
321
+
322
+ /**
323
+ * List card escrows
324
+ *
325
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
326
+ * @param {Object} [options={}] - Filtering options
327
+ * @param {string} [options.businessId] - Filter by business ID
328
+ * @param {string} [options.status] - Filter by status
329
+ * @param {number} [options.limit] - Maximum number of results
330
+ * @param {number} [options.offset] - Results offset
331
+ * @returns {Promise<Object>} List of card escrows
332
+ *
333
+ * @example
334
+ * const escrows = await listCardEscrows(client, {
335
+ * businessId: 'business-id',
336
+ * status: 'pending'
337
+ * });
338
+ */
339
+ export async function listCardEscrows(client, options = {}) {
340
+ const queryParams = new URLSearchParams();
341
+ if (options.businessId) queryParams.set('businessId', options.businessId);
342
+ if (options.status) queryParams.set('status', options.status);
343
+ if (options.limit) queryParams.set('limit', options.limit.toString());
344
+ if (options.offset) queryParams.set('offset', options.offset.toString());
345
+
346
+ const queryString = queryParams.toString();
347
+ const url = `/api/stripe/escrows${queryString ? `?${queryString}` : ''}`;
348
+
349
+ return client.request('GET', url);
350
+ }
351
+
352
+ /**
353
+ * Release a card escrow
354
+ *
355
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
356
+ * @param {string} escrowId - Escrow ID to release
357
+ * @returns {Promise<Object>} Release result
358
+ *
359
+ * @example
360
+ * const result = await releaseCardEscrow(client, 'escrow-123');
361
+ */
362
+ export async function releaseCardEscrow(client, escrowId) {
363
+ return client.request('POST', '/api/stripe/escrow/release', {
364
+ escrowId,
365
+ });
366
+ }
367
+
368
+ /**
369
+ * Refund a card escrow
370
+ *
371
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
372
+ * @param {string} escrowId - Escrow ID to refund
373
+ * @param {Object} [options={}] - Refund options
374
+ * @param {number} [options.amount] - Partial refund amount in cents
375
+ * @param {string} [options.reason] - Refund reason
376
+ * @returns {Promise<Object>} Refund result
377
+ *
378
+ * @example
379
+ * // Full refund
380
+ * const result = await refundCardEscrow(client, 'escrow-123');
381
+ *
382
+ * // Partial refund
383
+ * const result = await refundCardEscrow(client, 'escrow-123', {
384
+ * amount: 2500, // $25.00 in cents
385
+ * reason: 'partial_delivery'
386
+ * });
387
+ */
388
+ export async function refundCardEscrow(client, escrowId, options = {}) {
389
+ return client.request('POST', '/api/stripe/escrow/refund', {
390
+ escrowId,
391
+ ...options,
392
+ });
393
+ }
394
+
395
+ /**
396
+ * Get card escrow status and transaction details
397
+ *
398
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
399
+ * @param {string} escrowId - Escrow/transaction ID
400
+ * @returns {Promise<Object>} Transaction details with escrow status
401
+ *
402
+ * @example
403
+ * const status = await getCardEscrowStatus(client, 'escrow-123');
404
+ * console.log(status.escrow_status); // 'pending', 'released', 'refunded'
405
+ */
406
+ export async function getCardEscrowStatus(client, escrowId) {
407
+ return client.request('GET', `/api/stripe/transactions/${escrowId}`);
408
+ }
409
+
288
410
  /**
289
411
  * Default export with all convenience functions
290
412
  */
@@ -295,4 +417,9 @@ export default {
295
417
  getPaymentMethodSupport,
296
418
  formatCardAmount,
297
419
  calculateCardPaymentFees,
420
+ createCardEscrow,
421
+ listCardEscrows,
422
+ releaseCardEscrow,
423
+ refundCardEscrow,
424
+ getCardEscrowStatus,
298
425
  };
package/src/client.js CHANGED
@@ -523,6 +523,43 @@ export class CoinPayClient {
523
523
  return authenticateEscrow(this, escrowId, token);
524
524
  }
525
525
 
526
+ // ── Stripe Payout Methods ───────────────────────────────
527
+
528
+ /**
529
+ * Create a payout to your connected Stripe account
530
+ *
531
+ * @param {Object} params - Payout parameters
532
+ * @param {number} params.amount - Amount in cents
533
+ * @param {string} [params.currency='usd'] - Currency code
534
+ * @param {string} [params.description] - Payout description
535
+ * @param {Object} [params.metadata] - Custom metadata
536
+ * @returns {Promise<Object>} Created payout
537
+ */
538
+ async createPayout(params) {
539
+ const { createPayout } = await import('./payouts.js');
540
+ return createPayout(this, params);
541
+ }
542
+
543
+ /**
544
+ * List payouts
545
+ * @param {Object} [filters] - Filter options
546
+ * @returns {Promise<Object>} Payouts list with pagination
547
+ */
548
+ async listPayouts(filters) {
549
+ const { listPayouts } = await import('./payouts.js');
550
+ return listPayouts(this, filters);
551
+ }
552
+
553
+ /**
554
+ * Get payout by ID
555
+ * @param {string} payoutId - Payout ID
556
+ * @returns {Promise<Object>} Payout details
557
+ */
558
+ async getPayout(payoutId) {
559
+ const { getPayout } = await import('./payouts.js');
560
+ return getPayout(this, payoutId);
561
+ }
562
+
526
563
  // ── Stripe Card Payment Methods ────────────────────────
527
564
 
528
565
  /**
@@ -685,6 +722,64 @@ export class CoinPayClient {
685
722
  }),
686
723
  });
687
724
  }
725
+ // ── Escrow Series (Recurring) Methods ────────────────────
726
+
727
+ /**
728
+ * Create a recurring escrow series
729
+ * @param {Object} params
730
+ * @returns {Promise<Object>} Created series
731
+ */
732
+ async createEscrowSeries(params) {
733
+ return this.request('/escrow/series', {
734
+ method: 'POST',
735
+ body: JSON.stringify(params),
736
+ });
737
+ }
738
+
739
+ /**
740
+ * List escrow series for a business
741
+ * @param {string} businessId
742
+ * @param {string} [status]
743
+ * @returns {Promise<Object>}
744
+ */
745
+ async listEscrowSeries(businessId, status) {
746
+ const params = new URLSearchParams({ business_id: businessId });
747
+ if (status) params.set('status', status);
748
+ return this.request(`/escrow/series?${params.toString()}`);
749
+ }
750
+
751
+ /**
752
+ * Get escrow series detail with child escrows
753
+ * @param {string} id
754
+ * @returns {Promise<Object>}
755
+ */
756
+ async getEscrowSeries(id) {
757
+ return this.request(`/escrow/series/${id}`);
758
+ }
759
+
760
+ /**
761
+ * Update escrow series (pause/resume/cancel, update amount/interval)
762
+ * @param {string} id
763
+ * @param {Object} params - { status?, amount?, interval? }
764
+ * @returns {Promise<Object>}
765
+ */
766
+ async updateEscrowSeries(id, params) {
767
+ return this.request(`/escrow/series/${id}`, {
768
+ method: 'PATCH',
769
+ body: JSON.stringify(params),
770
+ });
771
+ }
772
+
773
+ /**
774
+ * Cancel an escrow series
775
+ * @param {string} id
776
+ * @returns {Promise<Object>}
777
+ */
778
+ async cancelEscrowSeries(id) {
779
+ return this.request(`/escrow/series/${id}`, {
780
+ method: 'DELETE',
781
+ });
782
+ }
688
783
  }
689
784
 
690
785
  export default CoinPayClient;
package/src/index.js CHANGED
@@ -101,6 +101,42 @@ import {
101
101
  getSwapHistory,
102
102
  } from './swap.js';
103
103
 
104
+ // Payouts
105
+ import {
106
+ createPayout,
107
+ listPayouts,
108
+ getPayout,
109
+ formatPayoutAmount,
110
+ } from './payouts.js';
111
+
112
+ // Card payments
113
+ import {
114
+ createQuickCardPayment,
115
+ waitForStripeOnboarding,
116
+ createCardPaymentWithOnboardingCheck,
117
+ getPaymentMethodSupport,
118
+ formatCardAmount,
119
+ calculateCardPaymentFees,
120
+ createCardEscrow,
121
+ listCardEscrows,
122
+ releaseCardEscrow,
123
+ refundCardEscrow,
124
+ getCardEscrowStatus,
125
+ } from './card-payments.js';
126
+
127
+ // Subscriptions
128
+ import {
129
+ createSubscriptionPlan,
130
+ listSubscriptionPlans,
131
+ subscribeCustomer,
132
+ cancelSubscription,
133
+ listSubscriptions,
134
+ getSubscription,
135
+ formatSubscriptionAmount,
136
+ PlanInterval,
137
+ SubscriptionStatus,
138
+ } from './subscriptions.js';
139
+
104
140
  export {
105
141
  // Client
106
142
  CoinPayClient,
@@ -140,6 +176,36 @@ export {
140
176
  getSwapStatus,
141
177
  getSwapHistory,
142
178
 
179
+ // Payouts
180
+ createPayout,
181
+ listPayouts,
182
+ getPayout,
183
+ formatPayoutAmount,
184
+
185
+ // Card payments
186
+ createQuickCardPayment,
187
+ waitForStripeOnboarding,
188
+ createCardPaymentWithOnboardingCheck,
189
+ getPaymentMethodSupport,
190
+ formatCardAmount,
191
+ calculateCardPaymentFees,
192
+ createCardEscrow,
193
+ listCardEscrows,
194
+ releaseCardEscrow,
195
+ refundCardEscrow,
196
+ getCardEscrowStatus,
197
+
198
+ // Subscriptions
199
+ createSubscriptionPlan,
200
+ listSubscriptionPlans,
201
+ subscribeCustomer,
202
+ cancelSubscription,
203
+ listSubscriptions,
204
+ getSubscription,
205
+ formatSubscriptionAmount,
206
+ PlanInterval,
207
+ SubscriptionStatus,
208
+
143
209
  // Constants
144
210
  Blockchain,
145
211
  Cryptocurrency, // Deprecated, use Blockchain
package/src/payouts.js ADDED
@@ -0,0 +1,79 @@
1
+ /**
2
+ * CoinPay Payouts - Functions for Stripe Connect payouts
3
+ *
4
+ * Manage payouts to connected Stripe accounts (merchants).
5
+ */
6
+
7
+ /**
8
+ * Create a payout to a connected Stripe account
9
+ *
10
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
11
+ * @param {Object} params - Payout parameters
12
+ * @param {number} params.amount - Amount in cents (e.g., 5000 = $50.00)
13
+ * @param {string} [params.currency='usd'] - Currency code
14
+ * @param {string} [params.description] - Payout description
15
+ * @param {Object} [params.metadata] - Custom metadata
16
+ * @returns {Promise<Object>} Created payout
17
+ *
18
+ * @example
19
+ * const payout = await createPayout(client, {
20
+ * amount: 5000,
21
+ * currency: 'usd',
22
+ * description: 'Weekly payout'
23
+ * });
24
+ */
25
+ export async function createPayout(client, { amount, currency = 'usd', description, metadata }) {
26
+ return client.request('/stripe/payouts', {
27
+ method: 'POST',
28
+ body: JSON.stringify({ amount, currency, description, metadata }),
29
+ });
30
+ }
31
+
32
+ /**
33
+ * List payouts for the authenticated merchant
34
+ *
35
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
36
+ * @param {Object} [filters] - Filter options
37
+ * @param {string} [filters.status] - Filter by status (pending, paid, failed, canceled)
38
+ * @param {string} [filters.dateFrom] - Filter from date (ISO string)
39
+ * @param {string} [filters.dateTo] - Filter to date (ISO string)
40
+ * @param {number} [filters.limit=50] - Number of results
41
+ * @param {number} [filters.offset=0] - Pagination offset
42
+ * @returns {Promise<Object>} List of payouts with pagination
43
+ */
44
+ export async function listPayouts(client, filters = {}) {
45
+ const params = new URLSearchParams();
46
+ if (filters.status) params.set('status', filters.status);
47
+ if (filters.dateFrom) params.set('date_from', filters.dateFrom);
48
+ if (filters.dateTo) params.set('date_to', filters.dateTo);
49
+ if (filters.limit) params.set('limit', String(filters.limit));
50
+ if (filters.offset) params.set('offset', String(filters.offset));
51
+
52
+ const qs = params.toString();
53
+ return client.request(`/stripe/payouts${qs ? '?' + qs : ''}`);
54
+ }
55
+
56
+ /**
57
+ * Get a specific payout by ID
58
+ *
59
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
60
+ * @param {string} payoutId - Payout ID
61
+ * @returns {Promise<Object>} Payout details
62
+ */
63
+ export async function getPayout(client, payoutId) {
64
+ return client.request(`/stripe/payouts/${payoutId}`);
65
+ }
66
+
67
+ /**
68
+ * Format payout amount from cents to display string
69
+ *
70
+ * @param {number} amountCents - Amount in cents
71
+ * @param {string} [currency='usd'] - Currency code
72
+ * @returns {string} Formatted amount (e.g., "$50.00")
73
+ */
74
+ export function formatPayoutAmount(amountCents, currency = 'usd') {
75
+ const amount = (amountCents / 100).toFixed(2);
76
+ const symbols = { usd: '$', eur: '€', gbp: '£' };
77
+ const symbol = symbols[currency.toLowerCase()] || currency.toUpperCase() + ' ';
78
+ return `${symbol}${amount}`;
79
+ }
@@ -0,0 +1,231 @@
1
+ /**
2
+ * CoinPay Subscriptions - Stripe subscription billing integration
3
+ *
4
+ * Provides functions for managing subscription plans and customer subscriptions
5
+ * through Stripe's subscription billing system.
6
+ */
7
+
8
+ /**
9
+ * Subscription plan intervals
10
+ */
11
+ export const PlanInterval = {
12
+ DAILY: 'day',
13
+ WEEKLY: 'week',
14
+ MONTHLY: 'month',
15
+ YEARLY: 'year',
16
+ };
17
+
18
+ /**
19
+ * Subscription statuses
20
+ */
21
+ export const SubscriptionStatus = {
22
+ ACTIVE: 'active',
23
+ PAST_DUE: 'past_due',
24
+ CANCELED: 'canceled',
25
+ INCOMPLETE: 'incomplete',
26
+ INCOMPLETE_EXPIRED: 'incomplete_expired',
27
+ TRIALING: 'trialing',
28
+ UNPAID: 'unpaid',
29
+ PAUSED: 'paused',
30
+ };
31
+
32
+ /**
33
+ * Create a subscription plan (Stripe Price + Product)
34
+ *
35
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
36
+ * @param {Object} params - Plan parameters
37
+ * @param {string} params.businessId - Business ID
38
+ * @param {string} params.name - Plan name
39
+ * @param {string} [params.description] - Plan description
40
+ * @param {number} params.amount - Amount in cents
41
+ * @param {string} [params.currency='usd'] - Currency code
42
+ * @param {string} [params.interval='month'] - Billing interval (day, week, month, year)
43
+ * @param {number} [params.intervalCount=1] - Number of intervals between billings
44
+ * @param {number} [params.trialDays] - Trial period in days
45
+ * @param {Object} [params.metadata] - Custom metadata
46
+ * @returns {Promise<Object>} Created plan
47
+ *
48
+ * @example
49
+ * const plan = await createSubscriptionPlan(client, {
50
+ * businessId: 'biz-123',
51
+ * name: 'Pro Monthly',
52
+ * amount: 2999,
53
+ * interval: 'month',
54
+ * });
55
+ */
56
+ export async function createSubscriptionPlan(client, params) {
57
+ const {
58
+ businessId,
59
+ name,
60
+ description,
61
+ amount,
62
+ currency = 'usd',
63
+ interval = 'month',
64
+ intervalCount = 1,
65
+ trialDays,
66
+ metadata = {},
67
+ } = params;
68
+
69
+ if (!businessId || !name || !amount) {
70
+ throw new Error('businessId, name, and amount are required');
71
+ }
72
+
73
+ return client.request('/stripe/subscriptions/plans', {
74
+ method: 'POST',
75
+ body: JSON.stringify({
76
+ businessId,
77
+ name,
78
+ description,
79
+ amount,
80
+ currency,
81
+ interval,
82
+ intervalCount,
83
+ trialDays,
84
+ metadata,
85
+ }),
86
+ });
87
+ }
88
+
89
+ /**
90
+ * List subscription plans for a business
91
+ *
92
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
93
+ * @param {string} businessId - Business ID
94
+ * @param {Object} [options] - Query options
95
+ * @param {number} [options.limit=20] - Max results
96
+ * @param {boolean} [options.active] - Filter by active status
97
+ * @returns {Promise<Object>} List of plans
98
+ */
99
+ export async function listSubscriptionPlans(client, businessId, options = {}) {
100
+ const params = new URLSearchParams({ businessId });
101
+ if (options.limit) params.set('limit', String(options.limit));
102
+ if (options.active !== undefined) params.set('active', String(options.active));
103
+
104
+ return client.request(`/stripe/subscriptions/plans?${params}`);
105
+ }
106
+
107
+ /**
108
+ * Subscribe a customer to a plan
109
+ *
110
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
111
+ * @param {Object} params - Subscription parameters
112
+ * @param {string} params.planId - Plan/Price ID
113
+ * @param {string} params.customerEmail - Customer email
114
+ * @param {string} [params.customerId] - Existing Stripe customer ID
115
+ * @param {string} [params.paymentMethodId] - Payment method ID
116
+ * @param {string} [params.successUrl] - Success redirect URL
117
+ * @param {string} [params.cancelUrl] - Cancel redirect URL
118
+ * @param {Object} [params.metadata] - Custom metadata
119
+ * @returns {Promise<Object>} Subscription or checkout session
120
+ *
121
+ * @example
122
+ * const sub = await subscribeCustomer(client, {
123
+ * planId: 'price_123',
124
+ * customerEmail: 'customer@example.com',
125
+ * successUrl: 'https://mysite.com/success',
126
+ * cancelUrl: 'https://mysite.com/cancel',
127
+ * });
128
+ * // Redirect to sub.checkout_url
129
+ */
130
+ export async function subscribeCustomer(client, params) {
131
+ const {
132
+ planId,
133
+ customerEmail,
134
+ customerId,
135
+ paymentMethodId,
136
+ successUrl,
137
+ cancelUrl,
138
+ metadata = {},
139
+ } = params;
140
+
141
+ if (!planId || (!customerEmail && !customerId)) {
142
+ throw new Error('planId and either customerEmail or customerId are required');
143
+ }
144
+
145
+ return client.request('/stripe/subscriptions', {
146
+ method: 'POST',
147
+ body: JSON.stringify({
148
+ planId,
149
+ customerEmail,
150
+ customerId,
151
+ paymentMethodId,
152
+ successUrl,
153
+ cancelUrl,
154
+ metadata,
155
+ }),
156
+ });
157
+ }
158
+
159
+ /**
160
+ * Cancel a subscription
161
+ *
162
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
163
+ * @param {string} subscriptionId - Subscription ID
164
+ * @param {Object} [options] - Cancellation options
165
+ * @param {boolean} [options.immediately=false] - Cancel immediately vs at period end
166
+ * @returns {Promise<Object>} Cancelled subscription
167
+ */
168
+ export async function cancelSubscription(client, subscriptionId, options = {}) {
169
+ if (!subscriptionId) {
170
+ throw new Error('subscriptionId is required');
171
+ }
172
+
173
+ return client.request(`/stripe/subscriptions/${encodeURIComponent(subscriptionId)}`, {
174
+ method: 'DELETE',
175
+ body: JSON.stringify({
176
+ immediately: options.immediately || false,
177
+ }),
178
+ });
179
+ }
180
+
181
+ /**
182
+ * List subscriptions
183
+ *
184
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
185
+ * @param {Object} [options] - Query options
186
+ * @param {string} [options.businessId] - Filter by business
187
+ * @param {string} [options.customerId] - Filter by customer
188
+ * @param {string} [options.status] - Filter by status
189
+ * @param {number} [options.limit=20] - Max results
190
+ * @param {number} [options.offset=0] - Pagination offset
191
+ * @returns {Promise<Object>} List of subscriptions
192
+ */
193
+ export async function listSubscriptions(client, options = {}) {
194
+ const params = new URLSearchParams();
195
+ if (options.businessId) params.set('businessId', options.businessId);
196
+ if (options.customerId) params.set('customerId', options.customerId);
197
+ if (options.status) params.set('status', options.status);
198
+ if (options.limit) params.set('limit', String(options.limit));
199
+ if (options.offset) params.set('offset', String(options.offset));
200
+
201
+ return client.request(`/stripe/subscriptions?${params}`);
202
+ }
203
+
204
+ /**
205
+ * Get subscription status/details
206
+ *
207
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
208
+ * @param {string} subscriptionId - Subscription ID
209
+ * @returns {Promise<Object>} Subscription details
210
+ */
211
+ export async function getSubscription(client, subscriptionId) {
212
+ if (!subscriptionId) {
213
+ throw new Error('subscriptionId is required');
214
+ }
215
+
216
+ return client.request(`/stripe/subscriptions/${encodeURIComponent(subscriptionId)}`);
217
+ }
218
+
219
+ /**
220
+ * Format subscription amount for display
221
+ *
222
+ * @param {number} amount - Amount in cents
223
+ * @param {string} [currency='usd'] - Currency code
224
+ * @param {string} [interval='month'] - Billing interval
225
+ * @returns {string} Formatted amount (e.g., "$29.99/month")
226
+ */
227
+ export function formatSubscriptionAmount(amount, currency = 'usd', interval = 'month') {
228
+ const dollars = (amount / 100).toFixed(2);
229
+ const symbol = currency.toLowerCase() === 'usd' ? '$' : currency.toUpperCase() + ' ';
230
+ return `${symbol}${dollars}/${interval}`;
231
+ }