text changes
This commit is contained in:
@@ -269,11 +269,11 @@ router.post("/", authenticateToken, requireVerifiedEmail, async (req, res) => {
|
||||
totalAmount = RentalDurationCalculator.calculateRentalCost(
|
||||
rentalStartDateTime,
|
||||
rentalEndDateTime,
|
||||
item
|
||||
item,
|
||||
);
|
||||
|
||||
// Check for overlapping rentals using datetime ranges
|
||||
// Note: "active" rentals are stored as "confirmed" with startDateTime in the past
|
||||
// "active" rentals are stored as "confirmed" with startDateTime in the past
|
||||
// Two ranges [A,B] and [C,D] overlap if and only if A < D AND C < B
|
||||
// Here: existing rental [existingStart, existingEnd], new rental [rentalStartDateTime, rentalEndDateTime]
|
||||
// Overlap: existingStart < rentalEndDateTime AND rentalStartDateTime < existingEnd
|
||||
@@ -352,7 +352,7 @@ router.post("/", authenticateToken, requireVerifiedEmail, async (req, res) => {
|
||||
await emailServices.rentalFlow.sendRentalRequestEmail(
|
||||
rentalWithDetails.owner,
|
||||
rentalWithDetails.renter,
|
||||
rentalWithDetails
|
||||
rentalWithDetails,
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Rental request notification sent to owner", {
|
||||
@@ -374,7 +374,7 @@ router.post("/", authenticateToken, requireVerifiedEmail, async (req, res) => {
|
||||
try {
|
||||
await emailServices.rentalFlow.sendRentalRequestConfirmationEmail(
|
||||
rentalWithDetails.renter,
|
||||
rentalWithDetails
|
||||
rentalWithDetails,
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Rental request confirmation sent to renter", {
|
||||
@@ -474,7 +474,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
itemName: rental.item.name,
|
||||
renterId: rental.renterId,
|
||||
ownerId: rental.ownerId,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Check if 3DS authentication is required
|
||||
@@ -494,7 +494,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
itemName: rental.item.name,
|
||||
ownerName: rental.owner.firstName,
|
||||
amount: rental.totalAmount,
|
||||
}
|
||||
},
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Authentication required email sent to renter", {
|
||||
@@ -503,15 +503,12 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
});
|
||||
} catch (emailError) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error(
|
||||
"Failed to send authentication required email",
|
||||
{
|
||||
error: emailError.message,
|
||||
stack: emailError.stack,
|
||||
rentalId: rental.id,
|
||||
renterId: rental.renterId,
|
||||
}
|
||||
);
|
||||
reqLogger.error("Failed to send authentication required email", {
|
||||
error: emailError.message,
|
||||
stack: emailError.stack,
|
||||
rentalId: rental.id,
|
||||
renterId: rental.renterId,
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(402).json({
|
||||
@@ -557,17 +554,14 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
// Create condition check reminder schedules
|
||||
try {
|
||||
await EventBridgeSchedulerService.createConditionCheckSchedules(
|
||||
updatedRental
|
||||
updatedRental,
|
||||
);
|
||||
} catch (schedulerError) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error(
|
||||
"Failed to create condition check schedules",
|
||||
{
|
||||
error: schedulerError.message,
|
||||
rentalId: updatedRental.id,
|
||||
}
|
||||
);
|
||||
reqLogger.error("Failed to create condition check schedules", {
|
||||
error: schedulerError.message,
|
||||
rentalId: updatedRental.id,
|
||||
});
|
||||
// Don't fail the confirmation - schedules are non-critical
|
||||
}
|
||||
|
||||
@@ -577,7 +571,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
await emailServices.rentalFlow.sendRentalApprovalConfirmationEmail(
|
||||
updatedRental.owner,
|
||||
updatedRental.renter,
|
||||
updatedRental
|
||||
updatedRental,
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Rental approval confirmation sent to owner", {
|
||||
@@ -593,7 +587,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
stack: emailError.stack,
|
||||
rentalId: updatedRental.id,
|
||||
ownerId: updatedRental.ownerId,
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -616,7 +610,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
renterNotification,
|
||||
updatedRental,
|
||||
renter.firstName,
|
||||
true // isRenter = true to show payment receipt
|
||||
true, // isRenter = true to show payment receipt
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Rental confirmation sent to renter", {
|
||||
@@ -633,7 +627,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
stack: emailError.stack,
|
||||
rentalId: updatedRental.id,
|
||||
renterId: updatedRental.renterId,
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -670,7 +664,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
itemName: rental.item.name,
|
||||
declineReason: renterMessage,
|
||||
rentalId: rental.id,
|
||||
}
|
||||
},
|
||||
);
|
||||
reqLogger.info("Payment declined email auto-sent to renter", {
|
||||
rentalId: rental.id,
|
||||
@@ -728,17 +722,14 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
// Create condition check reminder schedules
|
||||
try {
|
||||
await EventBridgeSchedulerService.createConditionCheckSchedules(
|
||||
updatedRental
|
||||
updatedRental,
|
||||
);
|
||||
} catch (schedulerError) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error(
|
||||
"Failed to create condition check schedules",
|
||||
{
|
||||
error: schedulerError.message,
|
||||
rentalId: updatedRental.id,
|
||||
}
|
||||
);
|
||||
reqLogger.error("Failed to create condition check schedules", {
|
||||
error: schedulerError.message,
|
||||
rentalId: updatedRental.id,
|
||||
});
|
||||
// Don't fail the confirmation - schedules are non-critical
|
||||
}
|
||||
|
||||
@@ -748,7 +739,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
await emailServices.rentalFlow.sendRentalApprovalConfirmationEmail(
|
||||
updatedRental.owner,
|
||||
updatedRental.renter,
|
||||
updatedRental
|
||||
updatedRental,
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Rental approval confirmation sent to owner", {
|
||||
@@ -764,7 +755,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
stack: emailError.stack,
|
||||
rentalId: updatedRental.id,
|
||||
ownerId: updatedRental.ownerId,
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -787,7 +778,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
renterNotification,
|
||||
updatedRental,
|
||||
renter.firstName,
|
||||
true // isRenter = true (for free rentals, shows "no payment required")
|
||||
true, // isRenter = true (for free rentals, shows "no payment required")
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Rental confirmation sent to renter", {
|
||||
@@ -804,7 +795,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
stack: emailError.stack,
|
||||
rentalId: updatedRental.id,
|
||||
renterId: updatedRental.renterId,
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -910,7 +901,7 @@ router.put("/:id/decline", authenticateToken, async (req, res) => {
|
||||
await emailServices.rentalFlow.sendRentalDeclinedEmail(
|
||||
updatedRental.renter,
|
||||
updatedRental,
|
||||
reason
|
||||
reason,
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Rental decline notification sent to renter", {
|
||||
@@ -1130,7 +1121,7 @@ router.post("/cost-preview", authenticateToken, async (req, res) => {
|
||||
const totalAmount = RentalDurationCalculator.calculateRentalCost(
|
||||
rentalStartDateTime,
|
||||
rentalEndDateTime,
|
||||
item
|
||||
item,
|
||||
);
|
||||
|
||||
// Calculate fees
|
||||
@@ -1202,7 +1193,7 @@ router.get("/:id/refund-preview", authenticateToken, async (req, res, next) => {
|
||||
try {
|
||||
const preview = await RefundService.getRefundPreview(
|
||||
req.params.id,
|
||||
req.user.id
|
||||
req.user.id,
|
||||
);
|
||||
res.json(preview);
|
||||
} catch (error) {
|
||||
@@ -1246,7 +1237,7 @@ router.get(
|
||||
|
||||
const lateCalculation = LateReturnService.calculateLateFee(
|
||||
rental,
|
||||
actualReturnDateTime
|
||||
actualReturnDateTime,
|
||||
);
|
||||
|
||||
res.json(lateCalculation);
|
||||
@@ -1260,7 +1251,7 @@ router.get(
|
||||
});
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Cancel rental with refund processing
|
||||
@@ -1276,7 +1267,7 @@ router.post("/:id/cancel", authenticateToken, async (req, res, next) => {
|
||||
const result = await RefundService.processCancellation(
|
||||
req.params.id,
|
||||
req.user.id,
|
||||
reason.trim()
|
||||
reason.trim(),
|
||||
);
|
||||
|
||||
// Return the updated rental with refund information
|
||||
@@ -1302,7 +1293,7 @@ router.post("/:id/cancel", authenticateToken, async (req, res, next) => {
|
||||
updatedRental.owner,
|
||||
updatedRental.renter,
|
||||
updatedRental,
|
||||
result.refund
|
||||
result.refund,
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Cancellation emails sent", {
|
||||
@@ -1403,7 +1394,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res, next) => {
|
||||
await emailServices.rentalFlow.sendRentalCompletionEmails(
|
||||
rentalWithDetails.owner,
|
||||
rentalWithDetails.renter,
|
||||
rentalWithDetails
|
||||
rentalWithDetails,
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Rental completion emails sent", {
|
||||
@@ -1441,7 +1432,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res, next) => {
|
||||
if (statusOptions?.returned_late && actualReturnDateTime) {
|
||||
const lateReturnDamaged = await LateReturnService.processLateReturn(
|
||||
rentalId,
|
||||
actualReturnDateTime
|
||||
actualReturnDateTime,
|
||||
);
|
||||
damageUpdates.status = "returned_late_and_damaged";
|
||||
damageUpdates.lateFees = lateReturnDamaged.lateCalculation.lateFee;
|
||||
@@ -1463,7 +1454,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res, next) => {
|
||||
|
||||
const lateReturn = await LateReturnService.processLateReturn(
|
||||
rentalId,
|
||||
actualReturnDateTime
|
||||
actualReturnDateTime,
|
||||
);
|
||||
|
||||
updatedRental = lateReturn.rental;
|
||||
@@ -1484,7 +1475,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res, next) => {
|
||||
await emailServices.customerService.sendLostItemToCustomerService(
|
||||
updatedRental,
|
||||
owner,
|
||||
renter
|
||||
renter,
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -1562,7 +1553,7 @@ router.post("/:id/report-damage", authenticateToken, async (req, res, next) => {
|
||||
"damage-reports",
|
||||
{
|
||||
maxKeys: IMAGE_LIMITS.damageReports,
|
||||
}
|
||||
},
|
||||
);
|
||||
if (!keyValidation.valid) {
|
||||
return res.status(400).json({
|
||||
@@ -1576,7 +1567,7 @@ router.post("/:id/report-damage", authenticateToken, async (req, res, next) => {
|
||||
const result = await DamageAssessmentService.processDamageAssessment(
|
||||
rentalId,
|
||||
damageInfo,
|
||||
userId
|
||||
userId,
|
||||
);
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
@@ -1654,7 +1645,7 @@ router.put("/:id/payment-method", authenticateToken, async (req, res, next) => {
|
||||
let paymentMethod;
|
||||
try {
|
||||
paymentMethod = await StripeService.getPaymentMethod(
|
||||
stripePaymentMethodId
|
||||
stripePaymentMethodId,
|
||||
);
|
||||
} catch {
|
||||
return res.status(400).json({ error: "Invalid payment method" });
|
||||
@@ -1699,7 +1690,7 @@ router.put("/:id/payment-method", authenticateToken, async (req, res, next) => {
|
||||
status: "pending",
|
||||
paymentStatus: "pending",
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (updateCount === 0) {
|
||||
@@ -1725,7 +1716,7 @@ router.put("/:id/payment-method", authenticateToken, async (req, res, next) => {
|
||||
itemName: rental.item.name,
|
||||
rentalId: rental.id,
|
||||
approvalUrl: `${process.env.FRONTEND_URL}/rentals/${rentalId}`,
|
||||
}
|
||||
},
|
||||
);
|
||||
} catch (emailError) {
|
||||
// Don't fail the request if email fails
|
||||
@@ -1781,7 +1772,7 @@ router.get(
|
||||
|
||||
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
|
||||
const paymentIntent = await stripe.paymentIntents.retrieve(
|
||||
rental.stripePaymentIntentId
|
||||
rental.stripePaymentIntentId,
|
||||
);
|
||||
|
||||
return res.json({
|
||||
@@ -1798,7 +1789,7 @@ router.get(
|
||||
});
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -1812,8 +1803,29 @@ router.post(
|
||||
try {
|
||||
const rental = await Rental.findByPk(req.params.id, {
|
||||
include: [
|
||||
{ model: User, as: "renter", attributes: ["id", "firstName", "lastName", "email", "stripeCustomerId"] },
|
||||
{ model: User, as: "owner", attributes: ["id", "firstName", "lastName", "email", "stripeConnectedAccountId", "stripePayoutsEnabled"] },
|
||||
{
|
||||
model: User,
|
||||
as: "renter",
|
||||
attributes: [
|
||||
"id",
|
||||
"firstName",
|
||||
"lastName",
|
||||
"email",
|
||||
"stripeCustomerId",
|
||||
],
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: "owner",
|
||||
attributes: [
|
||||
"id",
|
||||
"firstName",
|
||||
"lastName",
|
||||
"email",
|
||||
"stripeConnectedAccountId",
|
||||
"stripePayoutsEnabled",
|
||||
],
|
||||
},
|
||||
{ model: Item, as: "item" },
|
||||
],
|
||||
});
|
||||
@@ -1837,7 +1849,7 @@ router.post(
|
||||
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
|
||||
const paymentIntent = await stripe.paymentIntents.retrieve(
|
||||
rental.stripePaymentIntentId,
|
||||
{ expand: ['latest_charge.payment_method_details'] }
|
||||
{ expand: ["latest_charge.payment_method_details"] },
|
||||
);
|
||||
|
||||
if (paymentIntent.status !== "succeeded") {
|
||||
@@ -1864,7 +1876,8 @@ router.post(
|
||||
paymentMethodLast4 = paymentMethodDetails.card?.last4 || null;
|
||||
} else if (type === "us_bank_account") {
|
||||
paymentMethodBrand = "bank_account";
|
||||
paymentMethodLast4 = paymentMethodDetails.us_bank_account?.last4 || null;
|
||||
paymentMethodLast4 =
|
||||
paymentMethodDetails.us_bank_account?.last4 || null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1882,13 +1895,10 @@ router.post(
|
||||
await EventBridgeSchedulerService.createConditionCheckSchedules(rental);
|
||||
} catch (schedulerError) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error(
|
||||
"Failed to create condition check schedules",
|
||||
{
|
||||
error: schedulerError.message,
|
||||
rentalId: rental.id,
|
||||
}
|
||||
);
|
||||
reqLogger.error("Failed to create condition check schedules", {
|
||||
error: schedulerError.message,
|
||||
rentalId: rental.id,
|
||||
});
|
||||
// Don't fail the confirmation - schedules are non-critical
|
||||
}
|
||||
|
||||
@@ -1897,13 +1907,16 @@ router.post(
|
||||
await emailServices.rentalFlow.sendRentalApprovalConfirmationEmail(
|
||||
rental.owner,
|
||||
rental.renter,
|
||||
rental
|
||||
rental,
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Rental approval confirmation sent to owner (after 3DS)", {
|
||||
rentalId: rental.id,
|
||||
ownerId: rental.ownerId,
|
||||
});
|
||||
reqLogger.info(
|
||||
"Rental approval confirmation sent to owner (after 3DS)",
|
||||
{
|
||||
rentalId: rental.id,
|
||||
ownerId: rental.ownerId,
|
||||
},
|
||||
);
|
||||
} catch (emailError) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error(
|
||||
@@ -1911,7 +1924,7 @@ router.post(
|
||||
{
|
||||
error: emailError.message,
|
||||
rentalId: rental.id,
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1929,7 +1942,7 @@ router.post(
|
||||
renterNotification,
|
||||
rental,
|
||||
rental.renter.firstName,
|
||||
true
|
||||
true,
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Rental confirmation sent to renter (after 3DS)", {
|
||||
@@ -1938,17 +1951,17 @@ router.post(
|
||||
});
|
||||
} catch (emailError) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error(
|
||||
"Failed to send rental confirmation email after 3DS",
|
||||
{
|
||||
error: emailError.message,
|
||||
rentalId: rental.id,
|
||||
}
|
||||
);
|
||||
reqLogger.error("Failed to send rental confirmation email after 3DS", {
|
||||
error: emailError.message,
|
||||
rentalId: rental.id,
|
||||
});
|
||||
}
|
||||
|
||||
// Trigger payout if owner has payouts enabled
|
||||
if (rental.owner.stripePayoutsEnabled && rental.owner.stripeConnectedAccountId) {
|
||||
if (
|
||||
rental.owner.stripePayoutsEnabled &&
|
||||
rental.owner.stripeConnectedAccountId
|
||||
) {
|
||||
try {
|
||||
await PayoutService.processRentalPayout(rental);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
@@ -1983,7 +1996,7 @@ router.post(
|
||||
});
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user