81 lines
2.3 KiB
JavaScript
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,
|
|
};
|