Stripe "Missing required param: line_items[0][currency]." Node js - 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.

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

Checkout flow: how should I process a new order?

I'm trying to build an App which allows the customer to download a custom document after his order (so, the product I'm selling it's a digital custom document in pdf).
I'm trying to build that using React for the frontend, Node and Express for the backend, and PayPal Express Checkout (full-stack implementation) as payment.
What is not clear to me is what steps I should take to process an order.
I'm a this point:
Once customer clicks on PayPal button on client side it starts a call on http://localhost/api/paypal/orders endpoint which create the order on the Paypal side and return the PayPal order ID (e.g. id12345)
After the customer approves the payment on the PayPal popup, the client starts a call on http://localhost/api/paypal/orders/id12345/capture endpoint
then? What other endpoints should I crete on the server and what they should return? Maybe... 1 for save the actual store order on my MongoDB and the transaction details, 1 for create the invoice for the order, 1 for allowing the product download ???
Could you please clarify what steps I need to take, what are the endpoints I should create, and what each endpoints should return?
Maybe something like this?
You're trying to do too many things in too many routes. just create a route called something like process-order then you could have an async controller of that route which would contain separate functions for
Storing the order details in mongodb.
Create an invoice
Send a token that allows access to download page.
first , just await the function to complete then call the next.
exports.processOrder = async (req, res, next) => {
try {
const newOrder = await Order.create({
order: req.params.id,
details: req.body.details
})
const createInvoice = () => {
return newOrder._id;
}
const invoice = createInvoice();
const token = jwt.sign(
{
invoiceId = newOrder._id,
},
process.env.JWT_SECRET,
{
expiresIn: '24h'
}
);
return res.status(200).json({status: 'success', message: 'you have 24 hours to download your digital goods', token})
} catch (error) {
res.status(500).send(error)
}
}
This is a very basic idea, but basically store all of your info in one controller, then send a token with the id of the invoice, then when they go to download the book you would have a route where you verify the token. If it succeeds then the book is sent. like this:
app.use(
'/book-download',
expressJwt({ secret: process.env.JWT_SECRET, algorithms: ['HS256'] })
);
app.get('/book-download/success', async (req, res) => {
try{
const invoiceId = req.user.invoiceId;
const invoice = await Invoice.find({_id: invoiceId})
if (invoice) {
return res.status(200).json({status: 'success', message: 'congratulations on your new download', data: {E-book})
} else {
return.res.status(404).json({status: 'fail', message: 'could not find an invoice with that ID'})
} catch (error) {
return res.send(error)
}
});
You can choose to send the pdf via express, or you can allow them to enter a certain part of the website if the return is valid. There you go, that's an idea.
In Express check out, you need to define success / cancel handler
Ref here : https://www.section.io/engineering-education/nodejs-paypal-checkout/
In the success handler, based on the returned URI => you will get a Payer ID and payment ID. Based on that retrieve the transactio information (that internally contains yours order ID). Update your Order id as scucess / cancelled based on the paypal response.
app.get('/success', (req, res) => {
const payerId = req.query.PayerID;
const paymentId = req.query.paymentId;
const execute_payment_json = {
"payer_id": payerId,
"transactions": [{
"amount": {
"currency": "USD",
"total": "25.00"
}
}]
};
// Obtains the transaction details from paypal
paypal.payment.execute(paymentId, execute_payment_json, function (error, payment) {
//When error occurs when due to non-existent transaction, throw an error else log the transaction details in the console then send a Success string reposponse to the user.
if (error) {
console.log(error.response);
throw error;
} else {
console.log(JSON.stringify(payment));
res.send('Success');
}
});
});

How to access clientReferenceId on success using Stripe Checkout

I'm new to Stripe and using what appears to be a very simplistic setup to accept payments using Stripe Checkout. I'm passing a clientReferenceId which I need to access on the success page. Is this possible? Here's the code which is called on the Checkout button:
const stripeCheckout = async () => {
setLoading(true)
const stripe = await stripePromise;
const { error } = await stripe.redirectToCheckout({
lineItems: [
{
price: 'price_xxxxx',
quantity:1,
}
],
mode:"payment",
cancelUrl: window.location.origin,
successUrl: `${window.location.origin}/payment-complete`,
clientReferenceId: 'abc',
});
if (error) {
console.log("Error # Checkout: ",error)
setLoading(false)
}
}
Thank you for any help.
You can access the client_reference_id by retrieving the Checkout Session on the success page: https://stripe.com/docs/payments/checkout/custom-success-page
However a much easier solution would be to just encode your variable directly into the success URL:
successURL: `${window.location.origin}/payment-complete?id=abc`,
Then on your success page you can just access the query string variables.

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