Files
rentall-app/lambdas/shared/stripe/client.js
2026-01-14 18:05:41 -05:00

81 lines
2.3 KiB
JavaScript

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<Object>} 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,
};