const Stripe = require("stripe"); let stripeClient = null; /** * Get or create a Stripe client. * Reuses client across Lambda invocations for better performance. */ function getStripeClient() { if (!stripeClient) { const secretKey = process.env.STRIPE_SECRET_KEY; if (!secretKey) { throw new Error("STRIPE_SECRET_KEY environment variable is required"); } stripeClient = new Stripe(secretKey); } return stripeClient; } /** * Create a Stripe transfer to a connected account. * @param {Object} params - Transfer parameters * @param {number} params.amount - Amount in dollars (will be converted to cents) * @param {string} params.destination - Stripe connected account ID * @param {string} [params.currency='usd'] - Currency code * @param {Object} [params.metadata={}] - Additional metadata for the transfer * @returns {Promise} Stripe transfer object */ async function createTransfer({ amount, destination, currency = "usd", metadata = {} }) { const stripe = getStripeClient(); // Generate idempotency key from rental ID to prevent duplicate transfers const idempotencyKey = metadata?.rentalId ? `transfer_rental_${metadata.rentalId}` : undefined; const transfer = await stripe.transfers.create( { amount: Math.round(amount * 100), // Convert to cents currency, destination, metadata, }, idempotencyKey ? { idempotencyKey } : undefined ); return transfer; } /** * Check if an error indicates the connected account is disconnected. * Used as fallback detection when webhook was missed. * @param {Error} error - Stripe error object * @returns {boolean} True if error indicates disconnected account */ function isAccountDisconnectedError(error) { // Stripe returns these error codes when account is disconnected or invalid const disconnectedCodes = ["account_invalid", "platform_api_key_expired"]; // Error messages that indicate disconnection const disconnectedMessages = [ "cannot transfer", "not connected", "no longer connected", "account has been deauthorized", ]; if (disconnectedCodes.includes(error.code)) { return true; } const message = (error.message || "").toLowerCase(); return disconnectedMessages.some((msg) => message.includes(msg)); } module.exports = { getStripeClient, createTransfer, isAccountDisconnectedError, };