Stripe processed, now what? - stripe-payments

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.

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 "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 No such customer (Express Connected Account)

I'm trying to allow users to create one-off invoices after they have onboarded to the platform via an Express account.
I have a Node route set up to create and send an invoice.
I'm getting the error
StripeInvalidRequestError: No such customer: 'cus_I3Xra0juO9x2Iu'
However the customer does exist in the user's connect account.
The route is below
app.post('/api/new_invoice', async (req, res) => {
try {
const { customer, deliverables, amount, payableBy } = req.body
const product = await stripe.products.create({
name: deliverables,
})
const price = await stripe.prices.create({
unit_amount: amount,
currency: 'aud',
product: product.id,
})
const invoiceItem = await stripe.invoiceItems.create({
customer,
price: price.id,
})
const stripeInvoice = await stripe.invoices.create(
{
customer,
collection_method: 'send_invoice',
days_until_due: 30,
invoiceItem,
},
{
stripeAccount: req.user.stripeAcct,
}
)
const invoice = await new Invoice({
customer,
deliverables,
amount,
paid: false,
issueDate: Date.now(),
payableBy,
_user: req.user.id,
}).save()
res.send(invoice.data)
console.log(invoiceItem, stripeInvoice)
} catch (err) {
console.log(err)
res.status(400)
res.send({ error: err })
return
}
})
From what I understand adding a second object to stripe.invoices.create with the connect account's id then it should look into their Stripe account for the customer?
Thanks in advance
When making a call on behalf of a connected account, you need to set the Stripe-Account header as their account id as documented here. This header has to be set on every API request you make on behalf of that connected account.
In your code, you are only setting the header on the Invoice Create API request but not the other one(s) such as the Invoice Item Create. Make sure to pass it everywhere and your code will start working.

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