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+ } ;
0 commit comments