Stripe issues when "pay with stripe" React Js method - reactjs

I am following tutorial step by step https://www.youtube.com/watch?v=4mOkFXyxfsU everything doing step by steps but when clicking on paying with stripe the window redirecting will pop up but that's it... when checking console I've got this errors
http://localhost:3000/api/stripe 400 (Bad Request)
In console I have
Uncaught (in promise) IntegrationError: stripe.redirectToCheckout: You must provide one of lineItems, items, or sessionId.
My code's so far
getStripe.js
import { loadStripe } from '#stripe/stripe-js';
let stripePromise;
const getStripe = () => {
if(!stripePromise) {
stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY);
}
return stripePromise;
}
export default getStripe;
stripe.js
import Stripe from 'stripe';
const stripe = new Stripe(process.env.NEXT_PUBLIC_STRIPE_SECRET_KEY);
export default async function handler(req, res) {
if (req.method === 'POST') {
try {
const params = {
submit_type: 'pay',
mode: 'payment',
payment_method_types: ['card'],
billing_address_collection: 'auto',
shipping_options: [
{ shipping_rate: 'shr_1Lx6efBDBeK8mdWXxij3rOj5' },
],
line_items: req.body.map((item) => {
const img = item.image[0].asset._ref;
const newImage = img.replace('image-','https://cdn.sanity.io/images/{id of sanity project}/production/').replace('-webp', '.webp');
return {
price_data: {
currency: 'usd',
product_data: {
name: item.name,
images: [newImage],
},
unit_amount: item.price * 100,
},
adjustable_quantity: {
enabled:true,
minimum: 1,
},
quantity: item.quantity
}
}),
success_url: `${req.headers.origin}/success`,
cancel_url: `${req.headers.origin}/canceled`,
}
// Create Checkout Sessions from body params.
const session = await stripe.checkout.sessions.create(params);
res.status(200).json(session);
} catch (err) {
res.status(err.statusCode || 500).json(err.message);
}
} else {
res.setHeader('Allow', 'POST');
res.status(405).end('Method Not Allowed');
}
}
Here is also whole code that was originally set up from this course similar to mine..: https://github.com/adrianhajdin/ecommerce_sanity_stripe
Thanks
I've tried to redo the course and even thought check original code from github but got this error..

Problem resolved.
Issue was just with Stripe itself, you need to set your payment site before proceeding with payment, now it's working like a charm

Related

Stripe AxiosError: Request failed with status code 500

I am trying for implement Stripe checkout in NextJS following this example.
But I always get this error:
checkout.js:
import React from "react";
import Header from "../components/Header";
import Image from "next/image";
import { selectItems, selectTotal } from "../slices/basketSlice";
import { useSelector } from "react-redux";
import CheckoutProduct from "../components/CheckoutProduct";
import Currency from "react-currency-formatter";
import { useSession } from "next-auth/react";
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 { data: session } = useSession();
const CreateCheckoutSession = async () => {
const stripe = await stripePromise
// create checkout session
const checkoutSession = await axios.post("/api/create-checkout-session", {
items : items,
email: session.user.email,
})
//redirect user to Stripe checkout
const result = await stripe.redirectToCheckout({
sessionId: checkoutSession.data.id
})
if (result.error) {
alert(result.error.message)
}
}
create-checkout-session.js (tried to do it the way on doc page but get same relult):
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)
// console.log(process.env.STRIPE_SECRET_KEY)
const transformedItems = items.map(item => ({
description: item.description,
quantity: 1,
price_data: {
currency: 'usd',
unit_amount: item.price * 100,
product_data: {
name: item.title,
images: [item.image]
},
},
}))
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
mode: 'payment',
shipping_rates: ['shr_1MFhHmL1z8EG1fYglFH9EBP7'],
shipping_address_collection: {
allowed_countries: ['GB', 'US', 'CA']
},
line_items: transformedItems,
success_url: `${pocess.env.HOST}/success`,
cancel_url: `${process.env.HOST}/checkout`,
metadata: {
email,
images: JSON.stringify(items.map(item => item.image))
}
})
}
res.status(err.statusCode || 500).json(err.message);
res.status(200).json({
id: session.id
})
I seem to me following ths Stipe formating but just keep on getting this unhelpfull error. Does anybody have a clue what it means?
The error seems to be coming from your own server and not Stripe's API. In your create-checkout-session.js file, the scope of the async function seem to end prior to you running res.status(xxx).json(...).
In this case, session variable is out of the scope of your res.status(...).json(...) function. Additionally, I suspect this code is sending the 500 res.status(err.statusCode || 500).json(err.message); as it doesn't know what err variable is.
Can you try fixing the scope as well as defining the err variable and see if that makes a difference?
In case this helps someone.
It turns out the Stripe api version I was using had different formatting requirements, so that description needs to be inside product_data array.
I find Stripe documentation just so hard to navigate. I don't get why not just use a flat array for all items and why keep nesting - renesting the items? :(
The stripe-session-checkout.js images
Remove the shipping-rates and the payment-method-types
It worked for me

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client in nextJS

