Stripe Api call Generate double Invoice on Firebase cloud function call - node.js

Why when i call this function Stripe generate 2 invoice?
I want to set a custom price % for the invoice and not using a fixed price as suggested on the documentation.
I want that the invoice is automatically payed by the customer.
exports = module.exports = functions.https.onRequest(
async (request, response) => {
let data = request.body;
var imponibile = 100;
const { stripe } = require("./stripe");
const { db } = require("./admin");
const invoiceItem = await stripe.invoiceItems.create({
customer: data.customerId,
description: data.description,
amount: imponibile,
currency: "eur",
});
const invoice = await stripe.invoices.create({
customer: data.customerId,
auto_advance: true, // Auto-finalize this draft after ~1 hour
collection_method: "charge_automatically",
});
const payinvoice = await stripe.invoices.pay(invoice.id);
return payinvoice;
}
);

Solved, the problem was that i was returning the actual payment function recursively.
Solution :
return respond.status(200).send('ok')

Related

NodeJs Application to send alerts to users email ID

I'm creating a NodeJs application that sends alerts to a user's email ID when the price of the bitcoin goes above the price specified by the user. For scheduling tasks, I'm using cron. Also, I've used the bull as a message broker. When I'm running this program, It isn't working and it isn't sending emails when the price is above the specified price. Please help me to find out what is the problem.
require('dotenv').config({path: require("find-config")(".env")});
const CronJob = require("cron").CronJob;
let Queue = require("bull");
const Alert = require("../models/alert");
const { currentPrice } = require("../utilities/currentPrice");
const { sendEmail } = require("../utilities/sendEmailNotification");
//Creating a Queue
let alertQueue = new Queue("alerts", process.env.RedisURL);
//Consumer Process
alertQueue.process(async function(job, done) {
const {mailTo, title, text} = job.data;
const mailObj = {
from: process.env.SendGridForm,
recipients: mailTo,
subject: title,
message: text
}
const response = sendEmail(mailObj);
if(response.error) {
done(new Error("Error Sending Alert!!!"));
}
done();
})
let sendAlert = new CronJob("*/25 * * * * *", async function () {
let priceObj = await currentPrice();
if (priceObj.error)
return;
let price = priceObj.data;
const alerts = await Alert.find({status: "Created"});
alerts.forEach((alert) => {
if(alert.price <= price) {
mailTo = alert.email;
title = `Bitcoint is UP!`;
text = `Price of Bitcoin has just exceeded your alert price of ${alert.price} USD. Current price is ${price} USD.`;
alertQueue.add(
{mailTo, title, text},
{
attempts: 3,
backoff: 3000
}
)
alert.status = "Triggered";
alert.save();
}
})
});
sendAlert.start();

StripeInvalidRequestError: No such setupintent: 'seti_...'

