@profullstack/coinpay 0.6.3 → 0.6.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@profullstack/coinpay",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
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",
@@ -17,6 +17,10 @@
17
17
  "import": "./src/payments.js",
18
18
  "types": "./src/payments.d.ts"
19
19
  },
20
+ "./card-payments": {
21
+ "import": "./src/card-payments.js",
22
+ "types": "./src/card-payments.d.ts"
23
+ },
20
24
  "./webhooks": {
21
25
  "import": "./src/webhooks.js",
22
26
  "types": "./src/webhooks.d.ts"
@@ -0,0 +1,298 @@
1
+ /**
2
+ * CoinPay Card Payments - Convenience functions for Stripe integration
3
+ *
4
+ * This module provides high-level convenience functions for working with
5
+ * card payments through Stripe Connect, similar to the payments.js module.
6
+ */
7
+
8
+ /**
9
+ * Quick card payment creation with sensible defaults
10
+ *
11
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
12
+ * @param {string} businessId - Business ID
13
+ * @param {number} amountUSD - Amount in USD (will be converted to cents)
14
+ * @param {string} description - Payment description
15
+ * @param {Object} [options] - Additional options
16
+ * @param {Object} [options.metadata] - Custom metadata
17
+ * @param {string} [options.successUrl] - Success redirect URL
18
+ * @param {string} [options.cancelUrl] - Cancel redirect URL
19
+ * @param {boolean} [options.escrowMode=false] - Enable escrow mode
20
+ * @returns {Promise<Object>} Payment session with checkout URL
21
+ *
22
+ * @example
23
+ * import { createQuickCardPayment } from '@profullstack/coinpay/card-payments';
24
+ *
25
+ * const payment = await createQuickCardPayment(client, 'business-id', 50, 'Order #123', {
26
+ * metadata: { orderId: '123' },
27
+ * escrowMode: true
28
+ * });
29
+ *
30
+ * // Redirect customer to: payment.checkout_url
31
+ */
32
+ export async function createQuickCardPayment(client, businessId, amountUSD, description, options = {}) {
33
+ const {
34
+ metadata = {},
35
+ successUrl,
36
+ cancelUrl,
37
+ escrowMode = false,
38
+ } = options;
39
+
40
+ // Convert USD to cents
41
+ const amountCents = Math.round(amountUSD * 100);
42
+
43
+ return client.createCardPayment({
44
+ businessId,
45
+ amount: amountCents,
46
+ currency: 'usd',
47
+ description,
48
+ metadata,
49
+ successUrl,
50
+ cancelUrl,
51
+ escrowMode,
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Wait for merchant to complete Stripe onboarding
57
+ *
58
+ * Polls the Stripe account status until onboarding is complete.
59
+ * Useful for integration flows where you need to wait for merchant setup.
60
+ *
61
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
62
+ * @param {string} businessId - Business ID
63
+ * @param {Object} [options] - Polling options
64
+ * @param {number} [options.intervalMs=5000] - Polling interval in ms
65
+ * @param {number} [options.timeoutMs=300000] - Timeout in ms (default: 5 minutes)
66
+ * @param {Function} [options.onStatusChange] - Callback for status changes
67
+ * @returns {Promise<Object>} Final account status when onboarding complete
68
+ *
69
+ * @example
70
+ * const accountStatus = await waitForStripeOnboarding(client, 'business-id', {
71
+ * onStatusChange: (status) => {
72
+ * console.log(`Onboarding status: ${JSON.stringify(status)}`);
73
+ * }
74
+ * });
75
+ */
76
+ export async function waitForStripeOnboarding(client, businessId, options = {}) {
77
+ const {
78
+ intervalMs = 5000,
79
+ timeoutMs = 300000, // 5 minutes
80
+ onStatusChange,
81
+ } = options;
82
+
83
+ const startTime = Date.now();
84
+
85
+ while (Date.now() - startTime < timeoutMs) {
86
+ const status = await client.getStripeAccountStatus(businessId);
87
+
88
+ if (onStatusChange) {
89
+ onStatusChange(status);
90
+ }
91
+
92
+ if (status.onboarding_complete) {
93
+ return status;
94
+ }
95
+
96
+ // Wait before next poll
97
+ await new Promise(resolve => setTimeout(resolve, intervalMs));
98
+ }
99
+
100
+ throw new Error(`Stripe onboarding timeout after ${timeoutMs}ms`);
101
+ }
102
+
103
+ /**
104
+ * Create payment with automatic merchant onboarding check
105
+ *
106
+ * Checks if merchant has completed Stripe onboarding first, and provides
107
+ * helpful error messages if not. Prevents failed payment attempts.
108
+ *
109
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
110
+ * @param {Object} params - Payment parameters (same as createCardPayment)
111
+ * @returns {Promise<Object>} Payment session or onboarding info if incomplete
112
+ *
113
+ * @example
114
+ * const result = await createCardPaymentWithOnboardingCheck(client, {
115
+ * businessId: 'business-id',
116
+ * amount: 5000,
117
+ * description: 'Order #123'
118
+ * });
119
+ *
120
+ * if (result.requires_onboarding) {
121
+ * // Redirect merchant to result.onboarding_url
122
+ * } else {
123
+ * // Redirect customer to result.checkout_url
124
+ * }
125
+ */
126
+ export async function createCardPaymentWithOnboardingCheck(client, params) {
127
+ try {
128
+ // Check onboarding status first
129
+ const status = await client.getStripeAccountStatus(params.businessId);
130
+
131
+ if (!status.onboarding_complete) {
132
+ // Generate onboarding link if needed
133
+ const onboarding = await client.createStripeOnboardingLink(params.businessId);
134
+
135
+ return {
136
+ requires_onboarding: true,
137
+ onboarding_url: onboarding.onboarding_url,
138
+ status: status,
139
+ message: 'Merchant must complete Stripe onboarding before accepting card payments',
140
+ };
141
+ }
142
+
143
+ // Onboarding complete, create payment
144
+ const payment = await client.createCardPayment(params);
145
+ return {
146
+ requires_onboarding: false,
147
+ ...payment,
148
+ };
149
+
150
+ } catch (error) {
151
+ if (error.status === 404) {
152
+ // No Stripe account exists, need onboarding
153
+ const onboarding = await client.createStripeOnboardingLink(params.businessId);
154
+
155
+ return {
156
+ requires_onboarding: true,
157
+ onboarding_url: onboarding.onboarding_url,
158
+ status: null,
159
+ message: 'Merchant needs to complete Stripe onboarding',
160
+ };
161
+ }
162
+ throw error;
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Get payment method support status
168
+ *
169
+ * Returns which payment methods are available for a merchant.
170
+ * Helps with conditional UI rendering.
171
+ *
172
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
173
+ * @param {string} businessId - Business ID
174
+ * @returns {Promise<Object>} Available payment methods
175
+ *
176
+ * @example
177
+ * const support = await getPaymentMethodSupport(client, 'business-id');
178
+ * console.log(support);
179
+ * // {
180
+ * // crypto: true,
181
+ * // cards: true,
182
+ * // escrow: true,
183
+ * // stripe_onboarding_complete: true
184
+ * // }
185
+ */
186
+ export async function getPaymentMethodSupport(client, businessId) {
187
+ try {
188
+ // Check if business exists (crypto payments always work)
189
+ await client.getBusiness(businessId);
190
+
191
+ let cardSupport = false;
192
+ let stripeOnboardingComplete = false;
193
+
194
+ // Check Stripe status
195
+ try {
196
+ const stripeStatus = await client.getStripeAccountStatus(businessId);
197
+ cardSupport = stripeStatus.onboarding_complete;
198
+ stripeOnboardingComplete = stripeStatus.onboarding_complete;
199
+ } catch (error) {
200
+ // No Stripe account = no card support yet
201
+ cardSupport = false;
202
+ stripeOnboardingComplete = false;
203
+ }
204
+
205
+ return {
206
+ crypto: true, // Always available
207
+ cards: cardSupport,
208
+ escrow: cardSupport, // Card escrow requires Stripe
209
+ stripe_onboarding_complete: stripeOnboardingComplete,
210
+ };
211
+
212
+ } catch (error) {
213
+ if (error.status === 404) {
214
+ return {
215
+ crypto: false,
216
+ cards: false,
217
+ escrow: false,
218
+ stripe_onboarding_complete: false,
219
+ error: 'Business not found',
220
+ };
221
+ }
222
+ throw error;
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Format amount for display
228
+ *
229
+ * Converts cents to dollar amount with proper formatting.
230
+ *
231
+ * @param {number} amountCents - Amount in cents
232
+ * @param {string} [currency='USD'] - Currency code
233
+ * @returns {string} Formatted amount string
234
+ *
235
+ * @example
236
+ * formatCardAmount(5000); // "$50.00"
237
+ * formatCardAmount(5050); // "$50.50"
238
+ * formatCardAmount(500, 'EUR'); // "€5.00"
239
+ */
240
+ export function formatCardAmount(amountCents, currency = 'USD') {
241
+ const amount = amountCents / 100;
242
+
243
+ const formatters = {
244
+ USD: (amt) => `$${amt.toFixed(2)}`,
245
+ EUR: (amt) => `€${amt.toFixed(2)}`,
246
+ GBP: (amt) => `£${amt.toFixed(2)}`,
247
+ CAD: (amt) => `C$${amt.toFixed(2)}`,
248
+ };
249
+
250
+ const formatter = formatters[currency.toUpperCase()];
251
+ if (formatter) {
252
+ return formatter(amount);
253
+ }
254
+
255
+ // Fallback for unknown currencies
256
+ return `${amount.toFixed(2)} ${currency.toUpperCase()}`;
257
+ }
258
+
259
+ /**
260
+ * Calculate platform fees for card payments
261
+ *
262
+ * @param {number} amountCents - Payment amount in cents
263
+ * @param {string} tier - Merchant tier ('free' or 'pro')
264
+ * @returns {Object} Fee breakdown
265
+ *
266
+ * @example
267
+ * const fees = calculateCardPaymentFees(5000, 'free');
268
+ * console.log(fees);
269
+ * // {
270
+ * // amount: 5000,
271
+ * // platformFee: 50,
272
+ * // platformFeePercent: 1,
273
+ * // merchantReceives: 4950 // before Stripe fees
274
+ * // }
275
+ */
276
+ export function calculateCardPaymentFees(amountCents, tier = 'free') {
277
+ const platformFeePercent = tier === 'pro' ? 0.5 : 1.0; // 0.5% or 1%
278
+ const platformFeeCents = Math.round(amountCents * (platformFeePercent / 100));
279
+
280
+ return {
281
+ amount: amountCents,
282
+ platformFee: platformFeeCents,
283
+ platformFeePercent,
284
+ merchantReceives: amountCents - platformFeeCents, // Before Stripe processing fees
285
+ };
286
+ }
287
+
288
+ /**
289
+ * Default export with all convenience functions
290
+ */
291
+ export default {
292
+ createQuickCardPayment,
293
+ waitForStripeOnboarding,
294
+ createCardPaymentWithOnboardingCheck,
295
+ getPaymentMethodSupport,
296
+ formatCardAmount,
297
+ calculateCardPaymentFees,
298
+ };
package/src/client.js CHANGED
@@ -522,6 +522,169 @@ export class CoinPayClient {
522
522
  const { authenticateEscrow } = await import('./escrow.js');
523
523
  return authenticateEscrow(this, escrowId, token);
524
524
  }
525
+
526
+ // ── Stripe Card Payment Methods ────────────────────────
527
+
528
+ /**
529
+ * Create a card payment using Stripe
530
+ *
531
+ * Creates a card payment session through Stripe Connect. Returns a checkout URL
532
+ * that customers can use to pay with their credit/debit card.
533
+ *
534
+ * @param {Object} params - Payment parameters
535
+ * @param {string} params.businessId - Business ID (merchant must have completed Stripe onboarding)
536
+ * @param {number} params.amount - Amount in cents (e.g., 5000 = $50.00)
537
+ * @param {string} [params.currency='usd'] - Currency code (usd, eur, etc.)
538
+ * @param {string} [params.description] - Payment description
539
+ * @param {Object} [params.metadata] - Custom metadata
540
+ * @param {string} [params.successUrl] - URL to redirect on successful payment
541
+ * @param {string} [params.cancelUrl] - URL to redirect on payment cancellation
542
+ * @param {boolean} [params.escrowMode=false] - Whether to use escrow mode (holds funds until release)
543
+ * @returns {Promise<Object>} Payment session with checkout URL
544
+ *
545
+ * @example
546
+ * // Create a $50 card payment
547
+ * const payment = await client.createCardPayment({
548
+ * businessId: 'your-business-id',
549
+ * amount: 5000, // $50.00 in cents
550
+ * currency: 'usd',
551
+ * description: 'Order #12345',
552
+ * metadata: { orderId: '12345' },
553
+ * successUrl: 'https://yourdomain.com/success',
554
+ * cancelUrl: 'https://yourdomain.com/cancel'
555
+ * });
556
+ *
557
+ * // Redirect customer to payment.checkout_url
558
+ */
559
+ async createCardPayment({
560
+ businessId,
561
+ amount,
562
+ currency = 'usd',
563
+ description,
564
+ metadata,
565
+ successUrl,
566
+ cancelUrl,
567
+ escrowMode = false
568
+ }) {
569
+ return this.request('/stripe/payments/create', {
570
+ method: 'POST',
571
+ body: JSON.stringify({
572
+ businessId,
573
+ amount,
574
+ currency,
575
+ description,
576
+ metadata,
577
+ successUrl,
578
+ cancelUrl,
579
+ escrowMode
580
+ }),
581
+ });
582
+ }
583
+
584
+ /**
585
+ * Get Stripe account onboarding status
586
+ *
587
+ * Checks whether a merchant has completed Stripe Connect onboarding
588
+ * and is able to accept card payments.
589
+ *
590
+ * @param {string} businessId - Business ID
591
+ * @returns {Promise<Object>} Account status and capabilities
592
+ *
593
+ * @example
594
+ * const status = await client.getStripeAccountStatus('business-id');
595
+ * if (status.onboarding_complete) {
596
+ * console.log('Merchant can accept card payments');
597
+ * }
598
+ */
599
+ async getStripeAccountStatus(businessId) {
600
+ return this.request(`/stripe/connect/status/${businessId}`);
601
+ }
602
+
603
+ /**
604
+ * Create Stripe Connect onboarding link
605
+ *
606
+ * Generates a Stripe Express onboarding link for a merchant.
607
+ * The merchant must complete this onboarding before they can accept card payments.
608
+ *
609
+ * @param {string} businessId - Business ID
610
+ * @param {Object} [params] - Optional parameters
611
+ * @param {string} [params.email] - Merchant email (pre-fills onboarding form)
612
+ * @param {string} [params.country='US'] - Country code (US, CA, GB, etc.)
613
+ * @returns {Promise<Object>} Onboarding link and account ID
614
+ *
615
+ * @example
616
+ * const onboarding = await client.createStripeOnboardingLink('business-id', {
617
+ * email: 'merchant@example.com',
618
+ * country: 'US'
619
+ * });
620
+ *
621
+ * // Redirect merchant to onboarding.onboarding_url
622
+ */
623
+ async createStripeOnboardingLink(businessId, params = {}) {
624
+ return this.request('/stripe/connect/onboard', {
625
+ method: 'POST',
626
+ body: JSON.stringify({
627
+ businessId,
628
+ ...params
629
+ }),
630
+ });
631
+ }
632
+
633
+ /**
634
+ * Release card escrow funds to merchant
635
+ *
636
+ * Releases funds held in card escrow mode to the merchant's Stripe account.
637
+ * Only works for payments created with escrowMode: true.
638
+ *
639
+ * @param {string} escrowId - Escrow ID (returned from escrow payment creation)
640
+ * @param {string} [reason] - Reason for release
641
+ * @returns {Promise<Object>} Release confirmation with transfer details
642
+ *
643
+ * @example
644
+ * const release = await client.releaseCardEscrow('escrow-id', 'Work completed');
645
+ * console.log(`Released $${release.amount_transferred / 100} to merchant`);
646
+ */
647
+ async releaseCardEscrow(escrowId, reason) {
648
+ return this.request('/stripe/escrow/release', {
649
+ method: 'POST',
650
+ body: JSON.stringify({
651
+ escrowId,
652
+ reason
653
+ }),
654
+ });
655
+ }
656
+
657
+ /**
658
+ * Refund a card payment
659
+ *
660
+ * Creates a refund for a card payment. Works for both gateway and escrow mode payments.
661
+ * For escrow payments, refunds from held funds. For gateway payments, refunds from merchant account.
662
+ *
663
+ * @param {string} paymentId - Payment intent ID or escrow ID
664
+ * @param {Object} [params] - Optional parameters
665
+ * @param {number} [params.amount] - Refund amount in cents (partial refund). Omit for full refund.
666
+ * @param {string} [params.reason] - Reason for refund
667
+ * @returns {Promise<Object>} Refund confirmation
668
+ *
669
+ * @example
670
+ * // Full refund
671
+ * const refund = await client.refundCardPayment('payment-id');
672
+ *
673
+ * // Partial refund
674
+ * const partialRefund = await client.refundCardPayment('payment-id', {
675
+ * amount: 2500, // $25.00 refund
676
+ * reason: 'Customer complaint'
677
+ * });
678
+ */
679
+ async refundCardPayment(paymentId, params = {}) {
680
+ return this.request('/stripe/escrow/refund', {
681
+ method: 'POST',
682
+ body: JSON.stringify({
683
+ escrowId: paymentId, // API expects escrowId param
684
+ ...params
685
+ }),
686
+ });
687
+ }
525
688
  }
526
689
 
527
690
  export default CoinPayClient;