Files
rentall-app/backend/services/stripeService.js
jackiettran 6853ae264c Add Stripe embedded onboarding
- Update StripeConnectOnboarding component with embedded flow
- Add new Stripe routes and service methods for embedded onboarding
- Update EarningsStatus and EarningsDashboard to support new flow
- Add required frontend dependencies

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 18:26:53 -05:00

231 lines
6.6 KiB
JavaScript

const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
const logger = require("../utils/logger");
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'}/payment-complete`,
metadata,
expand: ['charges.data.payment_method_details'], // Expand to get payment method details
});
// Extract payment method details from charges
const charge = paymentIntent.charges?.data?.[0];
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 {
paymentIntentId: paymentIntent.id,
status: paymentIntent.status,
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) {
logger.error("Error charging payment method", { error: error.message, stack: error.stack });
throw error;
}
}
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 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',
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;