After a user registers on my app I want them to add a payment method. Their stripe customer account is created as soon as they register and from there they are transferred to the 'AddPaymentMethod' screen. As soon as the ' AddPaymentMethod' screen appears, I send a request to my server to create a setupIntent.
Creating Setup Intent:
exports.createSetupIntent = functions.https.onCall(async (data, context) => {
const userId = data.userId;
const snapshot = await db
.collection("development")
.doc("development")
.collection("users")
.doc(userId).get();
const customerId = snapshot.data().customer_id;
const setupIntent = await stripe.setupIntents.create({
customer: customerId,
});
const clientSecret = setupIntent.client_secret;
return {
clientsecret: clientSecret,
};
});
Calling the function when the screen appears on my client (This successfully creates the client secret key and stores it in a variable in the frontend):
FirebaseReferenceManager.functions.httpsCallable("createSetupIntent").call(["userId": Auth.auth().currentUser?.uid]) { (response, error) in
if let error = error {
print(error.localizedDescription)
}
if let response = (response?.data as? [String: Any]) {
let clientSecretKey = response["clientsecret"] as! String?
self.clientSecret = clientSecretKey ?? "-"
print("created client secret key: \(clientSecretKey!)")
}
}
Next, the user enters their credit card information and creates a payment method. Here is the function on my server:
exports.createPaymentMethod = functions.https.onCall(async (data, context) => {
const number = data.number;
const expMonth = data.expMonth;
const expYear = data.expYear;
const cvc = data.cvc;
const paymentMethod = await stripe.paymentMethods.create({
type: "card",
card: {
number: number,
exp_month: expMonth,
exp_year: expYear,
cvc: cvc,
},
});
const pmId = paymentMethod.id;
return {
paymentMethodId: pmId,
};
});
I call this function from the frontend when the user presses the "Save payment method" button. This successfully creates a payment method and returns the payment method id which is stored in a variable on the front end.
Lastly, using the client secret id and payment method id that was returned from the previous functions, I call the last function to confirm the setupIntent.
This function is called when a payment method is created successfully:
exports.confirmSetupIntent = functions.https.onCall(async (data, context) => {
const clientSecretKey = data.clientSecretKey;
const paymentMethodId = data.paymentMethodId;
const setupIntent = await stripe.setupIntents.confirm(
clientSecretKey,
{payment_method: paymentMethodId}
);
});
This is how the createPaymentMethod and confirmSetupIntent functions are called from the frontend:
FirebaseReferenceManager.functions.httpsCallable("createPaymentMethod").call(["number": self.cardNumber, "expMonth": self.expMonth, "expYear": "20\(self.expYear)", "cvc": self.cvvCode]) { (response, error) in
if let error = error {
print("error occured when creating payment method: \(error.localizedDescription)")
}
if let response = response?.data as? [String: Any] {
let paymentMethodId = response["paymentMethodId"] as! String?
self.paymentMethodID = paymentMethodId ?? "-"
print(paymentMethodId!)
FirebaseReferenceManager.functions.httpsCallable("confirmSetupIntent").call(["clientSecretKey": self.clientSecret, "paymentMethodId": self.paymentMethodID]) { (response, error) in
if let error = error {
print("error occured when confirming setup intent: \(error.localizedDescription)")
}
print("setup intent confirmed")
}
}
}
In the debug console on the frontend it says that the error from confirming the setupIntent was INTERNAL. When I check the logs on my server I it says:
StripeInvalidRequestError: No such setupintent: 'seti_...'
Note that I am using SwiftUI and custom screens/textfields for the stripe integration.
Any help is appreciated!
The No such setupintent error indicates you have a mismatch in your API keys, and you should double check that your server secret key and client publishable are a matched pair for the same account and both for test mode, eg.
Of greater concern is that you appear to be passing payment details to your server to create the payment method. This is not recommended, and has significant PCI Compliance implications. Instead of creating the payment method like this on your server, you should use Elements and provide a reference to the Card Element when you use confirmCardSetup (docs):
stripe.confirmCardSetup(
clientSecret,
{
payment_method: {
card: cardElement,
},
}
)

How to redirect a user to a site only after successful stripe payment?

For my website i have one site to explain the pricing plans and where you can submit your email and credit card info. After that you get redirected to the create account site. I want to redirect though only to the site if payment was successful - otherwise, the /createaccount url shouldn't be accessible. I have some ideas on how to do that but would need help with that or tipps.
In the past I tried to add some id to the /createaccount:id url but that didnt work out.
Frontend code is the typical stripe subscription code with stripe.elemens and elements.create('card')
Backend code looks like that:
app.post("/charge", (req, res) => {
let email = req.body.email;
let card = req.body.stripeToken;
let amount = req.body.plan;
let interval = req.body.interval;
const createSubscription = async () => {
const stripe = require("stripe")(SK);
const {
data: plans
} = await stripe.plans.list({
product: "prod_id"
})
// Create Customer
const customer = await stripe.customers.create({
email: email,
card: card,
});
const planToSubscribeTo = plans.find(p => p.interval === interval)
// Subscribe Customer to plan
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [{
plan: planToSubscribeTo.id
}],
});
return {
customer,
// prodgHack,
planToSubscribeTo,
// ThreeMonthFreeCoupon,
subscription
};
};
console.log('Creating subscriptions ...... ');
createSubscription().then((sub) => {
console.log(sub);
}).catch(err => {
console.warn('Failed to create subscriptions ', err);
});
res.redirect('/createAccount');
});
Using Stripe in the past (though for making a charge and not a subscription), assuming that the process is similar I have done something like below
app.post('/payment', async (req, res) => {
stripe.charges.create({
amount: 10000,
currency: 'gbp',
description: 'Description Here',
source: req.body.stripeToken,
receipt_email: req.body.stripeEmail,
metadata: { email: req.body.stripeEmail },
}, async (err, charge) => {
if (err && err.type === 'StripeCardError') {
res.render('payment_declined');
} else {
// Do anything else here like update DB etc and then render view
res.render('successful_payment');
}
});
});
Whether this is the correct way or I have missed pieces out I am unsure but maybe this gets you a little further? If not happy to remove this answer for someone else to add something
Also as a side note in your code you probably don't need to declare variables to the following, just call them directly
let email = req.body.email;
let card = req.body.stripeToken;
let amount = req.body.plan;
let interval = req.body.interval;
// So just use req.body.email, req.body.plan where required
const stripe = require("stripe")(SK); is better placed at the top of your file (call it once only, what happens if you have multiple payment paths in your application) as opposed to being in the post function itself