The code works properly and redirects to the stripe checkout page but after deployment, it doesn't. I'm getting the status 500 when trying to checkout but the cart items and amount get posted in stripe logs with the status unpaid.
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at new NodeError (node:internal/errors:387:5)
at ServerResponse.setHeader (node:_http_outgoing:603:11)
at NodeNextResponse.setHeader (C:\Users\subash\OneDrive\Desktop\ecommerce\ecommerce\node_modules\next\dist\server\base-http\node.js:56:19)
Here's the code
lib/getStripe.js
import {loadStripe} from '#stripe/stripe-js';
let stripePromise;
const getStripe = () => {
if(!stripePromise){
stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY);
}
return stripePromise;
}
export default getStripe;
cart.js
const handleCheckout = async () => {
const stripe = await getStripe();
const response = await fetch('/api/stripe', {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(cartItems)
});
if (response.statusCode === 500) return;
const data = await response.json();
toast.loading("Redirecting...");
stripe.redirectToCheckout({ sessionId: data.id });
};
pages/api/stripe.js
import Stripe from 'stripe';
const stripe = new Stripe(process.env.NEXT_PUBLIC_STRIPE_SECRET_KEY);
export default async function handler(req, res) {
if (req.method === 'POST') {
try {
// Create Checkout Sessions from body params.
const params = {
submit_type :'pay',
mode:'payment',
payment_method_types:['card'],
shipping_address_collection: {
allowed_countries: ['IN'],
},
shipping_options: [
{shipping_rate: '...'}
],
line_items: req.body.map((item)=>{
const img = item.image[0].asset._ref;
const newImage = img.replace('image-','https://cdn.sanity.io/..../').replace('-webp','.webp');
return {
price_data:{
currency:'inr',
product_data:{
name:item.name,
images:[newImage],
},
unit_amount:item.price*100,
},
adjustable_quantity:{
enabled:true,
minimum:1
},
quantity:item.quantity
}
}),
success_url: `${req.headers.origin}/success`,
cancel_url: `${req.headers.origin}`,
}
const session = await stripe.checkout.sessions.create(params);
res.status(200).json(session);
res.redirect(303, session.url);
} catch (err) {
res.status(err.statusCode || 500).json(err.message);
}
} else {
res.setHeader('Allow', 'POST');
console.log("error");
res.status(405).end('Method Not Allowed');
}
}
You're still using the legacy Checkout integration with redirectToCheckout instead you should look at the new integration path for Next.js that you can find here. If you want more info about migrating from the legacy integration you can check the Checkout migration guide here.

You did not provide an API key. You need to provide your API key in the Authorization header

