JayKay
JayKay

Reputation: 31

Uncaught (in promise) IntegrationError: Missing value for Stripe(): apiKey should be a string. Only while deployed to vercel but working fine on local

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.

enter image description here

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

Answers (3)

user21335630
user21335630

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

Ron
Ron

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

Jatin
Jatin

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

Related Questions