How to send metadata in stripe during checkout process? - javascript

const combineData = function (prod, pot, data) {
const newData = [...prod, ...pot];
const finalCheckout = [];
for (let i = 0; i < newData.length; i++) {
finalCheckout.push({
name: newData[i].plantName || newData[i].potName,
images: [newData[i].images[0]],
amount: newData[i].price * 100,
currency: "inr",
quantity: data[i].quantity,
metadata: { id: String(newData[i]._id) },// **Mongoose ID**
});
}
return finalCheckout;
};
exports.getCheckOutSession = catchAsync(async (req, res, next) => {
const [product, pot] = filterVal(req.body.product);
const userId = req.user._id;
const products = await ProductModel.find({ _id: { $in: product } });
const pots = await PotModel.find({ _id: { $in: pot } });
const newData = combineData(products, pots, req.body.product);
// 2. create the checkout session
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
success_url: `${req.protocol}://${req.get("host")}/?alert=booking`,
cancel_url: `${req.protocol}://${req.get("host")}/products/`,
customer_email: req.user.email,
client_reference_id: userId,
line_items: newData,
});
res.status(200).json({
status: "success",
url: session.url,
});
});
I'm build an Ecommerce website, when user makes a payment I want to send the product id's stored in my mongodb database as metadata in stripe. But i'm getting an error how to solve this.
Error:parameter_unknown - line_items[0][metadata]
Received unknown parameter: line_items[0][metadata]
Stripe is rejecting the mongodb id's has metadata
"metadata": {
"id": "61e40a5a7d83539092e7a92f"
}
NOTE: I'm sending all the successfull data like price, name,images amount,quanity in the above combineData function.
I have tested the above code without metadata key. It works fine my payment is registered and webhook event is registered in stripe.
When i use metadata keyword the above error occurs in stripe.

You need to create product + price first.
After informing stripe of the line items, the session token will be returned to client side, and users will then be redirected to stripe's payment page.
Stripe will be able to know what items (e.g image, product name, product description) to display to the user, through the line items you submitted while creating the session.
If you just pass a MongoDB _id, stripe will not know what items to display since they have no info of the product.
const session = await stripe.checkout.sessions.create({
customer: customerId,
billing_address_collection: 'auto',
payment_method_types: ['card'],
line_items: [
{
price: STRIPE_PRODUCT_PRICE, //price of created product
// For metered billing, do not pass quantity
quantity: 1,
},
],
// success_url: `${YOUR_DOMAIN}?success=true&session_id={CHECKOUT_SESSION_ID}`,
// cancel_url: `${YOUR_DOMAIN}?canceled=true`,
});
Example of how strip product and price id looks like. A single product can have many different prices.
STRIPE_PRODUCT=prod_I7a38cue83jd
STRIPE_PRICE=price_1HXL0wBXeaFPR83jhdue883
These references will be useful to you.
Creating a product: https://stripe.com/docs/api/products/create
Creating a price: https://stripe.com/docs/api/prices/create
I will normally store the product id and price id as stripe_product_id and stripe_product_price in the local MongoDB database so you can use during checkout.
So after you receive the order, you can create a local order first, with the products and quantity, then...
const line_items = ordered_products.map(product => ({ price: product.stripe_product_price, quantity: product.quantity }))
const session = await stripe.checkout.sessions.create({
....
line_items,
...
})
Advanced method - creating product and price on the go.
This is more tedious because you need to do more checks.
You can refer to creating line_items in the link below from stripe docs.
https://stripe.com/docs/api/checkout/sessions/object

Related

Issue with Stripe Payment Sheet using firebase, cloud functions and stripe. "Unexpected Character (at line 2, character 1) <html><head>