im getting this error when i try to use the my checkout function using Stripe:
You did not provide an API key. You need to provide your API key in the Authorization header, using Bearer auth (e.g. 'Authorization: Bearer YOUR_SECRET_KEY'). See https://stripe.com/docs/api#authentication for details, or we can help at https://support.stripe.com/.
I also tried to use a if check to check for the stripe key, but i got an error that said the key did not exist .
checkout function:
const handleCheckOut = async () => {
const stripe = await getStripe();
const response = await fetch("/api/stripe", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(cartItems),
});
if (response.statusCode === 500) return;
const data = await response.json();
toast.loading("Redirecting...");
stripe.redirectToCheckout({ sessionId: data.id });
};
Even though im passing the Stripe api secret key as Authorization header it is still not woking
getStripe.js
import { loadStripe } from "#stripe/stripe-js";
let stripePromise;
const getStripe = () => {
if (!stripePromise) {
stripePromise = loadStripe(`${process.env.STRIPE_PUBLIC_KEY}`);
}
return stripePromise;
};
export default getStripe;
api/stripe.js
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export default async function handler(req, res) {
console.log(req.body.cartItems);
if (req.method === "POST") {
try {
const params = {
submit_type: "pay",
mode: "payment",
payment_method_type: ["card"],
billing_address_collection: "auto",
// formas de envio
shipping_options: [
{ shipping_rate: "shr_1LJo2EHt0s8JSRoPQEDeHfo5" },
{ shipping_rate: "shr_1LJo3ZHt0s8JSRoP8uVNJhwS" },
],
line_items: req.body.map((item) => {
const img = item.image[0].asset._ref;
const newImage = img
.replace(
"image-",
"https://cdn.sanity.io/images/psdgq2wv/production/"
)
.replace("-webp", ".webp");
return {
price_data: {
currency: "usd",
product_data: {
name: item.name,
images: [newImage],
},
unit_amount: item.price * 100,
adjustable_quantity: {
enabled: true,
minimum: 1,
},
quantity: item.quantity,
},
};
}),
// success_url: `${req.headers.origin}/?success=true`,
// cancel_url: `${req.headers.origin}/?canceled=true`,
};
// Create Checkout Sessions from body params.
const session = await stripe.checkout.sessions.create(params);
res.status(200).json(session);
} catch (err) {
res.status(err.statusCode || 500).json(err.message);
}
} else {
res.setHeader("Allow", "POST");
res.status(405).end("Method Not Allowed");
}
}
I do not think that you need to send this: Authorization: Bearer ${process.env.STRIPE_SECRET_KEY},. I think the issue is here
const getStripe = () => {
if (!stripePromise) {
stripePromise = loadStripe(`${process.env.STRIPE_PUBLIC_KEY}`);
}
return stripePromise;
};
since you are on client side, process.env.STRIPE_PUBLIC_KEY will be undefined. because by default environment variables from .env files load to server. check this out: whats-the-difference-between-exposing-environment-variables-in-nextjs-through
You have to define your env variable
NEXT_PUBLIC_STRIPE_API_KEY=xxxxxxxxxxxxxxx
then use it in getStripe:
stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_API_KEY);

AWS Amplify and Next.JS with GraphQL Server Error No current user from getStaticPaths

