const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY); const logger = require("../utils/logger"); const { parseStripeError, PaymentError } = require("../utils/stripeErrors"); class StripeService { static async getCheckoutSession(sessionId) { try { return await stripe.checkout.sessions.retrieve(sessionId, { expand: ["setup_intent", "setup_intent.payment_method"], }); } catch (error) { logger.error("Error retrieving checkout session", { error: error.message, stack: error.stack, }); throw error; } } static async createConnectedAccount({ email, country = "US" }) { try { const account = await stripe.accounts.create({ type: "express", email, country, capabilities: { transfers: { requested: true }, }, }); return account; } catch (error) { logger.error("Error creating connected account", { error: error.message, stack: error.stack, }); throw error; } } static async createAccountLink(accountId, refreshUrl, returnUrl) { try { const accountLink = await stripe.accountLinks.create({ account: accountId, refresh_url: refreshUrl, return_url: returnUrl, type: "account_onboarding", }); return accountLink; } catch (error) { logger.error("Error creating account link", { error: error.message, stack: error.stack, }); throw error; } } static async getAccountStatus(accountId) { try { const account = await stripe.accounts.retrieve(accountId); return { id: account.id, details_submitted: account.details_submitted, payouts_enabled: account.payouts_enabled, capabilities: account.capabilities, requirements: account.requirements, }; } catch (error) { logger.error("Error retrieving account status", { error: error.message, stack: error.stack, }); throw error; } } static async createAccountSession(accountId) { try { const accountSession = await stripe.accountSessions.create({ account: accountId, components: { account_onboarding: { enabled: true }, }, }); return accountSession; } catch (error) { logger.error("Error creating account session", { error: error.message, stack: error.stack, }); throw error; } } static async createTransfer({ amount, currency = "usd", destination, metadata = {}, }) { try { const transfer = await stripe.transfers.create({ amount: Math.round(amount * 100), // Convert to cents currency, destination, metadata, }); return transfer; } catch (error) { logger.error("Error creating transfer", { error: error.message, stack: error.stack, }); throw error; } } static async createRefund({ paymentIntentId, amount, metadata = {}, reason = "requested_by_customer", }) { try { const refund = await stripe.refunds.create({ payment_intent: paymentIntentId, amount: Math.round(amount * 100), // Convert to cents metadata, reason, }); return refund; } catch (error) { logger.error("Error creating refund", { error: error.message, stack: error.stack, }); throw error; } } static async getRefund(refundId) { try { return await stripe.refunds.retrieve(refundId); } catch (error) { logger.error("Error retrieving refund", { error: error.message, stack: error.stack, }); throw error; } } static async chargePaymentMethod( paymentMethodId, amount, customerId, metadata = {} ) { try { // Create a payment intent with the stored payment method const paymentIntent = await stripe.paymentIntents.create({ amount: Math.round(amount * 100), // Convert to cents currency: "usd", payment_method: paymentMethodId, customer: customerId, // Include customer ID confirm: true, // Automatically confirm the payment off_session: true, // Indicate this is an off-session payment return_url: `${ process.env.FRONTEND_URL || "http://localhost:3000" }/complete-payment`, metadata, expand: ["latest_charge.payment_method_details"], // Expand to get payment method details }); // Check if additional authentication is required if (paymentIntent.status === "requires_action") { return { status: "requires_action", requiresAction: true, paymentIntentId: paymentIntent.id, clientSecret: paymentIntent.client_secret, }; } // Extract payment method details from latest_charge const charge = paymentIntent.latest_charge; const paymentMethodDetails = charge?.payment_method_details; // Build payment method info object let paymentMethod = null; if (paymentMethodDetails) { const type = paymentMethodDetails.type; if (type === "card") { paymentMethod = { type: "card", brand: paymentMethodDetails.card?.brand || "card", last4: paymentMethodDetails.card?.last4 || "****", }; } else if (type === "us_bank_account") { paymentMethod = { type: "bank", brand: "bank_account", last4: paymentMethodDetails.us_bank_account?.last4 || "****", }; } else { paymentMethod = { type: type || "unknown", brand: type || "payment", last4: null, }; } } return { status: "succeeded", paymentIntentId: paymentIntent.id, clientSecret: paymentIntent.client_secret, paymentMethod: paymentMethod, chargedAt: new Date(paymentIntent.created * 1000), // Convert Unix timestamp to Date amountCharged: amount, // Original amount in dollars }; } catch (error) { // Handle authentication_required error (thrown for off-session 3DS) if (error.code === "authentication_required") { return { status: "requires_action", requiresAction: true, paymentIntentId: error.payment_intent?.id, clientSecret: error.payment_intent?.client_secret, }; } // Parse Stripe error into structured format const parsedError = parseStripeError(error); logger.error("Payment failed", { code: parsedError.code, ownerMessage: parsedError.ownerMessage, originalError: parsedError._originalMessage, stripeCode: parsedError._stripeCode, paymentMethodId, customerId, amount, stack: error.stack, }); throw new PaymentError(parsedError); } } static async createCustomer({ email, name, metadata = {} }) { try { const customer = await stripe.customers.create({ email, name, metadata, }); return customer; } catch (error) { logger.error("Error creating customer", { error: error.message, stack: error.stack, }); throw error; } } static async getPaymentMethod(paymentMethodId) { try { return await stripe.paymentMethods.retrieve(paymentMethodId); } catch (error) { logger.error("Error retrieving payment method", { error: error.message, paymentMethodId, }); throw error; } } static async createSetupCheckoutSession({ customerId, metadata = {} }) { try { const session = await stripe.checkout.sessions.create({ customer: customerId, payment_method_types: ["card", "us_bank_account", "link"], mode: "setup", ui_mode: "embedded", redirect_on_completion: "never", // Configure for off-session usage - triggers 3DS during setup payment_method_options: { card: { request_three_d_secure: "any", }, }, metadata: { type: "payment_method_setup", ...metadata, }, }); return session; } catch (error) { logger.error("Error creating setup checkout session", { error: error.message, stack: error.stack, }); throw error; } } } module.exports = StripeService;