107 lines
3.1 KiB
JavaScript
107 lines
3.1 KiB
JavaScript
const express = require("express");
|
|
const StripeWebhookService = require("../services/stripeWebhookService");
|
|
const DisputeService = require("../services/disputeService");
|
|
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;
|
|
|
|
case "charge.dispute.created":
|
|
// Renter disputed a charge with their bank
|
|
await DisputeService.handleDisputeCreated(event.data.object);
|
|
break;
|
|
|
|
case "charge.dispute.closed":
|
|
case "charge.dispute.funds_reinstated":
|
|
case "charge.dispute.funds_withdrawn":
|
|
// Dispute was resolved (won, lost, or warning closed)
|
|
await DisputeService.handleDisputeClosed(event.data.object);
|
|
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;
|