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.
Related
I am using http cloud function ( https://firebase.google.com/docs/functions/http-events ) to write documents to a firestore collection:
exports.hello = functions.https.onRequest(
(req: { query: { name: string } }, res: { send: (arg0: string) => void }) => {
console.log(req.query.name);
var name = req.query.name || 'unknown';
res.send('hello' + name);
admin
.firestore()
.collection('ulala')
.doc()
.set({ token: 'asd' }, { merge: true });
}
);
this is a test. The problem is that, once you deploy and get the link to the function, it is executable by everyone. I would like instead that only I (project owner) can use it . Is it possible to do this?
One possible solution is to restrict your HTTPS Cloud Function to only a specific "Admin" user of your app.
There is an official Cloud Function sample which shows how to restrict an HTTPS Function to only the Firebase users of the app/Firebase project: Authorized HTTPS Endpoint sample.
You need to adapt it to check if the user is the Admin user. For example by checking the userID in the try/catch block at line 60 of the index.js file (untested).
try {
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
if (decodedToken.uid !== "<the admin user uid>") {
throw new Error("Wrong user");
} else {
req.user = decodedIdToken;
next();
}
return;
} catch (error) {
functions.logger.error('Error while verifying Firebase ID token:', error);
res.status(403).send('Unauthorized');
return;
}
The two drawbacks of this approach are:
Your Admin user needs to be declared as a Firebase project user in the Authentication service
You hard code the Admin userID in your Cloud Function (you could use the Google Cloud Secret Manager service to securely store it as a configuration value, see the doc).
IMPORTANT SIDE NOTE:
In your Cloud Function you call the send() method before the asynchronous work is complete:
res.send('hello' + name);
admin
.firestore()
.collection('ulala')
.doc()
.set({ token: 'asd' }, { merge: true });
By calling the send() method, you actually terminate the Cloud Function, indicating to the Cloud Functions instance running your function that it can shut down. Therefore in the majority of the cases the asynchronous set() operation will not be executed.
You need to do as follows:
admin
.firestore()
.collection('ulala')
.doc()
.set({ token: 'asd' }, { merge: true })
.then(() => {
res.send('hello' + name);
})
I would suggest you watch the 3 videos about "JavaScript Promises" from the Firebase video series as well as read this page of the documentation which explain this key point.
I have a problem with calling a cloud function from Flutter. In this Cloud Function I am doing some different things, like safe search checking an image via ML-Vision ImageAnnotatorClient, adding a document to Firestore or updating metadata for a Storage file. Usually all this is working fine, but nearly every time when a device is the first time calling this function after some time, it fails with the CloudFunctionException DEADLINE_EXCEEDED. In every other try it is working just fine without any issue. Does someone has an idea what could cause it?
I read that there is a 'cold start' of a function, which is causing the function to taking a much longer time to execute. But still, it is always executed in less than 10seconds, so a timeout does not seem to be an issue here.
This is how I call the Flutter Cloud Function
final HttpsCallable callable = _cloudFunctions.getHttpsCallable(
functionName: 'function',
);
dynamic resp = await callable.call({'prop1': prop1String, 'prop2': prop2String});
This is the content of the Cloud Function
exports.function = functions.https.onCall(async (req, context) => {
if (!context.auth) return {status: 'ERROR', code: 401, body: 'Not signed in'};
const [result] = await client.safeSearchDetection(req.url);
const detections = result.safeSearchAnnotation;
if (detections) {
if (detections.adult === 'VERY_LIKELY') {
const storage = new Storage();
await storage
.bucket('xxx.appspot.com/')
.file(req.filename)
.delete();
return {status: 'ERROR', code: 400, body: 'NSFW'};
}
}
return refs.add({
'prop1': req.prop2,
'prop2': req.prop1
})
.then(async () => {
const images = [];
const query = await refs
.where('prop1', '==', req.prop1)
.get();
query.forEach((document) => images.push(document));
if (images.length > 0) {
const docRef = refs.doc(images[0].id);
const filename = images[0['_fieldsProto']['filename']['stringValue'];
const storage = new Storage();
await storage
.bucket('xxx.appspot.com/')
.file(filename)
.setMetadata({metadata: {receiver: req.uid}});
docRef.update({'receiver': req.uid, 'receivedTimestamp': FieldValue.serverTimestamp()});
return {status: 'OK', code: 200, body: images[0]};
} else {
return {status: 'OK', code: 200, body: null};
}
})
.catch(err => {
console.log(err);
return {status: 'ERROR', code: 500, body: 'failed to create ref in firestore'};
});
});
There error occurs in the Flutter code, when calling the callable.
I couldn't figure out what call is generating this error, but there are a couple of things that you can do to workaround that.
Increase the memory and timeout of your function.
Your function is currently running on default settings, which is 128MB of memory and 60s before timeout. You can change that to up to 2GB and 540s, note that this will have an impact on your billing. You can implement that by doing the following:
const runtimeOpts = {
timeoutSeconds: 300,
memory: '1GB'
}
exports.function = functions.runWith(runtimeOpts).https.onCall(...)
Also here is a documentation with more details on that.
Increase the timeout of your Firestore calls
You can add the following code to your firestore initialization, replacing YOUR_NUMBER_HERE, to increase the time before timeout (The default is 60000 or 1 minute), this is also likely to have impacts on your billing:
const firestore = new Firestore({
clientConfig: {
interfaces: {
'google.firestore.v1.Firestore': {
methods: {
RunQuery: {
timeout_millis: YOUR_NUMBER_HERE
}
}
}
}
}
});
Let me know if this solved the issue.
I want to get data from my database on fire base and want to save that data to amount amount: snapshot, I did apply this const snapshot = firestore.collection('payment').doc(context.params.amount).get(); does that works in the same way? but I am getting an error that context is undefined.
I actually want to get data from database.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const firestore= admin.firestore();
const stripe = require('stripe')('');
const snapshot = firestore.collection('payment').doc(context.params.amount).get();
const customer = stripe.customers.create({
email: 'customer#example1.com',
});
stripe.customers
.create({
email: 'foo-customer#example.com',
})
.then((customer) => {
return stripe.customers.createSource(customer.id, {
source: 'tok_visa',
});
})
.then((source) => {
return stripe.charges.create({
amount: snapshot,
currency: 'usd',
customer: source.customer,
});
})
.then((charge) => {
// New charge created on a new customer
})
.catch((err) => {
// Deal with an error
});
you are trying to get amount through accessing params through context,
depends on your error, this means context is undefined which means you are trying to get params of undefined. you need to explain what is context means here, is it a global variable? is this code inside a cloud function? if yes you need to move this declaration const snapshot = firestore.collection('payment').doc(context.params.amount).get();
inside your cloud function ,
this is an example of firebase cloud function
I am trying to send a sample notification to all devices according to their token, however the token is being logged as "undefined" and the notification subsequently fails to deliver
The following lines from my code successfully show me the data from the database:
const notificationSnapshot = change.after.val(); //get new value
console.info(notificationSnapshot);
However, the following gives "undefined", despite the above retrieving the data successfully.
const userToken = notificationSnapshot.token;
console.info(userToken);
Is this not the correct way to retrieve the token to send the notification to all the registered devices in my firebase database?
my whole function (index.js)
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendSampleNotification = functions.database.ref('/User')
.onWrite((change, context) => {
const notificationSnapshot = change.after.val(); //get new value
const userToken = notificationSnapshot.token;
const name = notificationSnapshot.name;
const surname = notificationSnapshot.surname;
console.info(notificationSnapshot);
console.info(userToken);
var message = {
notification: {
title: 'test title',
body: 'test message'
},
token: userToken
};
admin.messaging().send(message).then((response) => {
console.log("Message sent successfully:", response);
return response;
})
.catch((error) => {
console.log("Error sending message: ", error);
});
});
I would say that your issue is very similar to this one since you are having a missing token (showed as undefined) due to the executions times, more or less what Doug was pointing out.
Note that the solution relies on considering the execution times and I’ve seen also that the implementation differs in some method executions but I would say the generals point in the same direction.
Ok so I'm going to start with some background (skip to MY ISSUE for tl;dr):
I have an application in development that passes data from a Google Sheet into a Firebase Realtime Database in the form of a 2d Array. The data layout of the Google sheet is as shown below:
This data is passed into a Firebase Realtime Database under the node masterSheet via an Apps Script function result shown below:
Which is used as the live database for my mobile web application I am developing using the Ionic Framework (preview below):
I have functions which deal with the setting of "Y" and "N" flags at the correct positions for the sub tasks of each job and a function which sets the overall job completion status flag to "Y" when all sub tasks are done working as intended.
I am trying to add in an automatic email service via Firebase Cloud Functions that sends off a "job completion notification" whenever a job's overall "Completed" status is set to "Y" (i.e the value at ref: 'masterSheet/0/1' is equal to "Y").
So far I have managed to get it to successfully send off the emails via a Firebase Cloud Function using nodemailer and the Firebase Admin SDK to all registered users of the Firebase app whenever a job's overall completed status is changed from an "N" to a "Y" via the onUpdate() method and the .ref() of the location to listen at.
Below is my Index.js file containing the cloud function I am using:
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp();
// The mail service used
const nodemailer = require('nodemailer');
// Cloud Fucntion to export:
exports.onMessageUpdate = functions.database.ref('/masterSheet/{subArray}/1')
.onUpdate((change) => {
var changeRef = change.after.ref.path;
console.log('changeRef: ' + changeRef);
var newVal = change.after.val();
if (newVal == "Y"){
getUsers();
}
})
// Fucntion to get all registers users of the Firebase Project
function getUsers(){
var userEmails = [];
admin.auth().listUsers()
.then(function(listUsersResult) {
listUsersResult.users.forEach(function(userRecord) {
console.log(userRecord);
userEmails.push(userRecord.email);
sendCompletionEmail(userRecord.email)
});
})
.catch(function(error) {
console.log("Error listing users:", error);
});
}
// Function to send automatic emails
function sendCompletionEmail(email){
var transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
type: 'OAuth2',
user: 'xxxxxxxx#gmail.com',
clientId: 'xxxxxxxx.apps.googleusercontent.com',
clientSecret: 'xxxxxxxxxxxxxxx',
refreshToken: 'xxxxxxxxxxxxxx'
}
})
// Email details:
var mailOptions = {
from: 'xxxxxxx',
to: email,
subject: 'Job completion notification',
text: 'This is an automated message to inform you that a job has been fully completed ' +
'with all of its tasks marked as done. \n\nYou can view this (along with any others) from the Completed ' +
'Jobs page within the app.'
}
transporter.sendMail(mailOptions, function (err, res) {
if(err){
console.log('Error');
} else {
console.log('Email Sent');
}
})
}
MY ISSUE:
I want to be able to include the job title in this automatic email that is sent.
logging the result of change.after.ref.path used in the snippet below:
// Cloud Fucntion to export:
exports.onMessageUpdate = functions.database.ref('/masterSheet/{subArray}/1')
.onUpdate((change) => {
var changeRef = change.after.ref.path;
console.log('changeRef: ' + changeRef);
var newVal = change.after.val();
if (newVal == "Y"){
getUsers();
}
})
Produces this log output:
which contains exactly what I want within it... But I don't know how to get it out...
How can I retrieve the second value from the changeRef variable so that I can pass this onto the sendCompletionEmail() function and use it to refer to the item at position [0] for that node?
something like:
var subArray = changeRef[1]
to get the value: 0 out of masterSheet/0/1
which i can store as a variable and use to refer to the job title of the job that has just been completed in the sent off email.
Thanks for any help!
If you're looking for the 0 from the request, that is available from the second parameter that is passed into your Cloud Function (but that you're not declaring).
exports.onMessageUpdate = functions.database.ref('/masterSheet/{subArray}/1')
.onUpdate((change, context) => {
console.log(context.params.subArray);
})
See the Firebase documentation on handling event data and the reference docs for onUpdate.