Firebase Stripe (Error) Promises must be handled appropriately

I am trying to process my payment with firebase and stripe and have come across a problem when trying to deploy my function to the cloud saying 'Promises must be handled appropriately. I know this is a tlint compilation error but can't figure out why the error is being triggered.
Here is my code
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp(functions.config().firebase);
const stripe = require('stripe')(functions.config().stripe.testkey);
exports.stripeCharge = functions.firestore
.document('/payments/{userId}/mypayments/{paymentId}')
.onCreate((snap,event) => {
const payment = snap.data()
const userId = event.params.userId
const paymentId = event.params.paymentId
// checks if payment exists or if it has already been charged
if (!payment || payment.charge) return null;
return admin.firestore()
.doc(`/users/${userId}`)
.get()
.then(snapshot => {
return snapshot
})
.then(customer => {
const amount = payment.price * 100 // amount must be in cents
const idempotency_key = paymentId // prevent duplicate charges
const source = payment.token.id
const currency = 'usd'
const charge = {amount, currency, source}
return stripe.charges.create(charge, { idempotency_key })
})
.then((charge) => {
admin.firestore()//The error keeps referring me to this line
.collection('/payments').doc(userId).collection('mypayments').doc(paymentId)
.set({
charge: charge
}, { merge: true })
})
})
The line generating the error is stated above
Actually, with the latest version(s) of Cloud Functions you are not obliged to include a catch() in your Promises chaining. The platform where the Cloud Function runs will handle the error itself.
Based on this post What could this be about? [TsLint Error: "Promises must be handled appropriately"] it is apparently an error generated by TsLint (EsLint?).
However, independently of this "error" detected by TsLint, I think you may encounter problems with your Cloud Function because you don't return the last promise of your chain:
return admin.firestore() //HERE YOU RETURN CORRECTLY
.doc(`/users/${userId}`)
.get()
.then(snapshot => {
return snapshot //HERE YOU RETURN CORRECTLY
})
.then(customer => {
const amount = payment.price * 100 // amount must be in cents
const idempotency_key = paymentId // prevent duplicate charges
const source = payment.token.id
const currency = 'usd'
const charge = {amount, currency, source}
return stripe.charges.create(charge, { idempotency_key }) //HERE YOU RETURN CORRECTLY
})
.then((charge) => {
return admin.firestore() //HERE, IN YOUR CODE, YOU DON'T RETURN
.collection('/payments').doc(userId).collection('mypayments').doc(paymentId)
.set({
charge: charge
}, { merge: true })
})
})
finally figure it out
Whenever you make a promise function, it has to end with an error handler so i fixed this by using a simple catch
.then((charge) => {
admin.firestore()
.collection('/payments').doc(userId).collection('mypayments').doc(paymentId)
.set({
charge: charge
}, { merge: true })
.catch(er=>{
console.log(er);
return er
}
)
})

Function returned undefined, expected Promise or value- NodeJS

I have an angular app that is using firestore as the DB and Google cloud functions to handle the backend. When I run my app and click on pay to make the call to the Stripe API I get the following message on the log for the cloud functions.
Function returned undefined, expected Promise or value
I have been reading several stackoverflow questions and they talk about me returning whatever in the Promise .then() but I keep getting the same error. The good thing is that the actual value gets store in Firestore with no problem, so it seems to be more like a warning rather than an error since nothing breaks.
What am I missing?
exports.stripeCharges = functions.firestore
.document("/payments/users/TAMO/{paymentId}")
.onWrite((event, context) => {
const payment = event.after.data();
const paymentId = context.params.paymentId;
if (!payment || payment.charge) return;
return admin
.firestore()
.doc(`/payments/users/TAMO/${paymentId}`)
.get()
.then(snapshot => {
return snapshot.data();
})
.then(customer => {
const amount = payment.amount * 100;
const idempotency_key = paymentId;
const source = payment.token.id;
const currency = "usd";
const description = "Test Charge";
const charges = {
amount,
currency,
description,
source
};
return stripe.charges.create(charges, { idempotency_key });
})
.then(charges => {
return admin
.firestore()
.doc(`/payments/users/TAMO/${paymentId}`)
.set(
{
charge: charges
},
{
merge: true
}
);
});
});
Looks like either you don't have payment or payment.charge.
if (!payment || payment.charge) return;
I solved this warning by doing the following:
if (!payment || payment.charge) return null;
The line above checks if payment exists or if it has already been charged

Resources