const express = require("express"); const StripeWebhookService = require("../services/stripeWebhookService"); const logger = require("../utils/logger"); const router = express.Router(); const WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET; /** * POST /stripe/webhooks * Stripe webhook endpoint - receives events from Stripe. * Must use raw body for signature verification. */ router.post("/", async (req, res) => { const signature = req.headers["stripe-signature"]; if (!signature) { logger.warn("Webhook request missing stripe-signature header"); return res.status(400).json({ error: "Missing signature" }); } if (!WEBHOOK_SECRET) { logger.error("STRIPE_WEBHOOK_SECRET not configured"); return res.status(500).json({ error: "Webhook not configured" }); } let event; try { // Use rawBody stored by bodyParser in server.js event = StripeWebhookService.constructEvent( req.rawBody, signature, WEBHOOK_SECRET ); } catch (err) { logger.error("Webhook signature verification failed", { error: err.message, }); return res.status(400).json({ error: "Invalid signature" }); } // Log event receipt for debugging // For Connect account events, event.account contains the connected account ID logger.info("Stripe webhook received", { eventId: event.id, eventType: event.type, connectedAccount: event.account || null, }); try { switch (event.type) { case "account.updated": await StripeWebhookService.handleAccountUpdated(event.data.object); break; case "payout.paid": // Payout to connected account's bank succeeded await StripeWebhookService.handlePayoutPaid( event.data.object, event.account ); break; case "payout.failed": // Payout to connected account's bank failed await StripeWebhookService.handlePayoutFailed( event.data.object, event.account ); break; default: logger.info("Unhandled webhook event type", { type: event.type }); } // Always return 200 to acknowledge receipt res.json({ received: true, eventId: event.id }); } catch (error) { logger.error("Error processing webhook", { eventId: event.id, eventType: event.type, error: error.message, stack: error.stack, }); // Still return 200 to prevent Stripe retries for processing errors // Failed payouts will be handled by retry job res.json({ received: true, eventId: event.id, error: error.message }); } }); module.exports = router;