const express = require("express"); const { authenticateToken, requireVerifiedEmail } = require("../middleware/auth"); const { User, Item } = require("../models"); const StripeService = require("../services/stripeService"); const StripeWebhookService = require("../services/stripeWebhookService"); const emailServices = require("../services/email"); const logger = require("../utils/logger"); const router = express.Router(); // Get checkout session status router.get("/checkout-session/:sessionId", async (req, res, next) => { try { const { sessionId } = req.params; const session = await StripeService.getCheckoutSession(sessionId); const reqLogger = logger.withRequestId(req.id); reqLogger.info("Stripe checkout session retrieved", { sessionId: sessionId, status: session.status, payment_status: session.payment_status, metadata: session.metadata, }); res.json({ status: session.status, payment_status: session.payment_status, customer_email: session.customer_details?.email, setup_intent: session.setup_intent, metadata: session.metadata, }); } catch (error) { const reqLogger = logger.withRequestId(req.id); reqLogger.error("Stripe checkout session retrieval failed", { error: error.message, stack: error.stack, sessionId: req.params.sessionId, }); next(error); } }); // Create connected account router.post("/accounts", authenticateToken, requireVerifiedEmail, async (req, res, next) => { try { const user = await User.findByPk(req.user.id); if (!user) { return res.status(404).json({ error: "User not found" }); } // Check if user already has a connected account if (user.stripeConnectedAccountId) { return res .status(400) .json({ error: "User already has a connected account" }); } // Create connected account const account = await StripeService.createConnectedAccount({ email: user.email, country: "US", // You may want to make this configurable }); // Update user with account ID await user.update({ stripeConnectedAccountId: account.id, }); const reqLogger = logger.withRequestId(req.id); reqLogger.info("Stripe connected account created", { userId: req.user.id, stripeConnectedAccountId: account.id, }); res.json({ stripeConnectedAccountId: account.id, success: true, }); } catch (error) { const reqLogger = logger.withRequestId(req.id); reqLogger.error("Stripe connected account creation failed", { error: error.message, stack: error.stack, userId: req.user.id, }); next(error); } }); // Generate onboarding link router.post("/account-links", authenticateToken, requireVerifiedEmail, async (req, res, next) => { let user = null; try { user = await User.findByPk(req.user.id); if (!user || !user.stripeConnectedAccountId) { return res.status(400).json({ error: "No connected account found" }); } const { refreshUrl, returnUrl } = req.body; if (!refreshUrl || !returnUrl) { return res .status(400) .json({ error: "refreshUrl and returnUrl are required" }); } const accountLink = await StripeService.createAccountLink( user.stripeConnectedAccountId, refreshUrl, returnUrl ); const reqLogger = logger.withRequestId(req.id); reqLogger.info("Stripe account link created", { userId: req.user.id, stripeConnectedAccountId: user.stripeConnectedAccountId, expiresAt: accountLink.expires_at, }); res.json({ url: accountLink.url, expiresAt: accountLink.expires_at, }); } catch (error) { const reqLogger = logger.withRequestId(req.id); reqLogger.error("Stripe account link creation failed", { error: error.message, stack: error.stack, userId: req.user.id, stripeConnectedAccountId: user?.stripeConnectedAccountId, }); next(error); } }); // Create account session for embedded onboarding router.post("/account-sessions", authenticateToken, requireVerifiedEmail, async (req, res, next) => { let user = null; try { user = await User.findByPk(req.user.id); if (!user || !user.stripeConnectedAccountId) { return res.status(400).json({ error: "No connected account found" }); } const accountSession = await StripeService.createAccountSession( user.stripeConnectedAccountId ); const reqLogger = logger.withRequestId(req.id); reqLogger.info("Stripe account session created", { userId: req.user.id, stripeConnectedAccountId: user.stripeConnectedAccountId, }); res.json({ clientSecret: accountSession.client_secret, }); } catch (error) { const reqLogger = logger.withRequestId(req.id); reqLogger.error("Stripe account session creation failed", { error: error.message, stack: error.stack, userId: req.user.id, stripeConnectedAccountId: user?.stripeConnectedAccountId, }); next(error); } }); // Get account status with reconciliation router.get("/account-status", authenticateToken, async (req, res, next) => { let user = null; try { user = await User.findByPk(req.user.id); if (!user || !user.stripeConnectedAccountId) { return res.status(400).json({ error: "No connected account found" }); } const accountStatus = await StripeService.getAccountStatus( user.stripeConnectedAccountId ); const reqLogger = logger.withRequestId(req.id); reqLogger.info("Stripe account status retrieved", { userId: req.user.id, stripeConnectedAccountId: user.stripeConnectedAccountId, detailsSubmitted: accountStatus.details_submitted, payoutsEnabled: accountStatus.payouts_enabled, }); // Reconciliation: Compare fetched status with stored User fields const previousPayoutsEnabled = user.stripePayoutsEnabled; const currentPayoutsEnabled = accountStatus.payouts_enabled; const requirements = accountStatus.requirements || {}; // Check if status has changed and needs updating const statusChanged = previousPayoutsEnabled !== currentPayoutsEnabled || JSON.stringify(user.stripeRequirementsCurrentlyDue || []) !== JSON.stringify(requirements.currently_due || []); if (statusChanged) { reqLogger.info("Reconciling account status from API call", { userId: req.user.id, previousPayoutsEnabled, currentPayoutsEnabled, previousCurrentlyDue: user.stripeRequirementsCurrentlyDue?.length || 0, newCurrentlyDue: requirements.currently_due?.length || 0, }); // Update user with current status await user.update({ stripePayoutsEnabled: currentPayoutsEnabled, stripeRequirementsCurrentlyDue: requirements.currently_due || [], stripeRequirementsPastDue: requirements.past_due || [], stripeDisabledReason: requirements.disabled_reason || null, stripeRequirementsLastUpdated: new Date(), }); // If payouts just became disabled (true -> false), send notification if (!currentPayoutsEnabled && previousPayoutsEnabled) { reqLogger.warn("Payouts disabled detected during reconciliation", { userId: req.user.id, disabledReason: requirements.disabled_reason, }); try { const disabledReason = StripeWebhookService.formatDisabledReason( requirements.disabled_reason ); await emailServices.payment.sendPayoutsDisabledEmail(user.email, { ownerName: user.firstName || user.lastName, disabledReason, }); reqLogger.info("Sent payouts disabled email during reconciliation", { userId: req.user.id, }); } catch (emailError) { reqLogger.error("Failed to send payouts disabled email", { userId: req.user.id, error: emailError.message, }); } } } res.json({ accountId: accountStatus.id, detailsSubmitted: accountStatus.details_submitted, payoutsEnabled: accountStatus.payouts_enabled, capabilities: accountStatus.capabilities, requirements: accountStatus.requirements, }); } catch (error) { const reqLogger = logger.withRequestId(req.id); reqLogger.error("Stripe account status retrieval failed", { error: error.message, stack: error.stack, userId: req.user.id, stripeConnectedAccountId: user?.stripeConnectedAccountId, }); next(error); } }); // Create embedded setup checkout session for collecting payment method router.post( "/create-setup-checkout-session", authenticateToken, requireVerifiedEmail, async (req, res, next) => { let user = null; try { const { rentalData } = req.body; user = await User.findByPk(req.user.id); if (!user) { return res.status(404).json({ error: "User not found" }); } // Create or get Stripe customer let stripeCustomerId = user.stripeCustomerId; if (!stripeCustomerId) { // Create new Stripe customer const customer = await StripeService.createCustomer({ email: user.email, name: `${user.firstName} ${user.lastName}`, metadata: { userId: user.id.toString(), }, }); stripeCustomerId = customer.id; // Save customer ID to user record await user.update({ stripeCustomerId }); } // Add rental data to metadata if provided const metadata = rentalData ? { rentalData: JSON.stringify(rentalData), } : {}; const session = await StripeService.createSetupCheckoutSession({ customerId: stripeCustomerId, metadata, }); const reqLogger = logger.withRequestId(req.id); reqLogger.info("Stripe setup checkout session created", { userId: req.user.id, stripeCustomerId: stripeCustomerId, sessionId: session.id, hasRentalData: !!rentalData, }); res.json({ clientSecret: session.client_secret, sessionId: session.id, }); } catch (error) { const reqLogger = logger.withRequestId(req.id); reqLogger.error("Stripe setup checkout session creation failed", { error: error.message, stack: error.stack, userId: req.user.id, stripeCustomerId: user?.stripeCustomerId, }); next(error); } } ); module.exports = router;