I'm having trouble accessing data from Amplify's API Graphql, and it keeps returning
Server Error
Error: No current user
I've been following this tutorial: https://youtu.be/13nYLmjZ0Ys?t=2292
I know I'm signed into Amplify because if I go into different pages, I can grab user Auth and I can even display the SignOut button. But for whatever reason, I'm not sure why I'm getting this error
import { API } from "aws-amplify";
import { useRouter } from "next/router";
import { listActivations, getActivation } from "../../graphql/queries";
const Activation = ({ activation }) => {
const router = useRouter();
if (router.isFallback) {
return <div>Loading</div>;
}
return <div>{activation.title}</div>;
};
export default Activation;
export async function getStaticPaths() {
const SSR = withSSRContext();
console.log("static paths");
const activationData = await SSR.API.graphql({
query: listActivations,
});
console.log("activationData", activationData);
const paths = activationData.data.listActivations.items.map((activation) => ({
params: { id: activation.id },
}));
return {
paths,
fallback: true,
};
}
export async function getStaticProps({ params }) {
const SSR = withSSRContext(); // added SSR, but still getting error
console.log("static props");
const { id } = params;
const activationData = await SSR.API.graphql({
query: getActivation,
variables: { id },
});
return {
props: {
activation: activationData.data.getActivation,
},
};
}
The console log static paths appears, and then after that, I get errors.
Do you think it has anything to do with my GraphQL schema?
type User #model #auth(rules: [{ allow: owner, ownerField: "username" }]) {
id: ID!
username: String!
email: String!
userType: UserType
}
type Activation
#model
#key(
name: "activationsByStudentId"
fields: ["student"]
queryField: "activationsByStudentId"
)
#auth(
rules: [
{ allow: groups, groups: ["Admin"] }
{ allow: owner }
{
allow: owner
ownerField: "studentId"
operations: [create, update, delete]
}
{ allow: private, operations: [read] }
{ allow: public, operations: [read] }
]
) {
id: ID!
studentId: ID!
title: String!
student: Student #connection(fields: ["studentId"])
teachers: [TeachersActivations] #connection(name: "ActivationTeachers")
}
Edit: I've also added User model to see if this could be a cause too.
Since both getStaticProps and getStaticPaths are called during build time, and on the server when fallback is equal to true, you need to configure Amplify for SSR (Server-Side Rendering). Make sure to take a look at SSR Support for AWS Amplify JavaScript Libraries.
The solution: first, configure Amplify for SSR:
Amplify.configure({ ...awsExports, ssr: true });
Then you need to use withSSRContext, and add the the authMode parameter. As quoted from the link above:
For example, take an AppSync GraphQL API that is backed by an identity provider such as Amazon Cognito User pools, Okto, or Auth0. Some GraphQL types may require a user to be authenticated to perform certain requests. Using the API class, the user identity will now automatically be configured and passed into the API request headers:
const SSR = withSSRContext();
const activationData = await SSR.API.graphql({
query: listActivations,
authMode: "AMAZON_COGNITO_USER_POOLS"
});
Still, I couldn't figure out the issue why this can't work, so I decided to move my query into client-side
const [activation, setActivation] = useState(null);
const router = useRouter();
const { aid } = router.query;
useEffect(() => {
if (!aid) return;
async function activationDataFromClient() {
try {
const getActivationData = await API.graphql({
query: getActivation,
variables: {
id: aid,
},
});
setActivation(getActivationData.data.getActivation);
} catch (err) {
console.log("error fetching activation data: ", err);
}
}
activationDataFromClient();
}, [aid]);
I had the same problem. Changing the authMode to 'API_KEY' enabled it to work for me. See example below:
export async function getStaticPaths(context) {
const SSR = withSSRContext();
const { data } = await SSR.API.graphql({
query: listArticles,
authMode: 'API_KEY'
});
const paths = data.listArticles.items.map((article) => ({
params: { id: article.id },
}));
return {
paths,
fallback: true,
};
}
export async function getStaticProps({ params }) {
const SSR = withSSRContext();
const { data } = await SSR.API.graphql({
query: getArticle,
variables: {
id: params.id,
},
authMode: 'API_KEY'
});
return {
props: {
article: data.getArticle
}
}
}

Facebook and Google requires two login click to login with Firebase Auth

