Stripe checkout failing to load from firebase cloud function (Fixed) - node.js

Edit: Update on how I got it working at bottom.
I have a cloud function which creates a stripe checkout session when it is called from my React application.
When it is called I get the following error in the debugger: Failed to load resource: net::ERR_FAILED
Here is the cloud function:
const functions = require('firebase-functions');
const express = require('express');
const app = express();
const cors = require('cors');
const stripe = require('stripe')((private key removed for this question))
app.use(cors());
exports.createCheckoutSession = functions.https.onRequest(async (req, res) => {
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [
{
price_data: {
currency: 'usd',
product_data: {
name: 'T-shirt',
},
unit_amount: 2000,
},
quantity: 1,
},
],
mode: 'payment',
success_url: 'https://example.com/success.html',
cancel_url: 'https://example.com/cancel.html',
});
res.json({id: session.id});
});
app.listen(4242, () => console.log(`Listening on port ${4242}!`));
I believe the cloud function is working correctly as when I run:
curl -X POST -is "https://us-central1-project-name.cloudfunctions.net/createCheckoutSession" -d ""
I get a success message.
And here is where it is called on the front-end:
const response = await fetch("https://us-central1-project-name.cloudfunctions.net/createCheckoutSession", {
method: "POST",
});
const session = await response.json();
// When the customer clicks on the button, redirect them to Checkout.
const result = await stripe.redirectToCheckout({
sessionId: session.id,
});
if (result.error) {
// If `redirectToCheckout` fails due to a browser or network
// error, display the localized error message to your customer
// using `result.error.message`.
alert(result.error.message);
}
};
I am getting the error on the fetch call.
Any help would be appreciated.
Update:
I managed to get it to work by referencing this post:
Access-Control-Allow-Origin not working Google Cloud Functions GCF
The issue was with CORS. I needed to add in these headers to the function method:
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Methods', 'GET, POST');
res.set('Access-Control-Allow-Headers', 'Content-Type');
res.set('Access-Control-Max-Age', '3600');

You need to create something called a paymentIntent from backend and then pass the paymentIntent Id to your frontend, here is an example const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); const paymentIntent = await stripe.paymentIntents.create({ amount: 2000, currency: 'usd', payment_method_types: ['card'], }); why are you using a cloud function using react and simple nodes app will be a lot better (because you will need to set up webhooks for stripe to hit your api for certain transaction) and safer too, I have integrated stripe this way into my react app and it works fine I can share some code snippets, the authentication and all the important stuff will be done in your nodejs app or what ever server-side you are using the front end will only handle the results and you will be able to show errors, add retry logic etc
React Stripe Doc

Related

Can't access the stripe check-out page

I have written my server-side code by nodejs .
I have implemented a route to create a stripe checkout session, which contains a bunch of data about the object that is going to be purchased.
router.get(
'/checkout-session/:productId',
catchAsync(async (req, res, next) => {
// 1) Get the currently ordered product
const product = await Product.findById(req.params.productId);
// 2) Create checkout session
const session = await stripe.checkout.sessions.create({
expand: ['line_items'],
payment_method_types: ['card'],
success_url: `https://nasim67reja.github.io/CareoCIty-ecommerce/`,
cancel_url: `https://nasim67reja.github.io/CareoCIty-ecommerce/#/${product.categories}`,
customer_email: req.user.email,
client_reference_id: req.params.productId,
line_items: [
{
price_data: {
currency: 'usd',
unit_amount: product.price * 100,
product_data: {
name: `${product.name} `,
description: product.summary,
images: [
`https://e-commerceapi.up.railway.app/Products/${product.categories}/${product.images[0]}`,
],
},
},
quantity: 1,
},
],
mode: 'payment',
});
// 3) Create session as response
res.status(200).json({
status: 'success',
session,
});
})
);
Then on the front-end (ReactJs), I created a function to request the checkout session from the server once the user clicks the buy button.
So once I hit that endpoint that I created on the backend, that will make a session and send it back to the client. Then based on that session, the stripe will automatically create a checkout page for us. where the user can then input
here is my client side code:
const buyProduct = async () => {
try {
const session = await axios.get(
`${URL}/api/v1/orders//checkout-session/${product}`
);
window.location.replace(session.data.session.url);
} catch (error) {
console.log(`error: `, error.response);
}
};
All was okay when I tried on the local server.But when I hosted my backend on the Railway, then I got an error
I have also tried to put the stripe public & private key on the authorization header. but still got that error.I searched a lot but didn't find any solution. I will be very happy to get help from you
Looks like your server on Railway doesn't have the correct secret key. It depends on how you initialize stripe in NodeJS, but normally if you read it from an environment variable, you want to make sure Railway also sets that value correctly.
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

PayPal + Node.js - Getting "Order Already Captured" if though I logged with another User and cleared browser Cache

