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.
Related
I have integrated stripe api into my ecommerce website. When you checkout you are sent to the stripe api payment link where you type in your information. Of course two things could happen here, either the payment goes through and succeed or the order gets canceled. Everything works except I am trying to remove all cart items only if the payment succeeds and goes through.
Here is my code:
app.post('/api/createCheckoutSession', async (req, res) => {
try {
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
mode: 'payment',
line_items: req.body.items.map(item => {
const storeItem = storeItems.get(item.id)
return {
price_data: {
currency: 'usd',
product_data: {
name: storeItem.name
},
unit_amount: storeItem.priceInCents
},
quantity: item.quantity
}
}),
success_url: `${process.env.SERVER_URL}`,
cancel_url: `${process.env.SERVER_URL}cart`,
})
res.json({ url: session.url })
} catch (e) {
console.log(e)
res.status(500).json({ error: e.message })
}
});
So if I do the following code I can remove all cartItems from the user, however this happens regardless of if the payment was successful or not:
app.post('/api/createCheckoutSession', async (req, res) => {
try {
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
mode: 'payment',
line_items: req.body.items.map(item => {
const storeItem = storeItems.get(item.id)
return {
price_data: {
currency: 'usd',
product_data: {
name: storeItem.name
},
unit_amount: storeItem.priceInCents
},
quantity: item.quantity
}
}),
success_url: `${process.env.SERVER_URL}`,
cancel_url: `${process.env.SERVER_URL}cart`,
})
cartItem.remove({ user: req.body.user }, function (err) {
if (err) {
console.log(err)
}
})
res.json({ url: session.url })
} catch (e) {
console.log(e)
res.status(500).json({ error: e.message })
}
});
So looking through Stripe api documentation and googling the only thing that consistently comes up is success_url, which wasn't what I am looking for (at least I do not think it will fix what I am trying to do). So for the original code I tried to console.log(session) and found a payment_status: 'unpaid' and figured I could use this to do what I am trying to by the following:
app.post('/api/createCheckoutSession', async (req, res) => {
try {
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
mode: 'payment',
line_items: req.body.items.map(item => {
const storeItem = storeItems.get(item.id)
return {
price_data: {
currency: 'usd',
product_data: {
name: storeItem.name
},
unit_amount: storeItem.priceInCents
},
quantity: item.quantity
}
}),
success_url: `${process.env.SERVER_URL}`,
cancel_url: `${process.env.SERVER_URL}cart`,
})
console.log(session)
if (session.payment_status == 'paid') {
cartItem.remove({ user: req.body.user }, function (err) {
if (err) {
console.log(err)
}
})
}
res.json({ url: session.url })
} catch (e) {
console.log(e)
res.status(500).json({ error: e.message })
}
});
With the above code, the if statement does not work as the only time something gets logged to the console is when the session first gets created, so the if statement does not execute after the payment succeeds or gets canceled.
Another solution is for me to create a different success_url where it pops up and I have a react useEffect call the remove cart items function. And then it redirects again somewhere else, but as I alluded to earlier this just seems like a terrible solution when it feels like I am just missing something very simple to get this to work the way I have attempted to.
I'm going to restate what I think your goal is here so I am clear on what the answer is.
After user is sent to checkout you have 2 potential outcomes you want your app to handle:
Successful payment: Get money, get user their goods
Payment canceled: Empty user cart
The problem you are running into is that your code can only await the creation of the user session. At that point you should just redirect to the Checkout URL. At this point your code does not know whether the payment will go through or not because the user is just being redirected to the Checkout UI where they can make their payment.
The state of the user's payment is reported back to your app in 2 different ways.
Customized Success/Cancel URLS - While this doc focuses on capturing the Session ID in the Success URL, you can do the same thing with the Cancel URL. In this case which URL the user is sent to tells your system a Checkout was either successful or was canceled. The Session ID injected into the URL identifies which Checkout session it was.
Checkout Webhook events - This approach provides confirmation of success but requires waiting until the session is expired to confirm cancelation. Still, it is recommended that most integrations make use of Webhooks to monitor account activities.
So to circle back to your code, I would move the code that clears the cart to a function that responds to either the user being redirected to the cancel_url in your app or responds to the checkout.session.expired webhook event
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
I am having difficulties when I try to apply the transfer method so I need your help.
What I want to do is transfer money [IN TEST MODE] from one stripe account to another, I did the exact thing that was written in the documentation looks like this:
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2020-03-02',
maxNetworkRetries: 2,
});
exports.handler = async (event) => {
try {
const paymentIntent = await stripe.paymentIntents.create({
amount: 100,
currency: 'eur',
payment_method_types: ['card'],
transfer_group: '{ORDER10}',
});
// Create a Transfer to the connected account (later):
await stripe.transfers.create({
amount: 70,
currency: 'eur',
destination: '{{ACCOUNT_ID}}',
transfer_group: '{ORDER10}',
});
return {
statusCode: 200,
body: JSON.stringify(paymentIntent),
};
} catch (error) {
console.log({ error });
return {
statusCode: 400,
body: JSON.stringify(error),
};
}
};
What I am getting is an error with saying there is no such destination, which means that the account ID is not available.
I got my other stripe account's ID with the curl command:
curl https://api.stripe.com/v1/account -u {{SK_TEST_KEY}}
this gave me the ID and that's what I pasted in the {{ACCOUNT_ID}} field.
What am I doing wrong? Is it something that this thing can not be done in test mode?
Or somehow I need to connect both of my stripe accounts??
Thanks in advance
I created an account on my dashboard stripe page, the main thing was that it has to be a standard account, filled in with some dummy data which I found thanks to Jonathan Steele, after that the transfer worked.
I cannot figure out how to send email to client after successful payment. Documentation says about setting "payment_intent_data.receipt_email" but what I have below in my code is not working (nothing arrives to emai-box). How am I correctly to set this?
app.post('/create-checkout-session', async (req, res) => {
try {
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
**payment_intent_data: {receipt_email: "test#test.com"},**
shipping_rates: ['shr_1JgbJxAuqIBxGbL5lCkeLyfu'],
shipping_address_collection: {
allowed_countries: ['FI'],
},
line_items: [
{
price_data: {
currency: 'eur',
product_data: {
name: 'book',
},
unit_amount: 6200,
},
quantity: 2,
},
],
mode: 'payment',
success_url: 'http://localhost:4200/myynti/success',
cancel_url: 'http://localhost:4200/myynti/cancel',
});
res.json({ id: session.id });
Stripe automatically sends email to the client once the payment is successfully done. You can edit this settings in your stripe dashboard. However, if this doesn't work or you want to send email through your platform/application then there are two ways possible ways to do this.
When the user completes the checkout session and returns back to the application, then you have to verify the CHECKOUT_SESSION_ID and retrieve the session using the CHECKOUT_SESSION_ID. If it is correct then call your email function to send email to that client.
const verifyCheckoutSession =async(req, res) => {
const sessionId = req.query.sessionId.toString();
if (!sessionId.startsWith('cs_')) {
throw Error('Incorrect CheckoutSession ID.');
}
/* retrieve the checkout session */
const checkout_session: Stripe.Checkout.Session = await stripe.checkout.sessions.retrieve(
sessionId,
{
expand: ['payment_intent'],
});
/* get the customer id */
const customerId = checkout_session.customer.toString();
/* get the customer through customerId */
const customer = await stripe.customers.retrieve(customerId.toString());
/*
1. get the customer email
2. You can also retrieve other details
*/
const email = customer.email;
/* Now call your email function to send email to this particular user */
/* sendEmailTo(email) */
}
Register the web hook endpoint and listen to the event
import { buffer } from 'micro';
const __handleStripeEvent = async (req, res) => {
if (req.method === 'POST') {
/* web hooks only support post type */
const requestBuffer = await buffer(req);
const sig = req.headers['stripe-signature'] as string;
let event: Stripe.Event;
try{
event = stripe.webhooks.constructEvent(requestBuffer.toString(), sig, webhookSecret);
switch (event.type) {
case 'checkout.session.completed':
/* repeat the same process */
/* get checkout session id, then customer id, customer */
/* and send email */
break;
}
}
catch(error){
/* do something on error */
}
}
}
It seems like your account is "test" mode. In "test" mode, it's totally impossible to automatically send email from stripe after payment even if you set "receipt_email" parameter. Only in "live" mode, it's possible. *In "test" mode, sending custom email using webhook is still possible.
Actually, I couldn't find the information above on stripe documentation so I asked stripe support, then, they said so as I said above.
You don't necessarily need to pass the customer's email in payment_intent_data.receipt_email.
When using Checkout, you can automatically email your customers upon successful payments. Enable this feature with the email customers for successful payments option in your email receipt settings.
However, the most important point to note is that receipts for payments created using your test API keys are not sent automatically. Instead, you can view or manually send a receipt using the Dashboard by finding the payment in the Dashboard and click "Send Receipt" under "Receipt history".
I've created a POST request to collect payments from customers via Stripe
let data = {
errorMsg:'',
key: process.env.STRIPE_PUBLIC_KEY
}
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [
{
price_data: {
currency: 'usd',
product_data: {
name: 'My tools',
},
unit_amount: 10,
},
quantity: 1,
},
],
mode: 'payment',
success_url: 'https://example.com/success',
cancel_url: 'https://example.com/cancel',
});
const sessiondetails = await stripe.checkout.sessions.retrieve(session.id);
console.log("session details", sessiondetails);
res.redirect(303, session.url);
}))
Can someone tell me how can I confirm that the payment is successfully processed before adding credits/allow customers to download digital products or before redirecting the user?
The recommended way to track this is using the checkout.session.completed webhook to handle fulfillment. Either here or retrieving the session as you've done, you can check if the payment_status (API ref) is paid before granting access/credits.