I'm building my first ecommerce site and currently trying to integrate stripe payment however I'm coming across an issue I can't seem to get my head around. Payment intent is being added to my stripe payments however the order isn't being added to my mongodb database using prisma as intended, neither is it being added to the local storage. When routing to /checkout I am getting an Internal Server Error which shows as the following in my terminal:
PrismaClientValidationError:
Invalid `prisma.order.create()` invocation:
{
data: {
user: {
connect: {
id: "65ce3a1f18f1723283abd0d1"
}
},
amount: 6900,
currency: "gbp",
status: "pending",
deliveryStatus: "pending",
paymentIntentId: "pi_3Ok7hqD8Ejb1s3Do1685nR0Z",
products: [
{
id: "*product id*",
name: "*Product name*",
description: "*My description*",
category: "*category*",
image: {
image: "*imageUrl*",
id: "*image id*"
},
quantity: 2,
price: 34.5
}
]
}
}
Argument `Id` is missing.
In my browser console I am also getting the following error:
Error SyntaxError: Unexpected end of JSON input
prisma.schema:
model Order{
id String @id @default(auto()) @map("_id") @db.ObjectId
userId String @db.ObjectId
amount Float
currency String
status String
deliveryStatus String?
createDate DateTime @default(now())
paymentIntentId String @unique
products CartProductType[]
address Address?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
type CartProductType{
id String
name String
description String
category String
brand String
selectedImg Image
quantity Int
price Float
}
type Image{
color String
colorCode String
image String
}
type Address{
city String
country String
line1 String
line2 String?
postal_code String
state String
}
/api/create-payment-intent/route.ts:
import Stripe from "stripe";
import prisma from "@/libs/prismadb";
import { NextResponse } from "next/server";
import { CartProductType } from "@/app/product/[productId]/productDetails";
import { getCurrentUser } from "@/actions/getCurrentUser";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, {
apiVersion: "2022-11-15",
});
const calculateOrderAmount = (items: CartProductType[]) => {
const totalPrice = items.reduce((acc, item) => {
const itemTotal = item.price * item.quantity;
return acc + itemTotal;
}, 0);
const price: any = Math.floor(totalPrice);
return price;
};
export async function POST(request: Request) {
const currentUser = await getCurrentUser();
if (!currentUser) {
return NextResponse.error();
}
const body = await request.json();
const { items, payment_intent_id } = body;
const total = calculateOrderAmount(items) * 100;
const orderData = {
user: { connect: { id: currentUser.id } },
amount: total,
currency: "gbp",
status: "pending",
deliveryStatus: "pending",
paymentIntentId: payment_intent_id,
products: items,
};
if (payment_intent_id) {
const current_intent = await stripe.paymentIntents.retrieve(
payment_intent_id
);
if (current_intent) {
const updated_intent = await stripe.paymentIntents.update(
payment_intent_id,
{ amount: total }
);
// update the order
const [existing_order, update_order] = await Promise.all([
prisma.order.findFirst({
where: { paymentIntentId: payment_intent_id },
}),
prisma.order.update({
where: { paymentIntentId: payment_intent_id },
data: {
amount: total,
products: items,
},
}),
]);
if (!existing_order) {
return NextResponse.error();
}
return NextResponse.json({ paymentIntent: updated_intent });
}
} else {
// create the intent
const paymentIntent = await stripe.paymentIntents.create({
amount: total,
currency: "gbp",
automatic_payment_methods: { enabled: true },
});
// create the order
orderData.paymentIntentId = paymentIntent.id;
await prisma.order.create({
data: orderData,
});
return NextResponse.json({ paymentIntent });
}
// Return a default response (e.g., an error response) if none of the conditions are met
return NextResponse.error();
}
POST request in my checkoutClient.tsx file:
useEffect(() => {
//create a paymentintent as soon as the page loads
if (cartProducts) {
setLoading(true);
setError(false);
fetch("/api/create-payment-intent", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
items: cartProducts,
payment_intent_id: paymentIntent,
}),
})
.then((res) => {
setLoading(false);
if (res.status === 401) {
return router.push("/login");
}
return res.json();
})
.then((data) => {
setClientSecret(data.paymentIntent.client_secret);
handleSetPaymentIntent(data.paymentIntent.id);
})
.catch((error) => {
setError(true);
console.log("Error", error);
toast.error("Something went wrong");
});
}
}, [cartProducts, paymentIntent]);
useCart.tsx hook:
import {
createContext,
useCallback,
useContext,
useEffect,
useState,
} from "react";
import { CartProductType } from "../product/[productId]/productDetails";
import { toast } from "react-hot-toast";
type CartContextType = {
cartTotalQuantity: number;
cartTotalAmount: number;
cartProducts: CartProductType[] | null;
handleAddToCart: (product: CartProductType) => void;
handleRemoveFromCart: (product: CartProductType) => void;
handleCartQuantityIncrease: (product: CartProductType) => void;
handleCartQuantityDecrease: (product: CartProductType) => void;
handleClearCart: () => void;
paymentIntent: string | null;
handleSetPaymentIntent: (val: string | null) => void;
};
export const CartContext = createContext<CartContextType | null>(null);
interface Props {
[propName: string]: any;
}
export const CartContextProvider = (props: Props) => {
const [cartTotalQuantity, setCartTotalQuantity] = useState(0);
const [cartTotalAmount, setCartTotalAmount] = useState(0);
const [cartProducts, setCartProducts] = useState<CartProductType[] | null>(
null
);
const [paymentIntent, setPaymentIntent] = useState<string | null>(null);
//Retrieves local storage cart data and updates the cart
useEffect(() => {
const cartItems: any = localStorage.getItem("HillsideCart");
const cartStorageProducts: CartProductType[] | null = JSON.parse(cartItems);
const paymentIntentStorage: any = localStorage.getItem("paymentIntentStorage");
const paymentIntent: string | null = JSON.parse(paymentIntentStorage);
setCartProducts(cartStorageProducts);
setPaymentIntent(paymentIntent);
}, []);
//Updates the total amount figure when items are added and removed
useEffect(() => {
const updateTotal = () => {
if (cartProducts) {
const { total, quantity } = cartProducts?.reduce(
(acc, item) => {
const itemTotal = item.price * item.quantity;
acc.total += itemTotal;
acc.quantity += item.quantity;
return acc;
},
{
total: 0,
quantity: 0,
}
);
setCartTotalAmount(total);
setCartTotalQuantity(quantity);
}
};
updateTotal();
}, [cartProducts]);
//Hanldes adding new products to the cart
const handleAddToCart = useCallback((product: CartProductType) => {
setCartProducts((prev) => {
let updatedCart;
if (prev) {
updatedCart = [...prev, product];
} else {
updatedCart = [product];
}
localStorage.setItem("HillsideCart", JSON.stringify(updatedCart));
return updatedCart;
});
toast.success("Item Added to Cart");
}, []);
//Handles removing products from the cart
const handleRemoveFromCart = useCallback(
(product: CartProductType) => {
if (cartProducts) {
const filteredProducts = cartProducts.filter((item) => {
return item.id !== product.id;
});
setCartProducts(filteredProducts);
localStorage.setItem("HillsideCart", JSON.stringify(filteredProducts));
}
toast.success("Item removed from Cart");
},
[cartProducts]
);
//
//handles increasing quantity of product in cart page
const handleCartQuantityIncrease = useCallback(
(product: CartProductType) => {
let updatedCart;
//checks for max quantity and returns error message
if (product.quantity === 99) {
return toast.error("Maximum quantity reached");
}
//checks to see if products exist in cart - finds the relevant item index and checks it exists
if (cartProducts) {
updatedCart = [...cartProducts];
const existingIndex = cartProducts.findIndex(
(item) => item.id === product.id
);
//accesses product and updates
if (existingIndex > -1) {
updatedCart[existingIndex].quantity = ++updatedCart[existingIndex]
.quantity;
}
setCartProducts(updatedCart);
localStorage.setItem("HillsideCart", JSON.stringify(updatedCart));
}
},
[cartProducts]
);
//
//handles decreasing quantity of product in cart page
const handleCartQuantityDecrease = useCallback(
(product: CartProductType) => {
let updatedCart;
//checks for max quantity and returns error message
if (product.quantity === 1) {
return toast.error("Minumum quantity reached");
}
//checks to see if products exist in cart - finds the relevant item index and checks it exists
if (cartProducts) {
updatedCart = [...cartProducts];
const existingIndex = cartProducts.findIndex(
(item) => item.id === product.id
);
//accesses product and updates
if (existingIndex > -1) {
updatedCart[existingIndex].quantity = --updatedCart[existingIndex]
.quantity;
}
setCartProducts(updatedCart);
localStorage.setItem("HillsideCart", JSON.stringify(updatedCart));
}
},
[cartProducts]
);
//handles clearing the cart
const handleClearCart = useCallback(() => {
setCartProducts(null);
setCartTotalQuantity(0);
localStorage.setItem("HillsideCart", JSON.stringify(null));
toast.success("Cart cleared");
}, []);
//sets payment intent
const handleSetPaymentIntent = useCallback(
(val: string | null) => {
setPaymentIntent(val);
localStorage.setItem("paymentIntentStorage", JSON.stringify(val));
},
[paymentIntent]
);
const value = {
cartTotalQuantity,
cartTotalAmount,
cartProducts,
handleAddToCart,
handleRemoveFromCart,
handleCartQuantityIncrease,
handleCartQuantityDecrease,
handleClearCart,
paymentIntent,
handleSetPaymentIntent,
};
return <CartContext.Provider value={value} {...props} />;
};
export const useCart = () => {
const context = useContext(CartContext);
if (context === null) {
throw new Error("useCart must be used within a CartContext Provider");
}
return context;
};
Searched through similar looking errors with no luck to direct me.