I'm developing a website with Nuxt + Node + Express and I'm trying to implement PayPal.
I'm using PayPal Node SDK and I created the files just as described in their Github page.
I'm developing in my pc (so, localhost..) and using PayPal Sandbox.
The problem is: it works perfectly for the first time. I click the 'Buy' button in the frontend, I'm redirected to PayPal payment page, I pay using Sandbox account, then I'm redirect to my website again with the TOKEN in the URL. The output from the call to the backend API is 'COMPLETED'.
BUT, if I login with another user, clear browser cache or even change the browser, and try to BUY again it says that: "Order already captured". After a few hours I can buy again. The only thing that works is RESTARTING the server. I've checked if there's some cookies in the server with cookies-parser but there isn't.
Call anyone help me understand why it happens?
Here is the files/code I used:
Created the paypal_controller.js with:
const paypal = require("#paypal/checkout-server-sdk");
let clientId = "<<MY CLIENT ID>>";
let clientSecret = "<<MY CLIENT SECRET>>";
let environment = new paypal.core.SandboxEnvironment(clientId, clientSecret);
let client = new paypal.core.PayPalHttpClient(environment);
let request = new paypal.orders.OrdersCreateRequest();
request.requestBody({
intent: "CAPTURE",
purchase_units: [
{
description: "Subscription",
amount: {
currency_code: "USD",
value: "200.00",
},
},
],
application_context: {
return_url: "http://localhost:3000/user/dashboard",
cancel_url: "http://localhost:3000",
},
});
const createOrder = async (req, res) => {
console.log("creating order");
await client
.execute(request)
.then((data) => {
res.json(data);
})
.catch((err) => {
console.log(err);
res.json(err);
});
};
Created the route in the api.js:
const express = require("express");
const {
createOrder,
captureOrder,
getOrderDetail,
} = require("../controllers/paypal_controller");
const router = express.Router();
router.get("/payment/createorder", createOrder)
module.exports = router;
and in the frontend I call:
await this.$axios.$get('http://localhost:9000/api/payment/createorder')
.then((det) => {
window.location.href = det['result']['links'][1]['href']
})
On boot of the server the following gets initialised, so is the same order for every user:
let request = new paypal.orders.OrdersCreateRequest();
request.requestBody({
intent: "CAPTURE",
purchase_units: [
{
description: "Subscription",
amount: {
currency_code: "USD",
value: "200.00",
},
},
],
application_context: {
return_url: "http://localhost:3000/user/dashboard",
cancel_url: "http://localhost:3000",
},
});
You want to place that code in the route, not outside
const createOrder = async (req, res) => {
console.log("creating order");
// place aforementioned code here, after validation!
await client
.execute(request)
.then((data) => {

Stripe checkout session not working nodejs

I'm trying to implement a stripe checkout with NodeJs and React and it seems to get this error from the console log :
Access to XMLHttpRequest at 'https://checkout.stripe.com/pay/cs_test_a1y3gwvPAqpqhArQ9B83g3Du9EvF87HVcxzcx5Opt6o1rKinGEQViuXIq' (redirected from 'http://localhost:5000/api/account/subscriptioncheckoutsession') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Here is what I have done so far:
env file
REACT_APP_FETCH_URL=http://localhost:5000
server.js
var whitelist = ['http://localhost:3000', 'http://localhost:5000'];
app.use(cors({ credentials: true, origin: whitelist }));
Route api
const YOUR_DOMAIN = 'http://localhost:5000';
router.post('/account/subscriptioncheckoutsession', async (req, res) => {
const session = await Stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [
{
price_data: {
currency: 'usd',
product_data: {
name: 'Product Subsciption',
images: ['https://i.imgur.com/EHyR2nP.png'],
},
unit_amount: 100,
},
quantity: 1,
},
],
mode: 'payment',
success_url: `${YOUR_DOMAIN}/success.html`,
cancel_url: `${YOUR_DOMAIN}/cancel.html`,
});
res.redirect(303, session.url)
})
Client
const handleCheckout = async (e) => {
e.preventDefault()
const response = await Axios.post(process.env.REACT_APP_FETCH_URL + '/api/account/subscriptioncheckoutsession')
}
<div className="form-group">
<button type="submit" className="btn btn-primary" onClick={handleCheckout}>
Subscribe or Renew Subscription</button>
</div>
What have I missed? Many thanks in advance and greatly appreciate any helps. Thanks again.
If you're using an XMLHTTPRequest(e.g., Axios.post) to get the URL for the CheckoutSession, it's not possible to redirect directly from the sever that way! That approach only works if you're doing what is described in Stripe's docs — calling that backend /subscriptioncheckoutsession route from the action of a <form> : https://stripe.com/docs/billing/subscriptions/checkout#create-session
So you could change your code to just have the form action be that route, and get rid of the onClick function and that usage of Axios entirely!
If you don't want to use a form submission and instead want to use an Ajax approach like Axios, what I'd suggest instead is your backend should just return the URL as part of the response body (e.g. res.json({"url":session.url}; instead of res.redirect) and do the redirect in JavaScript by setting window.location (e.g. let response = await axios.post(...); window.location = response.data.url)
I was using FastAPI and React to do the same, and ended up using the form action type. I tried using Fetch, but you cannot use the window.location = response.url method, or at least in TypeScript.
These are the parameter types in FastAPI and pip install python-multipart
session_id: str = Form(...)
price_id: str = Form(...)

Stripe Webhook Returning Error 401 and Success

I have a checkout session:
app.post("/create-checkout-session", async (req, res) => {
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
line_items: converted_items,
mode: "payment",
success_url: process.env.URL + "/order-success.html",
cancel_url: process.env.URL + `/order-page.html`,
billing_address_collection: 'required',
});
res.json({ id: session.id, order: req.body });
});
And I would like to set up a webhook so that after a payment is successfully made, it collects the data from the customer (name, address, products purchased)
I copied this right from the webhook docs:
const fulfillOrder = (session) => {
// TODO: fill me in
console.log("Fulfilling order", session);
}
app.post('/webhook', bodyParser.raw({ type: 'application/json' }), (request, response) => {
const payload = request.body;
const sig = request.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(payload, sig, endpointSecret);
} catch (err) {
return response.status(400).send(`Webhook Error: ${err.message}`);
}
console.log(event.type)
// Handle the checkout.session.completed event
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
console.log("did order")
// Fulfill the purchase...
fulfillOrder(session);
}
response.status(200);
});
How I am setting it up is I am first running my server on localhost and then I am running the testing webhook command from the terminal
stripe listen --forward-to localhost:3000/webhook --events=checkout.session.completed
When I make a payment, it shows me these messages in the terminal:
It looks like you've made some modifications to that webhook handler code from the docs. If you're using Checkout and following the docs, what made you switch the code to look for charge.succeeded?
You should add some logging to figure out why your server is returning 400, but most likely the verification is failing in constructEvent. The snippet you've shared doesn't show you setting endpointSecret, but you need to update the value using the secret shown to you when you run stripe listen.
You should review the Checkout fulfillment docs and focus on checkout.session.completed events. You can modify your CLI command to listen to online these events:
stripe listen --forward-to localhost:3000/webhook --events=checkout.session.completed
then your handler would look like the docs and you can implement fulfillOrder to meet your needs. If you want the details you listed, then you will need to retrieve the session with expansion to include the line items and payment details:
const session = await stripe.checkout.sessions.retrieve(
'cs_test_123',
{
expand: ['line_items', 'payment_intent.payment_method']
},
);
Then session.line_items will be what you created and the name, address and email will be on session.payment_intent.payment_method.billing_details.
Updated to suggest removing signature verification temporarily to diagnose:
The 400 errors suggest your server is returning an error, most likely as a result of signature verification. I recommend testing a basic endpoint without verification following this example, to ensure everything else is working as intended. When that is settled, re-adding signature verification is strongly recommended.

