Reputation: 31
My stripe checkout session on my localhost is working fine, but when I try to redirect from my deployed website's checkout page, there is an ERROR.
My code where I think the error is coming from:
/src/pages/checkout.js
import { useSession } from "next-auth/client";
import Image from "next/image";
import { useSelector } from "react-redux";
import CheckoutProduct from "../components/CheckoutProduct";
import Header from "../components/Header";
import Currency from "react-currency-formatter";
import { selectItems, selectTotal } from "../slices/basketSlice";
import { loadStripe } from "@stripe/stripe-js";
import axios from "axios";
const stripePromise = loadStripe(process.env.stripe_public_key);
function checkout() {
const items = useSelector(selectItems);
const total = useSelector(selectTotal);
const [session] = useSession();
const createCheckoutSession = async () => {
const stripe = await stripePromise;
// call the backend to create a checkout session
const checkoutSession = await axios.post("/api/create-checkout-session", {
items: items,
email: session.user.email,
});
// redirect customers to stripe checkout
const result = await stripe.redirectToCheckout({
sessionId: checkoutSession.data.id,
});
if (result.error) {
alert(result.error.message);
}
};
return (
<div className="h-screen">
<Header />
<main className="lg:flex flex-1 overflow-y-auto max-w-screen-2xl mx-auto">
{/* Left side */}
<div className="flex-grow m-5 shadow-sm">
<Image
src="/images/amazon-cart.jpg"
width={1020}
height={250}
objectFit="contain"
/>
<div className="fex flex-col p-5 space-y-10 bg-white">
<h1 className="text-3xl border-b pb-4">
{items.length === 0
? "Your Amazon basket is empty"
: "Shopping Basket"}
</h1>
{items.map((item, i) => (
<CheckoutProduct
key={i}
id={item.id}
title={item.title}
rating={item.rating}
price={item.price}
description={item.description}
category={item.category}
image={item.image}
hasPrime={item.hasPrime}
/>
))}
</div>
</div>
{/* Right side */}
<div className="flex flex-col bg-white p-10 shadow-md">
{items.length > 0 && (
<>
<h2 className="whitespace-nowrap">
Subtotal ({items.length} items):
<span className="font-bold">
<Currency quantity={total} currency="EUR" />
</span>
</h2>
<button
role="link"
onClick={createCheckoutSession}
disabled={!session}
className={`button mt-2 ${
!session &&
"from-gray-300 to-gray-500 border-gray-200 text-gray-300 cursor-not-allowed"
}`}
>
{!session ? "Sign in to Checkout" : "Proceed to Checkout"}
</button>
</>
)}
</div>
</main>
</div>
);
}
export default checkout;
/next.config.js
module.exports = {
env: {
stripe_public_key: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
},
};
My environment variables are also deployed on Vercel with the below naming convention:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_mykey
# Stripe Terminal/CLI
STRIPE_WEBHOOK_SECRET=whsec_mywebhooksecret
STRIPE_SECRET_KEY=sk_test_mysecret
/src/pages/api/create-checkout-session.js
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
export default async (req, res) => {
const { items, email } = req.body;
// console.log(items);
// console.log(email);
const transformedItems = items.map((item) => ({
price_data: {
currency: "eur",
product_data: {
name: item.title,
images: [item.image],
},
unit_amount: item.price * 100,
},
description: item.description,
quantity: 1,
}));
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
shipping_rates: ["shr_1JPZN2HHQ2qhqkUCKLyMJDLO"],
shipping_address_collection: {
allowed_countries: ["GB", "US", "CA", "DE"],
},
line_items: transformedItems,
mode: "payment",
success_url: `${process.env.HOST}/success`,
cancel_url: `${process.env.HOST}/checkout`,
metadata: {
email,
images: JSON.stringify(items.map((item) => item.image)),
},
});
res.status(200).json({ id: session.id });
};
/src/pages/api/webhook.js
import { buffer } from "micro";
import * as admin from "firebase-admin";
// secure a connection to FIREBASE from backend
const serviceAccount = require("../../../permissions.json");
const app = !admin.apps.length
? admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
})
: admin.app();
// Establish connection to stripe
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
const fullfillOrder = async (session) => {
//console.log("Fulfilling order", session);
return app
.firestore()
.collection("users")
.doc(session.metadata.email)
.collection("orders")
.doc(session.id)
.set({
amount: session.amount_total / 100,
amount_shipping: session.total_details.amount_shipping / 100,
images: JSON.parse(session.metadata.images),
timestamp: admin.firestore.FieldValue.serverTimestamp(),
customer_email: session.customer_details.email,
})
.then(() => {
console.log(`SUCCESS: Order ${session.id} has been added to DB`);
});
};
export default async (req, res) => {
if (req.method === "POST") {
const requestBuffer = await buffer(req);
const payload = requestBuffer.toString();
const sig = req.headers["stripe-signature"];
let event;
// verify that the event posted came from stripe
try {
event = stripe.webhooks.constructEvent(payload, sig, endpointSecret);
} catch (error) {
console.log("ERROR while constructing event", error.message);
return res
.status(400)
.send(`Webhook error while constructing event:${error.message}`);
}
// Handle checkout.session.completed event
if (event.type === "checkout.session.completed") {
const session = event.data.object;
// fulfill the order...
return fullfillOrder(session)
.then(() => res.status(200))
.catch((err) => {
console.log("ERROR while fulfilling order", err.message);
res
.status(400)
.send(`Webhook Error while fulfilling order: ${err.message}`);
});
}
}
};
export const config = {
api: {
bodyParser: false,
externalResolver: true,
},
};
The app is hosted on Vercel. Created with Next.js / React / Redux. Connected to Firebase / Stripe and I use Axios.
Is this snippet of code where the error is coming from? The hardest part about understanding this error is my app works perfectly fine in the localhost with zero issues on all pages in the console, but on the deployed website this error comes up.
What part of my code might be wrong? I want to be able to click the button and be taken to the Stripe checkout page.
Upvotes: 3
Views: 4026
Reputation:
I had the same issue. but in localhost I had this error: cannot set headers after they are sent to the client. so I commented res.status() & res.setHeader() and then it worked! note that I used fetch, not axios to post data from plans page to my api/handler.
Upvotes: 0
Reputation: 1161
Don't forget to restart each time you edit an env value locally.
For me const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLIC)
worked.
This is used in the browser, that requires it to be prefixed with NEXT_PUBLIC. I didn't have that initially, still didn't work cause I didn't restart the server.
Also when adding the envs on vercel they will not take effect until you do a new deployment.
Upvotes: 1
Reputation: 101
replace ...
const stripePromise = loadStripe(process.env.stripe_public_key);
with ...
const stripePromise = loadStripe(`${process.env.stripe_public_key}');
... hope it will be help you.
Upvotes: 8