I have a working ecommerce website where integration of other payment platforms works fine. However, trying to integrate paystack has been throwing some errors that even with much troubleshooting, I cannot seem to find where the problem is coming from.
I need paystack to be as functional as the rest because this ecommerce site is mainly set up to target clients from Nigeria and we need to be able to accept payments using their local card. The onSuccess component of the payment works pretty fine with the paystack-public-key set up but it doesn’t update the order database indicating that there was a successful payment and also it returns a 404 error with the headers
Request URL: http://localhost:3000/api/order/6626ea44b7683aa864d92837/pay
Request Method: PUT
Status Code: 404 Not Found
I had this same payment method in a single page reactjs ecommerce which worked pretty fine but I don’t know why it isn’t doing the same in this nextjs project. Below are the related code pages set up for this. Please go through and help thanks.
/pages/order/[id].js
import styles from "@/styles/order.module.scss";
import Header from "@/components/header";
import Order from "@/models/Order";
import User from "@/models/user";
import { IoIosArrowForward } from "react-icons/io";
import db from "@/utils/db";
import { PayPalButtons, usePayPalScriptReducer } from "@paypal/react-paypal-js";
import { useReducer, useEffect } from "react";
import axios from "axios";
import { PaystackButton } from "react-paystack";
import { toast } from "react-toastify";
function reducer(state, action) {
switch (action.type) {
case "PAY_REQUEST":
return { ...state, loading: true };
case "PAY_SUCCESS":
return { ...state, loading: false, success: true };
case "PAY_FAIL":
return { ...state, loading: false, error: action.payload };
case "PAY_RESET":
return { ...state, loading: false, success: false, error: false };
}
}
export default function order({
orderData,
paypal_client_id,
country,
}) {
const [{ isPending }, paypalDispatch] = usePayPalScriptReducer();
const [dispatch] = useReducer(reducer, {
loading: true,
error: "",
success: "",
});
console.log({ orderData });
useEffect(() => {
if (!orderData._id) {
dispatch({
type: "PAY_RESET",
});
} else {
paypalDispatch({
type: "resetOptions",
value: {
"client-id": paypal_client_id,
currency: "USD",
},
});
paypalDispatch({
type: "setLoadingStatus",
value: "pending",
});
}
}, [order]);
function createOrderHanlder(data, actions) {
return actions.order
.create({
purchase_units: [
{
amount: {
value: orderData.total / 550,
},
},
],
})
.then((order_id) => {
return order_id;
});
}
function onApproveHandler(data, actions) {
return actions.order.capture().then(async function (details) {
try {
dispatch({ type: "PAY_REQUEST" });
const { data } = await axios.put(
`/api/order/${orderData._id}/pay`,
details
);
dispatch({ type: "PAY_SUCCESS", payload: data });
} catch (error) {
dispatch({ type: "PAY_ERROR", payload: error });
}
});
}
function onErroHandler(error) {
console.log(error);
}
const componentProps = {
reference: new Date().getTime().toString(),
email: orderData.user.email,
amount: orderData.total * 100, //Amount is in the country's lowest currency. E.g Kobo, so 20000 kobo = N200
currency: "NGN",
publicKey: process.env.PAYSTACK_PUBLIC_KEY,
text: "Proceed To Pay",
onSuccess: () => {
try {
const { data } = axios.put(`/api/order/${orderData._id}/pay`, {
orderData,
});
console.log(data, "paid successfully");
window.location.reload(false);
toast.success("Order is paid");
} catch (err) {
toast.error(getError(err));
}
},
onClose: () =>
toast.error("We are sorry you have to leave, come back soon!"),
};
return (
<>
<Header country={country} />
<div className={styles.order}>
<div className={styles.container}>
<div className={styles.order__infos}>
<div className={styles.order__header}>
<div className={styles.order__header_head}>
Home <IoIosArrowForward /> Orders <IoIosArrowForward /> ID{" "}
{orderData._id}
</div>
<div className={styles.order__header_status}>
Payment Status :{" "}
{orderData.isPaid ? (
<img src="/images/verified.png" alt="paid" />
) : (
<img src="/images/unverified.png" alt="not-paid" />
)}
</div>
<div className={styles.order__header_status}>
Order Status :
<span
className={
orderData.status == "Not Processed"
? styles.not_processed
: orderData.status == "Processing"
? styles.processing
: orderData.status == "Dispatched"
? styles.dispatched
: orderData.status == "Cancelled"
? styles.cancelled
: orderData.status == "Completed"
? styles.completed
: ""
}
>
{orderData.status}
</span>
</div>
</div>
<div className={styles.order__products}>
{orderData.products.map((product) => (
<div className={styles.product} key={product._id}>
<div className={styles.product__img}>
<img src={product.image} alt={product.name} />
</div>
<div className={styles.product__infos}>
<h1 className={styles.product__infos_name}>
{product.name.length > 30
? `${product.name.substring(0, 30)}...`
: product.name}
</h1>
<div className={styles.product__infos_style}>
<img src={product.color.image} alt="" /> / {product.size}
</div>
<div className={styles.product__infos_priceQty}>
{product.price}₦ x {product.qty}
</div>
<div className={styles.product__infos_total}>
{product.price * product.qty}₦
</div>
</div>
</div>
))}
<div className={styles.order__products_total}>
{orderData.couponApplied ? (
<>
<div className={styles.order__products_total_sub}>
<span>Subtotal</span>
<span>{orderData.totalBeforeDiscount}₦</span>
</div>
<div className={styles.order__products_total_sub}>
<span>
Coupon Applied <em>({orderData.couponApplied})</em>{" "}
</span>
<span>
-
{(
orderData.totalBeforeDiscount - orderData.total
).toFixed(2)}
₦
</span>
</div>
<div className={styles.order__products_total_sub}>
<span>Tax price</span>
<span>+{orderData.taxPrice}₦</span>
</div>
<div
className={`${styles.order__products_total_sub} ${styles.bordertop}`}
>
<span>TOTAL TO PAY</span>
<b>{orderData.total}₦</b>
</div>
</>
) : (
<>
<div className={styles.order__products_total_sub}>
<span>Tax price</span>
<span>+ {orderData.taxPrice}₦</span>
</div>
<div
className={`${styles.order__products_total_sub} ${styles.bordertop}`}
>
<span>TOTAL TO PAY</span>
<b>{orderData.total}₦</b>
</div>
</>
)}
</div>
</div>
</div>
<div className={styles.order__actions}>
<div className={styles.order__address}>
<h1>Customer's Info</h1>
<div className={styles.order__address_user}>
<div className={styles.order__address_user_infos}>
<img src={orderData.user.image} alt="" />
<div>
<span>{orderData.user.name}</span>
<span>{orderData.user.email}</span>
</div>
</div>
</div>
<div className={styles.order__address_shipping}>
<h2>Shipping Address</h2>
<span>
{orderData.shippingAddress.firstName}{" "}
{orderData.shippingAddress.lastName}
</span>
<span>{orderData.shippingAddress.address1}</span>
<span>{orderData.shippingAddress.address2}</span>
<span>
{orderData.shippingAddress.state},
{orderData.shippingAddress.city}
</span>
<span>{orderData.shippingAddress.zipCode}</span>
<span>{orderData.shippingAddress.country}</span>
</div>
<div className={styles.order__address_shipping}>
<h2>Billing Address</h2>
<span>
{orderData.shippingAddress.firstName}{" "}
{orderData.shippingAddress.lastName}
</span>
<span>{orderData.shippingAddress.address1}</span>
<span>{orderData.shippingAddress.address2}</span>
<span>
{orderData.shippingAddress.state},
{orderData.shippingAddress.city}
</span>
<span>{orderData.shippingAddress.zipCode}</span>
<span>{orderData.shippingAddress.country}</span>
</div>
</div>
{!orderData.isPaid && (
<div className={styles.order__payment}>
{orderData.paymentMethod == "paypal" && (
<div>
{isPending ? (
<span>loading...</span>
) : (
<PayPalButtons
createOrder={createOrderHanlder}
onApprove={onApproveHandler}
onError={onErroHandler}
></PayPalButtons>
)}
</div>
)}
{orderData.paymentMethod == "credit_card" && (
<PaystackButton
{...componentProps}
className="bg-[#441617] w-full text-center text-white h-16 rounded"
/>
)}
{orderData.paymentMethod == "cash" && (
<div className={styles.cash}>cash</div>
)}
</div>
)}
</div>
</div>
</div>
</>
);
}
export async function getServerSideProps(context) {
await db.connectDb();
const { query } = context;
const id = query.id;
const order = await Order.findById(id)
.populate({ path: "user", model: User })
.lean();
let paypal_client_id = process.env.PAYPAL_CLIENT_ID;
let stripe_public_key = process.env.STRIPE_PUBLIC_KEY;
await db.disconnectDb();
return {
props: {
orderData: JSON.parse(JSON.stringify(order)),
paypal_client_id,
country: {
name: "Nigeria",
flag: "https://cdn.ipregistry.co/flags/emojitwo/ng.svg",
code: "NG",
},
},
};
}
/pages/api/order/[id].js
import nc from "next-connect";
import auth from "@/middleware/auth";
import Order from "@/models/Order";
import db from "@/utils/db";
import User from "@/models/user";
const handler = nc().use(auth);
handler.put(async (req, res) => {
try {
await db.connectDb();
const orderId = req.query.id; // Ensure this is the correct way to extract the ID
console.log("Order ID:", orderId);
const order = await Order.findById(orderId).populate({
path: "user",
model: User,
});
if (order) {
order.isPaid = true;
order.paidAt = Date.now();
order.paymentResult = {
id: "12345",
status: "processed",
email: "[email protected]",
};
const newOrder = await order.save();
await db.disconnectDb();
console.log("Order updated:", newOrder);
res.json({ message: "Order is paid.", order: newOrder });
} else {
await db.disconnectDb();
console.log("Order not found");
res.status(404).json({ message: "Order is not found." });
}
} catch (error) {
console.error("Error processing payment:", error);
await db.disconnectDb();
return res.status(500).json({ message: "Internal server error." });
}
});
export default handler;
/models/Order.js
import mongoose from "mongoose";
const { ObjectId } = mongoose.Schema;
const orderSchema = new mongoose.Schema(
{
user: {
type: ObjectId,
ref: "User",
required: true,
},
products: [
{
product: {
type: ObjectId,
ref: "Product",
},
name: {
type: String,
},
image: {
type: String,
},
size: {
type: String,
},
qty: {
type: Number,
},
color: {
color: String,
image: String,
},
price: {
type: Number,
},
},
],
shippingAddress: {
firstName: {
type: String,
},
lastName: {
type: String,
},
phoneNumber: {
type: String,
},
address1: {
type: String,
},
address2: {
type: String,
},
city: {
type: String,
},
state: {
type: String,
},
zipCode: {
type: String,
},
country: {
type: String,
},
},
paymentMethod: {
type: String,
},
paymentResult: {
id: String,
status: String,
email: String,
},
total: {
type: Number,
required: true,
},
totalBeforeDiscount: {
type: Number,
},
couponApplied: {
type: String,
},
shippingPrice: {
type: Number,
required: true,
default: 0,
},
taxPrice: {
type: Number,
default: 0,
},
isPaid: {
type: Boolean,
required: true,
default: false,
},
status: {
type: String,
default: "Not Processed",
enum: [
"Not Processed",
"Processing",
"Dispatched",
"Cancelled",
"Completed",
],
},
paidAt: {
type: Date,
},
deliveredAt: {
type: Date,
},
},
{
timestamps: true,
}
);
const Order = mongoose.models.Order || mongoose.model("Order", orderSchema);
export default Order;
Request URL:
http://localhost:3000/api/order/6626ea44b7683aa864d92837/pay Request
Method: PUT Status Code: 404 Not Found
-
A 404 means an endpoint structured like the request URL above does not exist.
-
Also, I see that you are doing this =>
const orderId = req.query.id;
but I cannot find a query in the request URL. A modified version of the request URL to include a query would be
http://localhost:3000/api/pay?orderId=6626ea44b7683aa864d92837
-
I do not use nextjs but from inspecting what you did; if the file name for your router is /pages/api/order/[id].js, you might have to modify the put request in that file to include ‘/pay’. It might be something like this
handler.put(‘/pay’, async (req, res) => {
1