As the title suggests, I am trying to implement Stripe into my flutter app using the stripe extension for Firebase and using Javascript Firebase Cloud Functions for the server side. I believe the issue is on the server side when I try to create a customer and create a payment intent.
The server side code is here:
const functions = require("firebase-functions");
const stripe = require("stripe")("my test secret key"); // this works fine for the other stripe functions I am calling
exports.stripePaymentIntentRequest = functions.https.onRequest(
async (req, res) => {
const {email, amount} = req.body;
try {
let customerId;
// Gets the customer who's email id matches the one sent by the client
const customerList = await stripe.customers.list({
email: email,
limit: 1,
});
// Checks the if the customer exists, if not creates a new customer
if (customerList.data.length !== 0) {
customerId = customerList.data[0].id;
} else {
const customer = await stripe.customers.create({
email: email,
});
customerId = customer.data.id;
}
// Creates a temporary secret key linked with the customer
const ephemeralKey = await stripe.ephemeralKeys.create(
{customer: customerId},
{apiVersion: "2022-11-15"},
);
// Creates a new payment intent with amount passed in from the client
const paymentIntent = await stripe.paymentIntents.create({
amount: parseInt(amount),
currency: "gbp",
customer: customerId,
});
res.status(200).send({
paymentIntent: paymentIntent.client_secret,
ephemeralKey: ephemeralKey.secret,
customer: customerId,
success: true,
});
} catch (error) {
res.status(404).send({success: false, error: error.message});
}
},
);
Then my client-side code is:
try {
// 1. create payment intent on the server
final response = await http.post(
Uri.parse(
'https://us-central1-clublink-1.cloudfunctions.net/stripePaymentIntentRequest'),
headers: {"Content-Type": "application/json"},
body: json.encode({
'email': email,
'amount': amount.toString(),
}),
);
final jsonResponse = json.decode(response.body);
if (jsonResponse['error'] != null) {
throw Exception(jsonResponse['error']);
}
log(jsonResponse.toString());
//2. initialize the payment sheet
await Stripe.instance.initPaymentSheet(
paymentSheetParameters: SetupPaymentSheetParameters(
paymentIntentClientSecret: jsonResponse['paymentIntent'],
merchantDisplayName: 'Clublink UK',
customerId: jsonResponse['customer'],
customerEphemeralKeySecret: jsonResponse['ephemeralKey'],
style: ThemeMode.dark,
),
);
await Stripe.instance.presentPaymentSheet();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Payment completed!')),
);
} catch (e) {
if (e is StripeException) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error from Stripe: ${e.error.localizedMessage}'),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
}
}
}
I basically copied the flutter_stripe documentation to create the payment sheet with the necessary changes. Any help would be greatly appreciated!
Ok so I found what worked! I was being given a 403 status error with reason "forbidden". This meant I had to go to the google cloud console and update the permissions in the cloud functions tab.

Send data to backend JS, after Stripe confirmation URL

