diff --git a/client/src/pages/pay/Pay.jsx b/client/src/pages/pay/Pay.jsx index 37cd0cb..15a43fc 100644 --- a/client/src/pages/pay/Pay.jsx +++ b/client/src/pages/pay/Pay.jsx @@ -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); diff --git a/client/src/utils/request.utils.js b/client/src/utils/request.utils.js index ad57396..66fa14b 100644 --- a/client/src/utils/request.utils.js +++ b/client/src/utils/request.utils.js @@ -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; \ No newline at end of file diff --git a/server/controllers/order.controller.js b/server/controllers/order.controller.js index d4d39e3..2d2c031 100644 --- a/server/controllers/order.controller.js +++ b/server/controllers/order.controller.js @@ -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) { @@ -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 }; \ No newline at end of file +module.exports = { intent, getOrders, confirmOrder }; diff --git a/server/middleware/jwt.js b/server/middleware/jwt.js index d0ac546..5eb042d 100644 --- a/server/middleware/jwt.js +++ b/server/middleware/jwt.js @@ -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; + diff --git a/server/routes/gig.route.js b/server/routes/gig.route.js index 51adbcd..9b2d0f3 100644 --- a/server/routes/gig.route.js +++ b/server/routes/gig.route.js @@ -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); diff --git a/server/routes/order.route.js b/server/routes/order.route.js index 4c8ad93..2d058de 100644 --- a/server/routes/order.route.js +++ b/server/routes/order.route.js @@ -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; \ No newline at end of file diff --git a/server/server.js b/server/server.js index 9c17aa4..e1e04b4 100644 --- a/server/server.js +++ b/server/server.js @@ -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");