I have React web application with firebase auth (mail, Facebook, Google).
Google and Facebook work only after 2 login clicks.
The code is equal, just the provider is different.
import React from 'react';
import firebase from "firebase/app";
import { app } from "../../../../config/firebase";
const signupWithGoogle = (user, userInfo)=>{
app.firestore().collection('users').doc(user.uid).set({
firstName: userInfo.profile.given_name,
lastName: userInfo.profile.family_name});
const batch = app.firestore().batch();
const initData = [
{ Applied: { positionIds: [], title: 'Applied' } },
{ Contract: { positionIds: [], title: 'Contract' } },
{ Denied: { positionIds: [], title: 'Denied' } },
{ InProgress: { positionIds: [], title: 'In Progress' } },
{ ReceivedTask: { positionIds: [], title: 'Received Task' } },
];
initData.forEach((doc) => {
const docRef = app
.firestore()
.collection('users')
.doc( user.uid)
.collection('columns')
.doc(Object.keys(doc)[0]);
batch.set(docRef, Object.values(doc)[0]);
});
const batchCommit= batch.commit();
return batchCommit;
}
export const googleLogin = async (
history
) => {
var provider = new firebase.auth.GoogleAuthProvider();
await firebase.auth()
.signInWithPopup(provider)
.then( resp => {
let {user, credential,additionalUserInfo: userInfo} = resp;
if (userInfo.isNewUser) signupWithGoogle(user, userInfo);
}).then(()=>
history.push('/')
)
.catch((error) => {
console.error(error.message);
});
};
I saw this question, but didn't help.(Firebase Authentication Requires Two 'Login' Calls)
I had the same problem with Firebase Authentication with Facebook, I had to register two times to make it works.
The problem was in my HTLM, I used a form.
I changed for a simpler code, and it worked.
While waiting for where you call your function from, as your issue would relate to improper state management, here are some edits you can make to the code you have shared so far to squash some problems that it has.
In your signupWithGoogle function, you create a floating promise that should be included in the write batch that you use to create the /users/{userId}/columns collection. Because you use Object.keys(doc)[0] and Object.values(doc)[0], you should consider using an array of [docId, docData] pairs or a JSON-like object structure like so:
// prepare data to add to the user's columns collection
const initColumnsData = {
Applied: { positionIds: [], title: 'Applied' },
Contract: { positionIds: [], title: 'Contract' },
Denied: { positionIds: [], title: 'Denied' },
InProgress: { positionIds: [], title: 'In Progress' },
ReceivedTask: { positionIds: [], title: 'Received Task' }
};
// queue columns data upload
Object.entries(initColumnsData)
.forEach(([docId, docData]) => {
const docRef = userDocRef
.collection('columns')
.doc(docId);
batch.set(docRef, docData);
});
As you mentioned that a lot of your code is shared aside from the provider implementation, you should consider extracting the common code from those functions:
const initUserData = (user, userDocData) => {
// init write batch
const batch = app.firestore().batch();
// init ref to user data
const userDocRef = app.firestore().collection('users').doc(user.uid);
// queue user data upload
batch.set(userDocRef, userDocData);
// prepare data to add to the user's columns collection
const initColumnsData = {
Applied: { positionIds: [], title: 'Applied' },
Contract: { positionIds: [], title: 'Contract' },
Denied: { positionIds: [], title: 'Denied' },
InProgress: { positionIds: [], title: 'In Progress' },
ReceivedTask: { positionIds: [], title: 'Received Task' }
};
// queue columns data upload
Object.entries(initColumnsData)
.forEach(([docId, docData]) => {
const docRef = userDocRef
.collection('columns')
.doc(docId);
batch.set(docRef, docData);
});
// make the changes
return batch.commit();
}
const initUserDataForGoogle(user, userInfo) {
return initUserData(user, {
firstName: userInfo.profile.given_name,
lastName: userInfo.profile.family_name
});
}
const initUserDataForFacebook(user, userInfo) {
return initUserData(user, {
firstName: /* ... */,
lastName: /* ... */
});
}
When exporting a function to be called elsewhere, avoid causing "side effects" (like navigating using the History API) and don't trap errors (using .catch() without rethrowing the error). The calling code should handle the result and any errors itself.
export const loginWithGoogle = async () => {
const provider = new firebase.auth.GoogleAuthProvider();
return firebase.auth()
.signInWithPopup(provider)
.then(async resp => {
const {user, credential, additionalUserInfo: userInfo} = resp;
if (userInfo.isNewUser)
await initUserDataForGoogle(user, userInfo);
return user;
});
};
Then in your components, you'd use:
setLoading(true);
/* await/return */ loginWithGoogle()
.then(() => {
history.push('/');
// or
// setLoading(false)
// then do something
})
.catch((err) => {
console.error("loginWithGoogle failed: ", err);
setLoading(false);
setError("Failed to log in with Google!"); // <- displayed in UI to user
});

Resources