Stripe AxiosError: Request failed with status code 500 - reactjs

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

Related

Request URL from React (Vite) Front-End to Express.js Back-End includes page path and results 404 Not Found

I created an Express.js Back-End that runs on port 4000. Also React (Vite) app runs on port 5173. I try to make some axios requests to my Back-End. Eventhough the URL looks wrong on DevTools when I make any request from my home page, it is still able to hit the Back-End and fetch the data (I can log the request on the Back-End console). But when I try to make a request from another page such as "127.0.0.1:5173/quiz", the request URL also includes "quiz". That's why I get 404.
So it shows "http://127.0.0.1:5173/quiz/api/quiz/:quizId"
But it needs to be "http://127.0.0.1:4000/api/quiz/:quizId"
But like I said, it works when I make a request on home page:
"http://127.0.0.1:5173/api/quiz" - This works, and fetches the quiz list.
Btw, to solve CORS issues, I tried to add "proxy" on package.json, but it didn't work. Then I add some configurations on vite.config.ts, it worked, but like I said I kept seeing "http://127.0.0.1:5173" on Dev Tools Network tab instead of "http://127.0.0.1:4000".
Here's my vite.config.ts:
import { defineConfig } from "vite";
import react from "#vitejs/plugin-react";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
proxy: {
"/api": {
target: "http://localhost:4000",
changeOrigin: true,
secure: false,
},
},
},
});
Here's my request code
import { useState } from "react";
import { useAuthContext } from "./useAuthContext";
import { useQuizContext } from "./useQuizContext";
import { QuizType, Types as QuizActionTypes } from "../context/quiz/types";
import axios from "axios";
export const useQuiz = () => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const { state: authState } = useAuthContext();
const { dispatch } = useQuizContext();
const getQuiz = async (id: string) => {
setIsLoading(true);
setError(null);
const queryParams: string[] = [];
// If it's a logged in user
if (authState.user.token) {
queryParams.push(`userId=${authState.user._id}`);
}
// If it's a participant who's done with the quiz
if (localStorage.getItem("gifQuizUser")) {
queryParams.push("participantCompleted=true");
}
const uri =
queryParams.length > 0
? `api/quiz/${id}?${queryParams.join("&")}`
: `api/quiz/${id}`;
console.log(uri);
// Fetch & Dispatch
try {
const response = await axios.get(uri);
if (!response.data.error) {
dispatch({
type: QuizActionTypes.GetQuiz,
payload: response.data,
});
setIsLoading(false);
}
} catch (err: any) {
if (err.response.data.error) {
setIsLoading(false);
setError(err.response.data.error);
}
}
};
return { isLoading, error, getQuizzes, getQuiz };
};
Thank you for your time.

Stripe issues when "pay with stripe" React Js method

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

How to push the data to firestore after stripe checkout session complete using React js frontend

I am trying to push the checkout basket data to firebase firestore after a stripe checkout session complete using react js node js. the checkout session successfully completed without error. However, there are NO user orders data being push to firebase firestore from the piece of code below:
const { error } = await stripe.redirectToCheckout({
sessionId
}).then(()=>{
db
.collection('users')
.doc(user?.uid)
.collection('orders')
.doc()
.set({
basket: basket,
// amount: paymentIntent.amount,
})
});
Below is the whole pieces of codes of backend and frontend
Functions/index.js
const functions = require("firebase-functions");
const express=require("express");
const cors=require("cors");
require('dotenv').config({ path: './.env' });
//API
const stripe=require("stripe")('sk_test_51KM...zIP');
//App config
const app=express();
var admin = require("firebase-admin");
var serviceAccount = require("./serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
//middlewares
app.use(cors({origin: true}));
app.use(express.json());
async function createCheckoutSession(req, res) {
const domainUrl = process.env.WEB_APP_URL;
const { line_items, customer_email } = req.body;
// check req body has line items and email
if (!line_items || !customer_email) {
return res.status(400).json({ error: 'missing required session parameters' });
}
let session;
try {
session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
mode: 'payment',
line_items,
customer_email,
success_url: `${domainUrl}/success?session_id={CHECKOUT_SESSION_ID}`,
// success_url: '/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: `${domainUrl}/canceled`,
shipping_address_collection: { allowed_countries: ['GB', 'US'] }
});
res.status(200).json({ sessionId: session.id, });
} catch (error) {
console.log(error);
res.status(400).json({ error: 'an error occured, unable to create session'});
}}
app.get('/',(req, res)=>res.send('Hello World!'));
app.post('/create-checkout-session', createCheckoutSession);
exports.api=functions.https.onRequest(app);
src/strip-checkout.js
import React, { useContext, useState,useEffect } from 'react';
import { useStripe } from '#stripe/react-stripe-js';
import CheckoutProduct from '../CheckoutProduct';
import { useStateValue } from '../StateProvider';
import { db } from '../firebase';
import { fetchFromAPI } from '../helpers';
import "./stripe-checkout.css";
import { useHistory } from 'react-router-dom/cjs/react-router-dom.min';
const StripeCheckout = () => {
const history=useHistory();
const [{basket, user},dispatch]=useStateValue();
const [email, setEmail] = useState('');
const [processing,setProcessing]=useState("");
const stripe = useStripe();
const handleGuestCheckout = async (e) => {
e.preventDefault();
setProcessing(true);
const line_items = basket?.map(item => {
return {
quantity: 1,
price_data: {
currency: 'usd',
unit_amount: item.price*100, // amount is in cents
product_data: {
name: item.title,
description: item.material
images: [item.image[0]],
}}}})
const response = await fetchFromAPI('create-checkout-session', {
body: { line_items, customer_email: user.email },
})
const { sessionId } = response;
const { error } = await stripe.redirectToCheckout({
sessionId
}).then(()=>{
db
.collection('users')
.doc(user?.uid)
.collection('orders')
.doc()
.set({
basket: basket,
})
});
console.log(sessionId);
if (error) {
console.log(error);
}}
return (
<form onSubmit={handleGuestCheckout}>
<div className='submit-btn'>
<button type='submit' >
<span>{processing ?<p>Processing</p>:
"Proceed to Checkout"}</span>
</button>
</div>
</form>
);
}
export default StripeCheckout;
Since there is no shown any error or warning, how to push the data to firestore after stripe checkout session complete in this case?
Stripe Checkout returns to either a success- or a cancel-URL.
It does not send data to both URLs, except the CHECKOUT_SESSION_ID you may add to it when defining these URLs.
The usual way to get data from Stripe Checkout is to use a Firebase Function Webhook. This Webhook is called by Stripe if a transactions is done, or upon failure etc. This Webhook stores the data in Firebase. I had the same problem and found no other, proper solution than using a Webhook.
https://stripe.com/docs/webhooks
There is also a Firebase Extension providing support for Stripe, including the Webhook. Basically. it will add some collections to Firestore, which you then can query.
https://firebase.google.com/products/extensions/stripe-firestore-stripe-payments

