Can't access the stripe check-out page - node.js

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);

Related

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 failing to load from firebase cloud function (Fixed)

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

Stripe "Missing required param: line_items[0][currency]." Node js

I'm creating subscription on node js backend. It had been working good but today I got this error. I didn't made any changes in code - it just starts returning this error.
backend code:
app.post('/api/subscription', async (req, res) => {
const { priceId } = req.body;
try {
const session = await stripe.checkout.sessions.create({
mode: "subscription",
payment_method_types: ["card"],
line_items: [
{
price: priceId,
// For metered billing, do not pass quantity
quantity: 1,
},
],
// {CHECKOUT_SESSION_ID} is a string literal; do not change it!
// the actual Session ID is returned in the query parameter when your customer
// is redirected to the success page.
success_url: 'https://someurl',
cancel_url: 'https://someurl',
});
res.send({
sessionId: session.id,
});
} catch (e) {
console.log(e)
res.status(400);
return res.send({
error: {
message: e.message,
}
});
}
})
from client I'm sending
fetch("http://localhost:8000/api/subscription", {
method: "POST",
body: JSON.stringify({ priceId }),
});
I've taken this code from official stripe example here https://stripe.com/docs/billing/subscriptions/checkout
And as I said it works fine, and now I've tested it on two different stripe accounts and getting the same error. It looks like something changed on stripe but not in their documentation
If priceId is undefined/null it won't be sent in the request. If the Price isn't present the API assumes you're trying to specify information about a line item without using a Price, and one of the first checks it performs is for a valid currency (which you don't have), which results in the Missing required param: line_items[0][currency]. error.
To fix the issue you'll need to figure out why priceId isn't being populated as expected, and you may also want to add a check to make sure priceId is valid before proceeding to the Checkout Session creation step.

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.

Why are the Stripe Payments Incomplete?

My Stripe payments show up on my dashboard, but they have an 'Incomplete' status, and hovering over shows the tip, "The customer has not entered their payment method." I thought I accounted for payment method in my sessions.create() method.
My Angular component creates a StripeCheckout and sends the session data to my API. API then includes it in the response to the browser (my first attempt was to use this sessionId to redirect to checkout, but I chose this because it was smoother).
Angular/TS StripeCheckout and handler:
let headers = new Headers();
headers.append("Content-Type", "application/json");
headers.append(
"authentication",
`Bearer ${this.authServ.getAuthenticatedUserToken()}`
);
this.handler = StripeCheckout.configure({
key: environment.stripe_public_key,
locale: "auto",
source: async source => {
this.isLoading = true;
this.amount = this.amount * 100;
this.sessionURL = `${this.stringValServ.getCheckoutSessionStripeURL}/${this.activeUser.id}/${this.amount}`;
const session = this.http
.get(this.sessionURL, {
headers: new HttpHeaders({
"Content-Type": "application/json",
authentication: `Bearer ${this.authServ.getAuthenticatedUserToken()}`
})
})
.subscribe(res => {
console.log(res);
});
}
});
//so that the charge is depicted correctly on the front end
this.amount = this.amount / 100;
}
async checkout(e) {
this.stripeAmountEvent.emit(this.amount);
this.handler.open({
name: "[REDACTED]",
amount: this.amount * 100,
description: this.description,
email: this.activeUser.email
});
e.preventDefault();
}
NodeJS API picks it up
exports.getCheckoutSession = catchAsync(async (req, res, next) => {
const currentUserId = req.params.userId;
const paymentAmount = req.params.amount;
const user = await User.findById(currentUserId);
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
success_url: `${process.env.CLIENT_URL}`,
cancel_url: `${process.env.CLIENT_URL}`,
customer_email: user.email,
client_reference_id: user.userId,
line_items: [
{
name: `Donation from ${user.userName}`,
description: '[REDACTED]',
amount: paymentAmount,
currency: 'usd',
quantity: 1,
customer: user.userId
}
]
});
const newPayment = await Payment.create({
amount: paymentAmount,
createdAt: Date.now(),
createdById: user._id
});
res.status(200).send({
status: 'success',
session
});
});
The payment gets created in my db, and the payment shows up on my Stripe dashboard. The payments show as 'Incomplete' when I expected it to charge the card.
Thanks in advance.
The latest version of Checkout lets you accept payments directly on a Stripe-hosted payment page. This includes collecting card details, showing what's in your cart and ensuring the customer pays before being redirected to your website.
At the moment, though, your code is mixing multiple products in one place incorrectly. The code you have client-side uses Legacy Checkout. This is an older version of Stripe's product that you could use to collect card details securely. This is not something you should use anymore.
Then server-side, you are using the newer version of Checkout by creating a Session. This part is correct but you never seem to be using it.
Instead, you need to create the Session server-side, and then client-side you only need to redirect to Stripe using redirectToCheckout as documented here.

Resources