Stripe processed, now what?

I'm trying to build a serverless NuxtJS app, utilizing firebase for authentication, netlify for deployment (and functions) and stripe for payment.
This whole payment-process and serverless functions on netlify is all new to me, so this might be a nooby question.
I've followed serveral docs and guides, and accomplished an app with firebase authentication, netlify deployment and serverless functions making me able to process a stripe payment - now I just cant figure out the next step. My stripe success_url leads to a /pages/succes/index.js route containing a success message -> though here I'd need some data response from Stripe, making me able to present the purchased item and also attaching the product id as a "bought-product" entry on the user object in firebase (the products will essentially be an upgrade to the user profile).
Click "buy product" function
async buyProduct(sku, qty) {
const data = {
sku: sku,
quantity: qty,
};
const response = await fetch('/.netlify/functions/create-checkout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
}).then((res) => res.json());
console.log(response);
const stripe = await loadStripe(response.publishableKey);
const { error } = await stripe.redirectToCheckout({
sessionId: response.sessionId,
});
if (error) {
console.error(error);
}
}
create-checkout Netlify function
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const inventory = require('./data/products.json');
exports.handler = async (event) => {
const { sku, quantity } = JSON.parse(event.body);
const product = inventory.find((p) => p.sku === sku);
const validatedQuantity = quantity > 0 && quantity < 11 ? quantity : 1;
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
billing_address_collection: 'auto',
shipping_address_collection: {
allowed_countries: ['US', 'CA'],
},
success_url: `${process.env.URL}/success`,
cancel_url: process.env.URL,
line_items: [
{
name: product.name,
description: product.description,
images: [product.image],
amount: product.amount,
currency: product.currency,
quantity: validatedQuantity,
},
],
});
return {
statusCode: 200,
body: JSON.stringify({
sessionId: session.id,
publishableKey: process.env.STRIPE_PUBLISHABLE_KEY,
}),
};
};
Please let me know if you need more information, or something doesnt make sense!
tldr; I've processed a Stripe payment in a serverless app using Netlify function, and would like on the success-page to be able to access the bought item and user information.
When you create your Checkout Session and define your success_url, you can append session_id={CHECKOUT_SESSION_ID} as a template where Stripe will auto-fill in the session ID. See https://stripe.com/docs/payments/checkout/accept-a-payment#create-checkout-session
In your case you'd do:
success_url: `${process.env.URL}/success?session_id={CHECKOUT_SESION_ID}`,
Then when your user is redirected to your success URL you can retrieve the session with the session ID and do any purchase fulfilment.

Resources