Mocking an axios post using React Testing Library / Jest

I am struggling with figuring out how to correctly mock this Axios post in my React test. It seems like the documentation and tutorials out there are all over the place in terms of how they do it, no strong consensus or best practice.
So here is my Test:
BatchList.test.js
import React from 'react';
import { setupWorker, rest } from 'msw';
import {
render,
screen,
within,
fireEvent,
waitFor,
} from '#testing-library/react';
import '#testing-library/jest-dom';
import { Provider as AlertProvider } from 'react-alert';
import AlertMUITemplate from 'react-alert-template-mui';
import BatchList from './BatchList';
import mockedAxios from 'axios';
// ... other tests that succeed here ...
// only test that fails here
test('clicking yes and passing back only 2 valid batch numbers should show 2 valid, 2 invalid batch numbers in list', async () => {
// renderBatchListWithNumbers();
const batchList = render(
<AlertProvider template={AlertMUITemplate}>
<BatchList
formulaID={''}
orderID={''}
itemID={''}
formulaResults={[]}
batchNumbers={[
{ BATCH_ID: '987654', ID: '78' },
{ BATCH_ID: '261010', ID: '79' },
{ BATCH_ID: '301967', ID: '80' },
{ BATCH_ID: '445566', ID: '81' },
]}
setBatchNumbers={mockedEmptyFn}
batchNumber={'5'}
setBatchNumber={mockedEmptyFn}
/>
</AlertProvider>
);
const completeButton = batchList.getByText('Complete');
fireEvent.click(completeButton);
const yesButton = batchList.getByText('Yes');
expect(yesButton).toBeInTheDocument();
fireEvent.click(yesButton);
// now we need to figure out mocking the API calls!!!
const data = {
msg: 'fail',
invalidBatchNumbers: ['987654', '445566'],
};
mockedAxios.post.mockResolvedValueOnce({
data: data,
});
await waitFor(() => {
expect(batchList.getByText('987654')).toHaveClass('invalid');
expect(batchList.getByText('261010')).toHaveClass('valid');
});
});
And here is my axios.js inside my __mocks__ folder:
export default {
post: jest.fn().mockResolvedValue(),
};
So the error I am getting in my test is this: thrown: "Unable to complete all batches - TypeError: (0 , _axios.default) is not a function"
And that error message string is coming from my client side API call here:
export const completeAllBatches = async (
orderID,
itemID,
batchNumbers
) => {
const completeAllBatchesURL =
serverURL + `:${port}/completeAllBatches`;
try {
return await axios({
method: 'post',
url: completeAllBatchesURL,
timeout: shortTimeout,
data: {
orderID,
itemID,
batchNumbers,
},
})
.then((res) => {
return res;
})
.catch((e) => {
return Promise.reject(
'1: Unable to complete all batches - ' + e
);
});
} catch (e) {
return Promise.reject('2: Unable to complete all batches - ' + e);
}
};
I solved a similar problem, perhaps this is useful?
I was following this tutorial.
Why this break?
I think the problem is that typescript doesn't know how to handle some of the complexities of an axios.post.
Instead of importing default as mocked, I imported as normal
import axios from 'axios'
jest.mock('axios');
Then I am a bit more explicit with the mock
const mockedAxios = axios as jest.Mocked<typeof axios>;
let payload:object = {}
const mockedPost = mockedAxios.post.mockReturnValueOnce(payload);
//I highly recommend separating out api client like in the tutorial, then call that api function...
const data = await postBatch();
expect(axios.post).toHaveBeenCalled();
LMK if this works for you, I'm still playing with my React testing patterns :)

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
}
}
}

Resources