This commit is contained in:
jackiettran
2025-09-02 16:15:09 -04:00
parent b52104c3fa
commit b59fc07fc3
23 changed files with 1080 additions and 417 deletions

View File

@@ -98,13 +98,12 @@ router.post("/", authenticateToken, async (req, res) => {
try {
const {
itemId,
startDate,
endDate,
startTime,
endTime,
startDateTime,
endDateTime,
deliveryMethod,
deliveryAddress,
notes,
paymentStatus,
} = req.body;
const item = await Item.findByPk(itemId);
@@ -116,16 +115,57 @@ router.post("/", authenticateToken, async (req, res) => {
return res.status(400).json({ error: "Item is not available" });
}
let rentalStartDateTime, rentalEndDateTime, baseRentalAmount;
// New UTC datetime format
rentalStartDateTime = new Date(startDateTime);
rentalEndDateTime = new Date(endDateTime);
// Calculate rental duration
const diffMs = rentalEndDateTime.getTime() - rentalStartDateTime.getTime();
const diffHours = Math.ceil(diffMs / (1000 * 60 * 60));
const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
// Calculate base amount based on duration
if (item.pricePerHour && diffHours <= 24) {
baseRentalAmount = diffHours * Number(item.pricePerHour);
} else if (item.pricePerDay) {
baseRentalAmount = diffDays * Number(item.pricePerDay);
} else {
baseRentalAmount = 0;
}
// Check for overlapping rentals using datetime ranges
const overlappingRental = await Rental.findOne({
where: {
itemId,
status: { [Op.in]: ["confirmed", "active"] },
[Op.or]: [
{
startDate: { [Op.between]: [startDate, endDate] },
},
{
endDate: { [Op.between]: [startDate, endDate] },
[Op.and]: [
{ startDateTime: { [Op.not]: null } },
{ endDateTime: { [Op.not]: null } },
{
[Op.or]: [
{
startDateTime: {
[Op.between]: [rentalStartDateTime, rentalEndDateTime],
},
},
{
endDateTime: {
[Op.between]: [rentalStartDateTime, rentalEndDateTime],
},
},
{
[Op.and]: [
{ startDateTime: { [Op.lte]: rentalStartDateTime } },
{ endDateTime: { [Op.gte]: rentalEndDateTime } },
],
},
],
},
],
},
],
},
@@ -137,11 +177,6 @@ router.post("/", authenticateToken, async (req, res) => {
.json({ error: "Item is already booked for these dates" });
}
const rentalDays = Math.ceil(
(new Date(endDate) - new Date(startDate)) / (1000 * 60 * 60 * 24)
);
const baseRentalAmount = rentalDays * (item.pricePerDay || 0);
// Calculate fees using FeeCalculator
const fees = FeeCalculator.calculateRentalFees(baseRentalAmount);
@@ -149,15 +184,14 @@ router.post("/", authenticateToken, async (req, res) => {
itemId,
renterId: req.user.id,
ownerId: item.ownerId,
startDate,
endDate,
startTime,
endTime,
startDateTime: rentalStartDateTime,
endDateTime: rentalEndDateTime,
totalAmount: fees.totalChargedAmount,
baseRentalAmount: fees.baseRentalAmount,
platformFee: fees.platformFee,
processingFee: fees.processingFee,
payoutAmount: fees.payoutAmount,
paymentStatus: paymentStatus || "pending",
deliveryMethod,
deliveryAddress,
notes,
@@ -356,34 +390,6 @@ router.post("/:id/mark-completed", authenticateToken, async (req, res) => {
}
});
// Legacy review endpoint (for backward compatibility)
router.post("/:id/review", authenticateToken, async (req, res) => {
try {
const { rating, review } = req.body;
const rental = await Rental.findByPk(req.params.id);
if (!rental) {
return res.status(404).json({ error: "Rental not found" });
}
if (rental.renterId !== req.user.id) {
return res.status(403).json({ error: "Only renters can leave reviews" });
}
if (rental.status !== "completed") {
return res
.status(400)
.json({ error: "Can only review completed rentals" });
}
await rental.update({ rating, review });
res.json(rental);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Calculate fees for rental pricing display
router.post("/calculate-fees", authenticateToken, async (req, res) => {
try {
@@ -406,8 +412,8 @@ router.post("/calculate-fees", authenticateToken, async (req, res) => {
}
});
// Get payout status for owner's rentals
router.get("/payouts/status", authenticateToken, async (req, res) => {
// Get earnings status for owner's rentals
router.get("/earnings/status", authenticateToken, async (req, res) => {
try {
const ownerRentals = await Rental.findAll({
where: {
@@ -429,7 +435,7 @@ router.get("/payouts/status", authenticateToken, async (req, res) => {
res.json(ownerRentals);
} catch (error) {
console.error("Error getting payout status:", error);
console.error("Error getting earnings status:", error);
res.status(500).json({ error: error.message });
}
});

View File

@@ -1,12 +1,10 @@
const express = require("express");
const { authenticateToken } = require("../middleware/auth");
const { User } = require("../models");
const { Rental, Item } = require("../models");
const { User, Item } = require("../models");
const StripeService = require("../services/stripeService");
const router = express.Router();
const platformFee = 0.1;
router.post("/create-checkout-session", async (req, res) => {
router.post("/create-checkout-session", authenticateToken, async (req, res) => {
try {
const { itemName, total, return_url, rentalData } = req.body;
@@ -20,18 +18,37 @@ router.post("/create-checkout-session", async (req, res) => {
return res.status(400).json({ error: "No return_url found" });
}
// Validate rental data and user authorization
if (rentalData && rentalData.itemId) {
const item = await Item.findByPk(rentalData.itemId);
if (!item) {
return res.status(404).json({ error: "Item not found" });
}
if (!item.availability) {
return res
.status(400)
.json({ error: "Item is not available for rent" });
}
// Check if user is trying to rent their own item
if (item.ownerId === req.user.id) {
return res.status(400).json({ error: "You cannot rent your own item" });
}
}
// Prepare metadata - Stripe metadata keys must be strings
const metadata = rentalData
? {
itemId: rentalData.itemId,
startDate: rentalData.startDate,
endDate: rentalData.endDate,
startTime: rentalData.startTime,
endTime: rentalData.endTime,
renterId: req.user.id.toString(), // Add authenticated user ID
startDateTime: rentalData.startDateTime,
endDateTime: rentalData.endDateTime,
totalAmount: rentalData.totalAmount.toString(),
deliveryMethod: rentalData.deliveryMethod,
}
: {};
: { renterId: req.user.id.toString() };
const session = await StripeService.createCheckoutSession({
item_name: itemName,