I have a problem to send data, in this case on the delivery address, the products purchased, the date of purchase, the user who made this purchase.
I have a problem with when to send this data and how?
I use the stripe api to realize the payment, everything works the user is redirected on the stripe link with his session, and pay according to the items in his cart. But my checkout data is lost.
The ideal would be that when stripe confirms the purchase and thus redirects on the url of success, which is the case, and well the quoted data are transferred.
So I have this code:
HTML CHECKOUT
<div class="form">
<p class="text">Adresse de livraison</p>
<input type="text" id="address" placeholder="adresse">
<div class="two-input-container">
<input type="text" id="city" placeholder="ville">
<input type="number" id="state" placeholder="code postale">
</div>
<input type="text" id="landmark" placeholder="informations complémentaire">
</div>
CHECKOUT JS
window.onload = () => {
if(!sessionStorage.user){
location.replace('/login')
}
if(location.search.includes('payment=done')){
let items = [];
localStorage.setItem('cart', JSON.stringify(items));
showFormError("order is placed");
}
if(location.search.includes('payment_fail=true')){
showFormError("Une erreur est survenue, merci de réessayer");
}
}
// select place order button
const placeOrderBtn = document.querySelector('.place-order-btn');
const getAddress = () => {
// form validation
let address = document.querySelector('#address').value;
let city = document.querySelector('#city').value;
let state = document.querySelector('#state').value;
let landmark = document.querySelector('#landmark').value;
if(!address.length || !city.length || !state.length){
return showFormError("Remplisser tous les champs");
} else{
return { address, city, state, landmark }
}
}
placeOrderBtn.addEventListener('click', () => {
let cart = JSON.parse(localStorage.getItem('cart'));
if(cart == null || !cart.length){
return showFormError("Vous commander aucun article");
}
else{
let address = getAddress();
if(address.address.length){
// send data to backend
fetch('/stipe-checkout', {
method: 'post',
headers: new Headers({'Content-Type': 'application/json'}),
body: JSON.stringify({
items: JSON.parse(localStorage.getItem('cart')),
address: address,
email: JSON.parse(sessionStorage.user).email
})
})
.then(res => res.json())
.then(url => {
location.href = url;
})
.catch(err => console.log(err))
}
}
})
SERVER index.JS
// stripe payment
let stripeGateway = stripe(process.env.stripe_key);
let DOMAIN = process.env.DOMAIN;
app.post('/stipe-checkout', async (req, res) => {
const session = await stripeGateway.checkout.sessions.create({
payment_method_types: ["card"],
mode: "payment",
success_url: `${DOMAIN}/success?session_id={CHECKOUT_SESSION_ID}&order=${JSON.stringify(req.body)}`,
cancel_url: `${DOMAIN}/checkout?payment_fail=true`,
line_items: req.body.items.map(item => {
return {
price_data: {
currency: "eur",
product_data: {
name: item.name,
description: item.shortDes,
images: item.images
},
unit_amount: item.price * 100
},
quantity: item.item
}
})
})
res.json(session.url)
})
app.get('/success', async (req, res) => {
let { order, session_id } = req.query;
try{
const session = await stripeGateway.checkout.sessions.retrieve(session_id);
const customer = await stripeGateway.customers.retrieve(session.customer);
let date = new Date();
let orders_collection = collection(db, "orders");
let docName = `${customer.email}-order-${date.getTime()}`;
setDoc(doc(orders_collection, docName), JSON.parse(order))
.then(data => {
res.redirect('/checkout?payment=done')
})
} catch{
res.redirect("/404");
}
})
I've tried to send it before but in fact if someone click to the button to redirect to stripe confirm page, his delivered data, will be stored even if he don't confirm his buy.
So when your customers trigger the redirect to Stripe Checkout, the address data you want to retain is present in the req variable in your app.post('/stripe-checkout'?
In that case you can include this data when creating the Checkout Session in the payment_intent_data.shipping parameter. Then this data will be stored on the shipping property of the Payment Intent that the Checkout creates. You can get this data when you retrieve the Checkout Session (either server-side or client-side) by passing payment_intent in the Expand parameter and looking at
the payment_intent.shipping.address property on the Checkout session object.
Alternatively, you could have the Checkout Session itself collect shipping address information for you. This would store the address in the shipping_details.address property.
To ensure your integration is able to get the customer's address information I recommend you configure your integration to listen to the checkout.session.completed webhook. That way, even if the customer fails to redirect back to your site, your back-end code will still be able to get the address information.

How to query realtime database properly for cloud fucntion?

UPDATED
I am trying to query my pricing data based on the user it is saved under and send it back in my stripe checkout cloud function. It keeps giving me an error stating that no value has been assigned to my variables when I have. I read the docs on how to do this, but I kinda got confused towards the end. I then saw something similar to what I was trying to do on a couple of other places, but then I got the codes mixed up. How can I call the variable names from the other function to put them in the pricing info?
Sources I used:
How to query specific data from Firebase using Cloud functions
How to run query from inside of Cloud function?
https://firebase.google.com/docs/database/extend-with-functions
https://firebase.google.com/docs/functions/database-events
This is how my data is set up in my real time database:
studiopick
studio
users
Gcsh31DCGAS2u2XXLuh8AbwBeap1
email : "Test#gmail.com"
firstName : "Test"
lastName : "one"
phoneNumber : "2223334567"
prices
| roomA
| serviceOne
| numberInput : "300"
| serviceType : "mix n master"
studioName : "Studio One"
uid : "Gcsh31DCGAS2u2XXLuh8AbwBeap1"
This is how my cloud function is set up:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
let price;
let info;
admin.initializeApp(functions.config().firebase);
exports.createStripeCheckout = functions.https.onCall(async (data, context) => {
const querySnapshot = await ref
.orderByChild("numberInput, serviceInput")
.equalTo(price, info)
.once("value");
// Stripe init
const stripe = require("stripe")(functions.config().stripe.secret_key);
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
mode: "payment",
success_url: "http://localhost:5500/success",
cancel_url: "http://localhost:5500/cancel",
shipping_address_collection: {
allowed_countries: ["US"],
},
line_items: [
{
quantity: 1,
price_data: {
currency: "usd",
unit_amount: price * 100, // 10000 = 100 USD
product_data: {
name: info,
},
},
},
],
});
return {
id: session.id,
};
});
exports.stripeWebhook = functions.https.onRequest(async (req, res) => {
const stripe = require("stripe")(functions.config().stripe.token);
let event;
try {
const whSec = functions.config().stripe.payments_webhook_secret;
event = stripe.webhooks.constructEvent(
req.rawBody,
req.headers["stripe-signature"],
whSec
);
} catch (err) {
console.error("⚠️ Webhook signature verification failed.");
return res.sendStatus(400);
}
const dataObject = event.data.object;
await admin.firestore().collection("orders").doc().set({
checkoutSessionId: dataObject.id,
paymentStatus: dataObject.payment_status,
shippingInfo: dataObject.shipping,
amountTotal: dataObject.amount_total,
});
return res.sendStatus(200);
});
Cloud Functions run in their own isolated containers when deployed.
When you call your retreivefromdatabase function, an instance of your Cloud Functions code is spun up, then the request is handled and the instance hibernates when it finishes (and it will be shut down later if not called upon again). When you call your createStripeCheckout function, a new instance of your Cloud Functions code is spun up, then the request is handled and the instance hibernates (and shuts down later).
Because these functions are hosted and handled by separate instances, you can't pass information between functions using global state.
Unfortunately the local testing emulator doesn't completely isolate functions in the same way (nor does it emulate throttling), which is what misled you to believe that it should function just fine in production.

