The payment gateway is working fine and all, but sometimes even if I get the message that payment was successful, there’s no entry in my db. I check the webhook screen and there are bunch of incomplete payments. When I come to check my db later on, I see bunch of payment entries in the db even when I am not using it, which I am guessing is because I go back from the payment screen? Even if payment is successful and it gets inserted in db, I am seeing two entries in the db at the same time. What is the problem here?
//Payment page
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string)
const BuyProduct = async({params}: BuyProductProps) => {
const product = await prisma.product.findUnique({where: {id: params.id}})
const {getUser, isAuthenticated} = getKindeServerSession()
const user = await getUser()
if(user == null) {
redirect("/")
}
if(product == null) {
return <div>Product not found.</div>
}
const paymentIntent = await stripe.paymentIntents.create({
amount: product?.price!,
currency: "inr",
metadata: {productId: product?.id, userId: user?.id}
})
if(paymentIntent.client_secret == null) {
throw Error("Stripe failed to create payment link.")
}
return (
<CheckoutForm product={product as Product} clientSecret={paymentIntent.client_secret} />
)
}
export default BuyProduct
//checkout form
"use client";
const stripePromise = loadStripe(
process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY as string
);
const CheckoutForm = ({ product, clientSecret }: CheckoutFormProps) => {
const [quantity, setQuantity] = useState(1);
return (
<div className="pb-16 lg:p-2">
<h1 className="font-bold text-red-500 text-center tracking-wide">This is in experimentation. May not work as intended.</h1>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="flex flex-col px-2 pt-20 h-full items-center justify-center">
<div className="relative h-[200px] w-full">
<Image
alt={product?.name}
src={`${process.env.NEXT_PUBLIC_SUPABASE_URL}/storage/v1/object/public/Zephyr-products/${product?.image}`}
fill
className="object-contain"
/>
</div>
<div className="my-8">
<h1 className="text-2xl font-bold">{`${product?.name} (x${quantity})`}</h1>
</div>
<div className="flex items-center gap-4">
<Button
disabled={quantity === 1}
onClick={() => setQuantity((prev) => prev - 1)}
variant="ghost"
className="border border-border bg-accent hover:opacity-75 duration-100 transition-opacity ease-in-out"
>
-
</Button>
<div className="text-bold text-red-500 text-md">{quantity}</div>
<Button
disabled={quantity === 10}
onClick={() => setQuantity((prev) => prev + 1)}
variant="ghost"
className="border border-border bg-accent hover:opacity-75 duration-100 transition-opacity ease-in-out"
>
+
</Button>
</div>
</div>
<div className="flex px-4 pt-20 h-full items-center justify-center">
<Elements
options={{ clientSecret, appearance: { theme: "night" } }}
stripe={stripePromise}
>
<Form price={product?.price} quantity={quantity} />
</Elements>
</div>
</div>
</div>
);
};
function Form({ price, quantity }: { price: number; quantity: number }) {
const stripe = useStripe();
const elements = useElements();
const [loading, setLoading] = useState(false);
const [errors, setError] = useState<string>();
const finalPrice = new Intl.NumberFormat("en-IN").format(price * quantity);
function handleSubmit(e: FormEvent) {
e.preventDefault();
if (stripe == null || elements == null) return;
setLoading(true);
stripe
.confirmPayment({
elements,
confirmParams: {
return_url: `${process.env.NEXT_PUBLIC_PAYMENT_REDIRECT_URL}/stripe/purchase-success`,
},
})
.then(({ error }) => {
if (error.type === "card_error" || error.type === "validation_error") {
setError(error.message);
} else {
console.log(error);
setError("An unexpected error occurred.");
}
})
.finally(() => {
setLoading(false);
});
}
return (
<form onSubmit={handleSubmit} className="w-full">
//styling
</form>
);
}
export default CheckoutForm;
//webhook
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string)
export async function POST(req: NextRequest ) {
const payload = await req.text()
const response = JSON.parse(payload)
const sig = req.headers.get("Stripe-Signature")
const event = stripe.webhooks.constructEvent(
payload,
sig!,
process.env.STRIPE_WEBHOOK_SECRET!
);
if(event.type === "charge.succeeded") {
const charge = event.data.object
const productId = charge.metadata.productId
const userId = charge.metadata.userId
const email = charge.billing_details.email
const price = charge.amount
const product = await prisma.product.findUnique({where: {id: productId}})
if(product == null || email == null) {
return new NextResponse("Bad Request", {status: 400})
}
await prisma.purchased.create({
data: {
id: product.id,
kindeAuth: userId,
pricePaid: price,
}
})
}
}