Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions client/src/pages/pay/Pay.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ const Pay = () => {
useEffect(() => {
const makeRequest = async () => {
try {
const res = await request.post(`/gigs/${id}/order/stripe`);
setClientSecret(res.data.data);
// CORRECTED: The endpoint now matches the backend route
const res = await request.post(`/gigs/${id}/order/create-payment-intent`);
// CORRECTED: Accessing clientSecret directly from the response data
setClientSecret(res.data.clientSecret);
} catch (err) {
const errorMessage = err.response?.data?.message || "Could not initialize payment.";
setError(errorMessage);
Expand Down
4 changes: 2 additions & 2 deletions client/src/utils/request.utils.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import axios from "axios";

const request = axios.create({
baseURL: "http://localhost:8080/api",
withCredentials: true,
baseURL: "http://localhost:8080/api",
withCredentials: true,
});

export default request;
78 changes: 47 additions & 31 deletions server/controllers/order.controller.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,55 @@
const Order = require("../models/order.model");
const Gig = require("../models/gig.model");
const stripe = require("stripe")(process.env.STRIPE_KEY);
const createError = require("../utils/createError");

const intent = async (req, res, next) => {
const gig = await Gig.findById(req.params.id); // req.params.id comes from gig.route.js (/:id/order)

const paymentIntent = await stripe.paymentIntents.create({
amount: gig.price * 100, // Amount in cents
currency: "usd",
automatic_payment_methods: {
enabled: true,
},
});

const newOrder = new Order({
gigId: gig._id,
img: gig.cover,
title: gig.title,
price: gig.price,
sellerId: gig.userId,
buyerId: req.userId, // From JWT middleware
payment_intent: paymentIntent.id,
});

await newOrder.save();
res.status(200).send({
clientSecret: paymentIntent.client_secret,
});
try {
// 1. Find the gig from the database
const gig = await Gig.findById(req.params.id);
if (!gig) {
return next(createError(404, "Gig not found."));
}

// 2. Create a payment intent with Stripe
const paymentIntent = await stripe.paymentIntents.create({
amount: gig.price * 100,
currency: "usd",
automatic_payment_methods: {
enabled: true,
},
});

// 3. Create a new order with the correct field names
const newOrder = new Order({
gig: gig._id, // CORRECTED: Was 'gigId'
img: gig.cover,
title: gig.title,
price: gig.price,
seller: gig.userId, // CORRECTED: Was 'sellerId'
buyer: req.userId, // CORRECTED: Was 'buyerId'
payment_intent: paymentIntent.id,
});

// 4. Save the order to the database
await newOrder.save();

// 5. Send the client secret back to the client
res.status(200).send({
clientSecret: paymentIntent.client_secret,
});
} catch (err) {
// Pass any errors to the error handling middleware
next(err);
}
};

const getOrders = async (req, res, next) => {
try {
const orders = await Order.find({
...(req.isSeller ? { sellerId: req.userId } : { buyerId: req.userId }),
isCompleted: true, // Only fetch completed orders for display
// CORRECTED: Use 'seller' and 'buyer' to match the schema
...(req.isSeller ? { seller: req.userId } : { buyer: req.userId }),
isCompleted: true,
});
res.status(200).send(orders);
} catch (err) {
Expand All @@ -43,21 +59,21 @@ const getOrders = async (req, res, next) => {

const confirmOrder = async (req, res, next) => {
try {
// Find the order using the paymentIntentId from the URL
const order = await Order.findOneAndUpdate(
{ payment_intent: req.params.paymentIntentId },
{ $set: { isCompleted: true } },
{ new: true } // Return the updated document
{ new: true }
);

if (!order) {
return res.status(404).send("Order not found or payment intent ID invalid.");
return next(createError(404, "Order not found."));
}

res.status(200).send("Order has been confirmed!");
} catch (err) {
} catch (err)
{
next(err);
}
};

module.exports = { intent, getOrders, confirmOrder };
module.exports = { intent, getOrders, confirmOrder };
37 changes: 26 additions & 11 deletions server/middleware/jwt.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,36 @@ const jwt = require("jsonwebtoken");
const createError = require("../utils/createError");

const isAuth = (req, res, next) => {
// FIX: Changed from req.cookies.token to req.cookies.accessToken
const token = req.cookies.accessToken;

let token;

// 1. Check for Bearer token in Authorization header
if (
req.headers.authorization &&
req.headers.authorization.startsWith("Bearer")
) {
token = req.headers.authorization.split(" ")[1];
}
// 2. If no header, fall back to checking for the cookie
else if (req.cookies.accessToken) {
token = req.cookies.accessToken;
}

// 3. If no token is found in either location, deny access
if (!token) {
return next(createError(401, "You are not authorized to access this route. No token found."));
return next(createError(401, "Authentication required. No token provided."));
}

jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if (err) {
// Provide a more specific error for debugging if the token is invalid
return next(createError(403, "Token is not valid."));
}
req.user = decoded;
// 4. Verify the token
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.userId = payload.id;
req.isSeller = payload.isSeller;
next();
});
} catch (err) {
return next(createError(403, "Token is not valid."));
}
};

// CORRECTED: Export the function directly
module.exports = isAuth;

4 changes: 3 additions & 1 deletion server/routes/gig.route.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ const isAuth = require("../middleware/jwt");
router.route("/").post(isAuth, addGig).get(getAllGigs);
router.route("/:id").get(getGig).delete(isAuth, deleteGig);

// FIX: The order router is now correctly nested under the gig ID
// FIX: This line correctly tells the server that order-related routes
// should be found under a specific gig ID (e.g., /api/gigs/some-id/order/...)
router.use("/:id/order", orderRouter);

router.use("/:id/reviews", reviewRouter);


Expand Down
11 changes: 9 additions & 2 deletions server/routes/order.route.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ const router = require("express").Router();
const {
intent,
getOrders,
confirmOrder, // You'll need this function
intent, // Make sure you're exporting 'intent' from your controller
confirmOrder,
} = require("../controllers/order.controller");
const isAuth = require("../middleware/jwt");

router.post("/create-payment-intent", isAuth, intent);
router.get("/", isAuth, getOrders);
router.patch("/:paymentIntentId", isAuth, confirmOrder); // New route to confirm order

// FIX: The route is now "/create-payment-intent" to match the frontend call
router.post("/create-payment-intent", isAuth, intent);

// This route seems to be for after the payment is successful
router.patch("/:payment_intent", confirmOrder)


module.exports = router;
3 changes: 2 additions & 1 deletion server/server.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require("dotenv").config();
const dotenv = require("dotenv");
dotenv.config();
const express = require("express");
const cookieParser = require("cookie-parser");
const cors = require("cors");
Expand Down