stripe webhooks. removed payout cron. webhook for when amount is deposited into bank. More communication about payout timelines
This commit is contained in:
@@ -9,6 +9,7 @@ const FeeCalculator = require("../utils/feeCalculator");
|
||||
const RentalDurationCalculator = require("../utils/rentalDurationCalculator");
|
||||
const RefundService = require("../services/refundService");
|
||||
const LateReturnService = require("../services/lateReturnService");
|
||||
const PayoutService = require("../services/payoutService");
|
||||
const DamageAssessmentService = require("../services/damageAssessmentService");
|
||||
const emailServices = require("../services/email");
|
||||
const logger = require("../utils/logger");
|
||||
@@ -877,51 +878,6 @@ router.post("/:id/review-item", authenticateToken, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Mark rental as completed (owner only)
|
||||
router.post("/:id/mark-completed", authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const rental = await Rental.findByPk(req.params.id);
|
||||
|
||||
if (!rental) {
|
||||
return res.status(404).json({ error: "Rental not found" });
|
||||
}
|
||||
|
||||
if (rental.ownerId !== req.user.id) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ error: "Only owners can mark rentals as completed" });
|
||||
}
|
||||
|
||||
if (!isActive(rental)) {
|
||||
return res.status(400).json({
|
||||
error: "Can only mark active rentals as completed",
|
||||
});
|
||||
}
|
||||
|
||||
await rental.update({ status: "completed", payoutStatus: "pending" });
|
||||
|
||||
const updatedRental = await Rental.findByPk(rental.id, {
|
||||
include: [
|
||||
{ model: Item, as: "item" },
|
||||
{
|
||||
model: User,
|
||||
as: "owner",
|
||||
attributes: ["id", "firstName", "lastName"],
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: "renter",
|
||||
attributes: ["id", "firstName", "lastName"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
res.json(updatedRental);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: "Failed to update rental" });
|
||||
}
|
||||
});
|
||||
|
||||
// Calculate fees for rental pricing display
|
||||
router.post("/calculate-fees", authenticateToken, async (req, res) => {
|
||||
try {
|
||||
@@ -1270,6 +1226,14 @@ router.post("/:id/mark-return", authenticateToken, async (req, res, next) => {
|
||||
rentalId,
|
||||
});
|
||||
}
|
||||
|
||||
// Trigger immediate payout attempt (non-blocking)
|
||||
PayoutService.triggerPayoutOnCompletion(rentalId).catch((err) => {
|
||||
logger.error("Error triggering payout on mark-return", {
|
||||
rentalId,
|
||||
error: err.message,
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
case "damaged":
|
||||
|
||||
93
backend/routes/stripeWebhooks.js
Normal file
93
backend/routes/stripeWebhooks.js
Normal file
@@ -0,0 +1,93 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user