How to pass information to the stripe.checkout.sessions.create() function

What I want to do:
I use firebase so as soon as someone purchases a Product over stripe I want to write that on the document of the user that bought it.
I want to use a stripe webhook for that. This webhook looks like this:
It essentially makes a https request to firebase functions as soon as the payment went trough. It triggers a https function that should write to the user's document that he has purchased the product. But to do that the webhook needs to contain the firebase user uid of the user that bought the product.
But to pass the user uid to the webhook I need to pass it to the stripe.checkout.sessions.create() function that looks like this:
exports.createCheckoutSession = functions.https.onCall(async(data, context) => {
const YOUR_DOMAIN = 'http://localhost:4242';
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [{
price_data: {
currency: 'usd',
product_data: {
name: 'Stubborn Attachments',
images: ['https://i.imgur.com/EHyR2nP.png'],
},
unit_amount: 2000,
},
quantity: 1,
}, ],
mode: 'payment',
success_url: `${YOUR_DOMAIN}/success.html`,
cancel_url: `${YOUR_DOMAIN}/cancel.html`,
});
console.log(session.id)
return session.id
})
(the user calls this cloud function to get a checkout session returned)
But how can I pass the user uid (context.user.uid) in so I can later send it with the webhook.
I hope it is clear what I want to do if not please ask. Thank you for your time.
You can add Firebase UID of user or any other information in metadata of stripe checkout session like this:
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
// line items and other stuff
metadata: {firebaseUID: uid, product_id: "ProductID"},
});
You can read them in the checkout session completed webhook or any success webhook.

Custom Controller code for Strapi Beta (3.0)

I have some code from the previous version of Strapi that works, and the beta version of controllers is much different. There is multipart / santization boilerplate added and something has changed. Do not understand how to integrate my order object and stripe charge.
Here is the boilerplate added:
module.exports = {
async create(ctx) {
// New Boilerplate added with Strapi Beta - how to integrate this with custom stuff below?
let entity;
if (ctx.is('multipart')) {
const { data, files } = parseMultipartData(ctx);
entity = await service.create(data, { files });
} else {
entity = await service.create(ctx.request.body);
}
return sanitizeEntity(entity, { model });
}
}
Here is my custom code (The controller name is Order.js)
const { address, amount, products, postalCode, token, city } = ctx.request.body;
// Send charge to Stripe
const charge = await stripe.charges.create({
amount: amount * 100,
currency: 'usd',
description: `Order ${new Date(Date.now())} - User ${ctx.state.user.id}`,
source: token
});
// Create order in database
const order = await strapi.services.order.add({
user: ctx.state.user.id,
address,
amount,
products,
postalCode,
city
});
It looks like I would add my code to the second part of the if statement since it's not multipart, but not user if "entity" is a real variable name Strapi needs or a placeholder variable I rename to "order" Code works fine in Alpha, but read the Strapi docs and there is no explanation to how to use this structure with "entity", {model} and "data" variables.
In the previous version of Strapi, to upload a file to a new entry you had to first, create your entry and two, upload the image and specify the entry you want to link this image.
Now with the multipart, you can send your image at the same time as your entry attributes.
Now about your use-case, service. has to be replaced by strapi.api.order.service.order in your case.
I agree the doc is not clear! I will update that right now.
const { parseMultipartData, sanitizeEntity } = require('strapi-utils');
module.exports = {
async create(ctx) {
// New Boilerplate added with Strapi Beta - how to integrate this with custom stuff below?
let entity;
if (ctx.is('multipart')) {
const { data, files } = parseMultipartData(ctx);
entity = await strapi.api.order.services.order.create(data, { files });
} else {
const { address, amount, products, postalCode, token, city } = ctx.request.body;
// Send charge to Stripe
const charge = await stripe.charges.create({
amount: amount * 100,
currency: 'usd',
description: `Order ${new Date(Date.now())} - User ${ctx.state.user.id}`,
source: token
});
entity = await strapi.api.order.services.order.create({
user: ctx.state.user,
address,
amount,
products,
postalCode,
city
});
}
return sanitizeEntity(entity, { model: strapi.query('order').model });
}
}

Categories