payment for rental from renter stripe integration
This commit is contained in:
21
backend/package-lock.json
generated
21
backend/package-lock.json
generated
@@ -19,6 +19,7 @@
|
||||
"pg": "^8.16.3",
|
||||
"sequelize": "^6.37.7",
|
||||
"sequelize-cli": "^6.6.3",
|
||||
"stripe": "^18.4.0",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -2201,6 +2202,26 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/stripe": {
|
||||
"version": "18.4.0",
|
||||
"resolved": "https://registry.npmjs.org/stripe/-/stripe-18.4.0.tgz",
|
||||
"integrity": "sha512-LKFeDnDYo4U/YzNgx2Lc9PT9XgKN0JNF1iQwZxgkS4lOw5NunWCnzyH5RhTlD3clIZnf54h7nyMWkS8VXPmtTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"qs": "^6.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">=12.x.x"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"pg": "^8.16.3",
|
||||
"sequelize": "^6.37.7",
|
||||
"sequelize-cli": "^6.6.3",
|
||||
"stripe": "^18.4.0",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const express = require('express');
|
||||
const { Op } = require('sequelize');
|
||||
const { Rental, Item, User } = require('../models'); // Import from models/index.js to get models with associations
|
||||
const { authenticateToken } = require('../middleware/auth');
|
||||
const express = require("express");
|
||||
const { Op } = require("sequelize");
|
||||
const { Rental, Item, User } = require("../models"); // Import from models/index.js to get models with associations
|
||||
const { authenticateToken } = require("../middleware/auth");
|
||||
const router = express.Router();
|
||||
|
||||
// Helper function to check and update review visibility
|
||||
@@ -31,7 +31,8 @@ const checkAndUpdateReviewVisibility = async (rental) => {
|
||||
|
||||
// Check renter review visibility (10-minute rule)
|
||||
if (rental.renterReviewSubmittedAt && !rental.renterReviewVisible) {
|
||||
const timeSinceSubmission = now - new Date(rental.renterReviewSubmittedAt);
|
||||
const timeSinceSubmission =
|
||||
now - new Date(rental.renterReviewSubmittedAt);
|
||||
if (timeSinceSubmission >= tenMinutesInMs) {
|
||||
updates.renterReviewVisible = true;
|
||||
needsUpdate = true;
|
||||
@@ -46,79 +47,98 @@ const checkAndUpdateReviewVisibility = async (rental) => {
|
||||
return rental;
|
||||
};
|
||||
|
||||
router.get('/my-rentals', authenticateToken, async (req, res) => {
|
||||
router.get("/my-rentals", authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const rentals = await Rental.findAll({
|
||||
where: { renterId: req.user.id },
|
||||
// Remove explicit attributes to let Sequelize handle missing columns gracefully
|
||||
include: [
|
||||
{ model: Item, as: 'item' },
|
||||
{ model: User, as: 'owner', attributes: ['id', 'username', 'firstName', 'lastName'] }
|
||||
{ model: Item, as: "item" },
|
||||
{
|
||||
model: User,
|
||||
as: "owner",
|
||||
attributes: ["id", "username", "firstName", "lastName"],
|
||||
},
|
||||
],
|
||||
order: [['createdAt', 'DESC']]
|
||||
order: [["createdAt", "DESC"]],
|
||||
});
|
||||
|
||||
console.log('My-rentals data:', rentals.length > 0 ? rentals[0].toJSON() : 'No rentals');
|
||||
res.json(rentals);
|
||||
} catch (error) {
|
||||
console.error('Error in my-rentals route:', error);
|
||||
console.error("Error in my-rentals route:", error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/my-listings', authenticateToken, async (req, res) => {
|
||||
router.get("/my-listings", authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const rentals = await Rental.findAll({
|
||||
where: { ownerId: req.user.id },
|
||||
// Remove explicit attributes to let Sequelize handle missing columns gracefully
|
||||
include: [
|
||||
{ model: Item, as: 'item' },
|
||||
{ model: User, as: 'renter', attributes: ['id', 'username', 'firstName', 'lastName'] }
|
||||
{ model: Item, as: "item" },
|
||||
{
|
||||
model: User,
|
||||
as: "renter",
|
||||
attributes: ["id", "username", "firstName", "lastName"],
|
||||
},
|
||||
],
|
||||
order: [['createdAt', 'DESC']]
|
||||
order: [["createdAt", "DESC"]],
|
||||
});
|
||||
|
||||
console.log('My-listings rentals:', rentals.length > 0 ? rentals[0].toJSON() : 'No rentals');
|
||||
res.json(rentals);
|
||||
} catch (error) {
|
||||
console.error('Error in my-listings route:', error);
|
||||
console.error("Error in my-listings route:", error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/', authenticateToken, async (req, res) => {
|
||||
router.post("/", authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { itemId, startDate, endDate, startTime, endTime, deliveryMethod, deliveryAddress, notes } = req.body;
|
||||
const {
|
||||
itemId,
|
||||
startDate,
|
||||
endDate,
|
||||
startTime,
|
||||
endTime,
|
||||
deliveryMethod,
|
||||
deliveryAddress,
|
||||
notes,
|
||||
} = req.body;
|
||||
|
||||
const item = await Item.findByPk(itemId);
|
||||
if (!item) {
|
||||
return res.status(404).json({ error: 'Item not found' });
|
||||
return res.status(404).json({ error: "Item not found" });
|
||||
}
|
||||
|
||||
if (!item.availability) {
|
||||
return res.status(400).json({ error: 'Item is not available' });
|
||||
return res.status(400).json({ error: "Item is not available" });
|
||||
}
|
||||
|
||||
const overlappingRental = await Rental.findOne({
|
||||
where: {
|
||||
itemId,
|
||||
status: { [Op.in]: ['confirmed', 'active'] },
|
||||
status: { [Op.in]: ["confirmed", "active"] },
|
||||
[Op.or]: [
|
||||
{
|
||||
startDate: { [Op.between]: [startDate, endDate] }
|
||||
startDate: { [Op.between]: [startDate, endDate] },
|
||||
},
|
||||
{
|
||||
endDate: { [Op.between]: [startDate, endDate] }
|
||||
}
|
||||
]
|
||||
}
|
||||
endDate: { [Op.between]: [startDate, endDate] },
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
if (overlappingRental) {
|
||||
return res.status(400).json({ error: 'Item is already booked for these dates' });
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Item is already booked for these dates" });
|
||||
}
|
||||
|
||||
const rentalDays = Math.ceil((new Date(endDate) - new Date(startDate)) / (1000 * 60 * 60 * 24));
|
||||
const rentalDays = Math.ceil(
|
||||
(new Date(endDate) - new Date(startDate)) / (1000 * 60 * 60 * 24)
|
||||
);
|
||||
const totalAmount = rentalDays * (item.pricePerDay || 0);
|
||||
|
||||
const rental = await Rental.create({
|
||||
@@ -132,15 +152,23 @@ router.post('/', authenticateToken, async (req, res) => {
|
||||
totalAmount,
|
||||
deliveryMethod,
|
||||
deliveryAddress,
|
||||
notes
|
||||
notes,
|
||||
});
|
||||
|
||||
const rentalWithDetails = await Rental.findByPk(rental.id, {
|
||||
include: [
|
||||
{ model: Item, as: 'item' },
|
||||
{ model: User, as: 'owner', attributes: ['id', 'username', 'firstName', 'lastName'] },
|
||||
{ model: User, as: 'renter', attributes: ['id', 'username', 'firstName', 'lastName'] }
|
||||
]
|
||||
{ model: Item, as: "item" },
|
||||
{
|
||||
model: User,
|
||||
as: "owner",
|
||||
attributes: ["id", "username", "firstName", "lastName"],
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: "renter",
|
||||
attributes: ["id", "username", "firstName", "lastName"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
res.status(201).json(rentalWithDetails);
|
||||
@@ -149,27 +177,35 @@ router.post('/', authenticateToken, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:id/status', authenticateToken, async (req, res) => {
|
||||
router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { status } = req.body;
|
||||
const rental = await Rental.findByPk(req.params.id);
|
||||
|
||||
if (!rental) {
|
||||
return res.status(404).json({ error: 'Rental not found' });
|
||||
return res.status(404).json({ error: "Rental not found" });
|
||||
}
|
||||
|
||||
if (rental.ownerId !== req.user.id && rental.renterId !== req.user.id) {
|
||||
return res.status(403).json({ error: 'Unauthorized' });
|
||||
return res.status(403).json({ error: "Unauthorized" });
|
||||
}
|
||||
|
||||
await rental.update({ status });
|
||||
|
||||
const updatedRental = await Rental.findByPk(rental.id, {
|
||||
include: [
|
||||
{ model: Item, as: 'item' },
|
||||
{ model: User, as: 'owner', attributes: ['id', 'username', 'firstName', 'lastName'] },
|
||||
{ model: User, as: 'renter', attributes: ['id', 'username', 'firstName', 'lastName'] }
|
||||
]
|
||||
{ model: Item, as: "item" },
|
||||
{
|
||||
model: User,
|
||||
as: "owner",
|
||||
attributes: ["id", "username", "firstName", "lastName"],
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: "renter",
|
||||
attributes: ["id", "username", "firstName", "lastName"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
res.json(updatedRental);
|
||||
@@ -179,33 +215,35 @@ router.put('/:id/status', authenticateToken, async (req, res) => {
|
||||
});
|
||||
|
||||
// Owner reviews renter
|
||||
router.post('/:id/review-renter', authenticateToken, async (req, res) => {
|
||||
router.post("/:id/review-renter", authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { rating, review, privateMessage } = req.body;
|
||||
const rental = await Rental.findByPk(req.params.id);
|
||||
|
||||
if (!rental) {
|
||||
return res.status(404).json({ error: 'Rental not found' });
|
||||
return res.status(404).json({ error: "Rental not found" });
|
||||
}
|
||||
|
||||
if (rental.ownerId !== req.user.id) {
|
||||
return res.status(403).json({ error: 'Only owners can review renters' });
|
||||
return res.status(403).json({ error: "Only owners can review renters" });
|
||||
}
|
||||
|
||||
if (rental.status !== 'completed') {
|
||||
return res.status(400).json({ error: 'Can only review completed rentals' });
|
||||
if (rental.status !== "completed") {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Can only review completed rentals" });
|
||||
}
|
||||
|
||||
if (rental.renterReviewSubmittedAt) {
|
||||
return res.status(400).json({ error: 'Renter review already submitted' });
|
||||
return res.status(400).json({ error: "Renter review already submitted" });
|
||||
}
|
||||
|
||||
// Submit the review and private message
|
||||
await rental.update({
|
||||
renterRating: rating,
|
||||
await rental.update({
|
||||
renterRating: rating,
|
||||
renterReview: review,
|
||||
renterReviewSubmittedAt: new Date(),
|
||||
renterPrivateMessage: privateMessage
|
||||
renterPrivateMessage: privateMessage,
|
||||
});
|
||||
|
||||
// Check and update visibility
|
||||
@@ -213,7 +251,7 @@ router.post('/:id/review-renter', authenticateToken, async (req, res) => {
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
reviewVisible: updatedRental.renterReviewVisible
|
||||
reviewVisible: updatedRental.renterReviewVisible,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
@@ -221,33 +259,35 @@ router.post('/:id/review-renter', authenticateToken, async (req, res) => {
|
||||
});
|
||||
|
||||
// Renter reviews item
|
||||
router.post('/:id/review-item', authenticateToken, async (req, res) => {
|
||||
router.post("/:id/review-item", authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { rating, review, privateMessage } = req.body;
|
||||
const rental = await Rental.findByPk(req.params.id);
|
||||
|
||||
if (!rental) {
|
||||
return res.status(404).json({ error: 'Rental not found' });
|
||||
return res.status(404).json({ error: "Rental not found" });
|
||||
}
|
||||
|
||||
if (rental.renterId !== req.user.id) {
|
||||
return res.status(403).json({ error: 'Only renters can review items' });
|
||||
return res.status(403).json({ error: "Only renters can review items" });
|
||||
}
|
||||
|
||||
if (rental.status !== 'completed') {
|
||||
return res.status(400).json({ error: 'Can only review completed rentals' });
|
||||
if (rental.status !== "completed") {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Can only review completed rentals" });
|
||||
}
|
||||
|
||||
if (rental.itemReviewSubmittedAt) {
|
||||
return res.status(400).json({ error: 'Item review already submitted' });
|
||||
return res.status(400).json({ error: "Item review already submitted" });
|
||||
}
|
||||
|
||||
// Submit the review and private message
|
||||
await rental.update({
|
||||
itemRating: rating,
|
||||
await rental.update({
|
||||
itemRating: rating,
|
||||
itemReview: review,
|
||||
itemReviewSubmittedAt: new Date(),
|
||||
itemPrivateMessage: privateMessage
|
||||
itemPrivateMessage: privateMessage,
|
||||
});
|
||||
|
||||
// Check and update visibility
|
||||
@@ -255,7 +295,7 @@ router.post('/:id/review-item', authenticateToken, async (req, res) => {
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
reviewVisible: updatedRental.itemReviewVisible
|
||||
reviewVisible: updatedRental.itemReviewVisible,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
@@ -263,31 +303,43 @@ router.post('/:id/review-item', authenticateToken, async (req, res) => {
|
||||
});
|
||||
|
||||
// Mark rental as completed (owner only)
|
||||
router.post('/:id/mark-completed', authenticateToken, async (req, res) => {
|
||||
router.post("/:id/mark-completed", authenticateToken, async (req, res) => {
|
||||
try {
|
||||
console.log('Mark completed endpoint hit for rental ID:', req.params.id);
|
||||
console.log("Mark completed endpoint hit for rental ID:", req.params.id);
|
||||
const rental = await Rental.findByPk(req.params.id);
|
||||
|
||||
if (!rental) {
|
||||
return res.status(404).json({ error: 'Rental not found' });
|
||||
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' });
|
||||
return res
|
||||
.status(403)
|
||||
.json({ error: "Only owners can mark rentals as completed" });
|
||||
}
|
||||
|
||||
if (!['active', 'confirmed'].includes(rental.status)) {
|
||||
return res.status(400).json({ error: 'Can only mark active or confirmed rentals as completed' });
|
||||
if (!["active", "confirmed"].includes(rental.status)) {
|
||||
return res.status(400).json({
|
||||
error: "Can only mark active or confirmed rentals as completed",
|
||||
});
|
||||
}
|
||||
|
||||
await rental.update({ status: 'completed' });
|
||||
await rental.update({ status: "completed" });
|
||||
|
||||
const updatedRental = await Rental.findByPk(rental.id, {
|
||||
include: [
|
||||
{ model: Item, as: 'item' },
|
||||
{ model: User, as: 'owner', attributes: ['id', 'username', 'firstName', 'lastName'] },
|
||||
{ model: User, as: 'renter', attributes: ['id', 'username', 'firstName', 'lastName'] }
|
||||
]
|
||||
{ model: Item, as: "item" },
|
||||
{
|
||||
model: User,
|
||||
as: "owner",
|
||||
attributes: ["id", "username", "firstName", "lastName"],
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: "renter",
|
||||
attributes: ["id", "username", "firstName", "lastName"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
res.json(updatedRental);
|
||||
@@ -297,21 +349,23 @@ router.post('/:id/mark-completed', authenticateToken, async (req, res) => {
|
||||
});
|
||||
|
||||
// Legacy review endpoint (for backward compatibility)
|
||||
router.post('/:id/review', authenticateToken, async (req, res) => {
|
||||
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' });
|
||||
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' });
|
||||
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' });
|
||||
if (rental.status !== "completed") {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Can only review completed rentals" });
|
||||
}
|
||||
|
||||
await rental.update({ rating, review });
|
||||
@@ -322,4 +376,4 @@ router.post('/:id/review', authenticateToken, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
||||
206
backend/routes/stripe.js
Normal file
206
backend/routes/stripe.js
Normal file
@@ -0,0 +1,206 @@
|
||||
const express = require("express");
|
||||
const { authenticateToken } = require("../middleware/auth");
|
||||
const { User } = require("../models");
|
||||
const { Rental, Item } = require("../models");
|
||||
const StripeService = require("../services/stripeService");
|
||||
const router = express.Router();
|
||||
const platformFee = 0.1;
|
||||
|
||||
router.post("/create-checkout-session", async (req, res) => {
|
||||
try {
|
||||
const { itemName, total, return_url } = req.body;
|
||||
|
||||
if (!itemName) {
|
||||
return res.status(400).json({ error: "No item name found" });
|
||||
}
|
||||
if (total == null || total === undefined) {
|
||||
return res.status(400).json({ error: "No total found" });
|
||||
}
|
||||
if (!return_url) {
|
||||
return res.status(400).json({ error: "No return_url found" });
|
||||
}
|
||||
|
||||
const session = await StripeService.createCheckoutSession({
|
||||
item_name: itemName,
|
||||
total: total,
|
||||
return_url: return_url,
|
||||
});
|
||||
|
||||
res.json({ clientSecret: session.client_secret });
|
||||
} catch (error) {
|
||||
console.error("Error creating checkout session:", error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Get checkout session status
|
||||
router.get("/checkout-session/:sessionId", async (req, res) => {
|
||||
try {
|
||||
const { sessionId } = req.params;
|
||||
|
||||
const session = await StripeService.getCheckoutSession(sessionId);
|
||||
|
||||
res.json({
|
||||
status: session.status,
|
||||
payment_status: session.payment_status,
|
||||
customer_email: session.customer_details?.email,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error retrieving checkout session:", error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// // Create connected account
|
||||
// router.post("/accounts", authenticateToken, async (req, res) => {
|
||||
// try {
|
||||
// const user = await User.findByPk(req.user.id);
|
||||
|
||||
// if (!user) {
|
||||
// return res.status(404).json({ error: "User not found" });
|
||||
// }
|
||||
|
||||
// // Check if user already has a connected account
|
||||
// if (user.stripeConnectedAccountId) {
|
||||
// return res
|
||||
// .status(400)
|
||||
// .json({ error: "User already has a connected account" });
|
||||
// }
|
||||
|
||||
// // Create connected account
|
||||
// const account = await StripeService.createConnectedAccount({
|
||||
// email: user.email,
|
||||
// country: "US", // You may want to make this configurable
|
||||
// });
|
||||
|
||||
// // Update user with account ID
|
||||
// await user.update({
|
||||
// stripeConnectedAccountId: account.id,
|
||||
// });
|
||||
|
||||
// res.json({
|
||||
// stripeConnectedAccountId: account.id,
|
||||
// success: true,
|
||||
// });
|
||||
// } catch (error) {
|
||||
// console.error("Error creating connected account:", error);
|
||||
// res.status(500).json({ error: error.message });
|
||||
// }
|
||||
// });
|
||||
|
||||
// // Generate onboarding link
|
||||
// router.post("/account-links", authenticateToken, async (req, res) => {
|
||||
// try {
|
||||
// const user = await User.findByPk(req.user.id);
|
||||
|
||||
// if (!user || !user.stripeConnectedAccountId) {
|
||||
// return res.status(400).json({ error: "No connected account found" });
|
||||
// }
|
||||
|
||||
// const { refreshUrl, returnUrl } = req.body;
|
||||
|
||||
// if (!refreshUrl || !returnUrl) {
|
||||
// return res
|
||||
// .status(400)
|
||||
// .json({ error: "refreshUrl and returnUrl are required" });
|
||||
// }
|
||||
|
||||
// const accountLink = await StripeService.createAccountLink(
|
||||
// user.stripeConnectedAccountId,
|
||||
// refreshUrl,
|
||||
// returnUrl
|
||||
// );
|
||||
|
||||
// res.json({
|
||||
// url: accountLink.url,
|
||||
// expiresAt: accountLink.expires_at,
|
||||
// });
|
||||
// } catch (error) {
|
||||
// console.error("Error creating account link:", error);
|
||||
// res.status(500).json({ error: error.message });
|
||||
// }
|
||||
// });
|
||||
|
||||
// // Get account status
|
||||
// router.get("/account-status", authenticateToken, async (req, res) => {
|
||||
// try {
|
||||
// const user = await User.findByPk(req.user.id);
|
||||
|
||||
// if (!user || !user.stripeConnectedAccountId) {
|
||||
// return res.status(400).json({ error: "No connected account found" });
|
||||
// }
|
||||
|
||||
// const accountStatus = await StripeService.getAccountStatus(
|
||||
// user.stripeConnectedAccountId
|
||||
// );
|
||||
|
||||
// res.json({
|
||||
// accountId: accountStatus.id,
|
||||
// detailsSubmitted: accountStatus.details_submitted,
|
||||
// payoutsEnabled: accountStatus.payouts_enabled,
|
||||
// capabilities: accountStatus.capabilities,
|
||||
// requirements: accountStatus.requirements,
|
||||
// });
|
||||
// } catch (error) {
|
||||
// console.error("Error getting account status:", error);
|
||||
// res.status(500).json({ error: error.message });
|
||||
// }
|
||||
// });
|
||||
|
||||
// // Create payment intent for rental
|
||||
// router.post("/payment-intents", authenticateToken, async (req, res) => {
|
||||
// try {
|
||||
// const { rentalId, amount } = req.body;
|
||||
|
||||
// if (!rentalId || !amount) {
|
||||
// return res
|
||||
// .status(400)
|
||||
// .json({ error: "rentalId and amount are required" });
|
||||
// }
|
||||
|
||||
// // Get rental details to find owner's connected account
|
||||
// const rental = await Rental.findByPk(rentalId, {
|
||||
// include: [{ model: Item, as: "item" }],
|
||||
// });
|
||||
|
||||
// if (!rental) {
|
||||
// return res.status(404).json({ error: "Rental not found" });
|
||||
// }
|
||||
|
||||
// if (rental.ownerId !== req.user.id) {
|
||||
// return res.status(403).json({ error: "Unauthorized" });
|
||||
// }
|
||||
|
||||
// // Get owner's connected account
|
||||
// const owner = await User.findByPk(rental.ownerId);
|
||||
// if (!owner || !owner.stripeConnectedAccountId) {
|
||||
// return res
|
||||
// .status(400)
|
||||
// .json({ error: "Owner does not have a connected account" });
|
||||
// }
|
||||
|
||||
// const applicationFeeAmount = Math.round(amount * platformFee);
|
||||
|
||||
// const paymentIntent = await StripeService.createPaymentIntent({
|
||||
// amount: Math.round(amount * 100), // Convert to cents
|
||||
// currency: "usd",
|
||||
// connectedAccountId: owner.stripeConnectedAccountId,
|
||||
// applicationFeeAmount: applicationFeeAmount * 100, // Convert to cents
|
||||
// metadata: {
|
||||
// rentalId: rental.id,
|
||||
// renterId: rental.renterId,
|
||||
// ownerId: owner.id,
|
||||
// },
|
||||
// });
|
||||
|
||||
// res.json({
|
||||
// clientSecret: paymentIntent.client_secret,
|
||||
// paymentIntentId: paymentIntent.id,
|
||||
// });
|
||||
// } catch (error) {
|
||||
// console.error("Error creating payment intent:", error);
|
||||
// res.status(500).json({ error: error.message });
|
||||
// }
|
||||
// });
|
||||
|
||||
module.exports = router;
|
||||
@@ -19,6 +19,7 @@ const rentalRoutes = require("./routes/rentals");
|
||||
const messageRoutes = require("./routes/messages");
|
||||
const betaRoutes = require("./routes/beta");
|
||||
const itemRequestRoutes = require("./routes/itemRequests");
|
||||
const stripeRoutes = require("./routes/stripe");
|
||||
|
||||
const app = express();
|
||||
|
||||
@@ -39,6 +40,7 @@ app.use("/api/items", itemRoutes);
|
||||
app.use("/api/rentals", rentalRoutes);
|
||||
app.use("/api/messages", messageRoutes);
|
||||
app.use("/api/item-requests", itemRequestRoutes);
|
||||
app.use("/api/stripe", stripeRoutes);
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
res.json({ message: "CommunityRentals.App API is running!" });
|
||||
|
||||
149
backend/services/stripeService.js
Normal file
149
backend/services/stripeService.js
Normal file
@@ -0,0 +1,149 @@
|
||||
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
|
||||
|
||||
class StripeService {
|
||||
static async createCheckoutSession({ item_name, total, return_url }) {
|
||||
try {
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
line_items: [
|
||||
{
|
||||
price_data: {
|
||||
currency: "usd",
|
||||
product_data: {
|
||||
name: item_name,
|
||||
},
|
||||
unit_amount: total * 100,
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
mode: "payment",
|
||||
ui_mode: "embedded",
|
||||
return_url: return_url, //"https://example.com/checkout/return?session_id={CHECKOUT_SESSION_ID}"
|
||||
});
|
||||
|
||||
return session;
|
||||
} catch (error) {
|
||||
console.error("Error creating connected account:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async getCheckoutSession(sessionId) {
|
||||
try {
|
||||
return await stripe.checkout.sessions.retrieve(sessionId);
|
||||
} catch (error) {
|
||||
console.error("Error retrieving checkout session:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// static async createConnectedAccount({ email, country = "US" }) {
|
||||
// try {
|
||||
// const account = await stripe.accounts.create({
|
||||
// type: "standard",
|
||||
// email,
|
||||
// country,
|
||||
// controller: {
|
||||
// stripe_dashboard: {
|
||||
// type: "full",
|
||||
// },
|
||||
// },
|
||||
// capabilities: {
|
||||
// transfers: { requested: true },
|
||||
// },
|
||||
// });
|
||||
|
||||
// return account;
|
||||
// } catch (error) {
|
||||
// console.error("Error creating connected account:", error);
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
|
||||
// static async createAccountLink(accountId, refreshUrl, returnUrl) {
|
||||
// try {
|
||||
// const accountLink = await stripe.accountLinks.create({
|
||||
// account: accountId,
|
||||
// refresh_url: refreshUrl,
|
||||
// return_url: returnUrl,
|
||||
// type: "account_onboarding",
|
||||
// });
|
||||
|
||||
// return accountLink;
|
||||
// } catch (error) {
|
||||
// console.error("Error creating account link:", error);
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
|
||||
// static async getAccountStatus(accountId) {
|
||||
// try {
|
||||
// const account = await stripe.accounts.retrieve(accountId);
|
||||
// return {
|
||||
// id: account.id,
|
||||
// details_submitted: account.details_submitted,
|
||||
// payouts_enabled: account.payouts_enabled,
|
||||
// capabilities: account.capabilities,
|
||||
// requirements: account.requirements,
|
||||
// };
|
||||
// } catch (error) {
|
||||
// console.error("Error retrieving account status:", error);
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
|
||||
// static async createPaymentIntent({
|
||||
// amount,
|
||||
// currency = "usd",
|
||||
// connectedAccountId,
|
||||
// applicationFeeAmount,
|
||||
// metadata = {},
|
||||
// }) {
|
||||
// try {
|
||||
// const paymentIntent = await stripe.paymentIntents.create({
|
||||
// amount,
|
||||
// currency,
|
||||
// transfer_data: {
|
||||
// destination: connectedAccountId,
|
||||
// },
|
||||
// application_fee_amount: applicationFeeAmount,
|
||||
// metadata,
|
||||
// automatic_payment_methods: {
|
||||
// enabled: true,
|
||||
// },
|
||||
// });
|
||||
|
||||
// return paymentIntent;
|
||||
// } catch (error) {
|
||||
// console.error("Error creating payment intent:", error);
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
|
||||
// static async confirmPaymentIntent(paymentIntentId, paymentMethodId) {
|
||||
// try {
|
||||
// const paymentIntent = await stripe.paymentIntents.confirm(
|
||||
// paymentIntentId,
|
||||
// {
|
||||
// payment_method: paymentMethodId,
|
||||
// }
|
||||
// );
|
||||
|
||||
// return paymentIntent;
|
||||
// } catch (error) {
|
||||
// console.error("Error confirming payment intent:", error);
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
|
||||
// static async retrievePaymentIntent(paymentIntentId) {
|
||||
// try {
|
||||
// return await stripe.paymentIntents.retrieve(paymentIntentId);
|
||||
// } catch (error) {
|
||||
// console.error("Error retrieving payment intent:", error);
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
module.exports = StripeService;
|
||||
Reference in New Issue
Block a user