Integrating FireStore/Stripe/iOS/Cloud Functions - firebase

I am a newbie to all the technologies mentioned. So, I apologize in advance if this question shouldn't be here.
So I am trying to write the firestore equivalent of the example stripe-firebase realtime database-cloud functions integration provided by firebase. It is located here: https://github.com/firebase/functions-samples/blob/master/stripe/functions/index.js
I successfully converted the exports.createStripeCustomer function. But I am having trouble with addPaymentSource.
This is what I have so far:
exports.addPaymentSource = functions.firestore.document('Users/{userId}/paymentSources/{paymentID}').onWrite((change, context) => {
let newPaymentSource = change.after.data();
let token = newPaymentSource.token;
return functions.firestore.document(`Users/${context.params.userId}`).get('customer_data')
.then((snapshot) => {
return snapshot.val();
}).then((customer) => {
return stripe.customers.createSource(customer, {newPaymentSource});
}).then((response) => {
return change.after.ref.parent.set(response);
}, (error) => {
return change.after.ref.parent.child('error').set(userFacingMessage(error));
}).then(() => {
return reportError(error, {user: context.params.userId});
});
});
Deployment happens successfully, but the function fails with the following error.
TypeError: functions.firestore.document(...).get is not a function
at exports.addPaymentSource.functions.firestore.document.onWrite (/user_code/index.js:73:77)
at Object. (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:112:27)
at next (native)
at /user_code/node_modules/firebase-functions/lib/cloud-functions.js:28:71
at __awaiter (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:24:12)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:82:36)
at /var/tmp/worker/worker.js:728:24
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
Clearly 'get' doesn't seem to be a function. I got this function from the following reference document. https://firebase.google.com/docs/reference/functions/functions.firestore.DocumentSnapshot
I seem to be missing something. I have been stuck on this for a couple of days. So, any help would be greatly appreciated. Thanks in advance.

use firebase-admin
const admin = require('firebase-admin');
exports.addPaymentSource = functions.firestore.document('Users/{userId}/paymentSources/{paymentID}').onWrite((change, context) => {
let newPaymentSource = change.after.data();
let token = newPaymentSource.token;
return admin.firestore().doc(`Users/${context.params.userId}`).get('customer_data')
// what you have to do
});
});

The functions package is used for triggering functions. You can use the following methods:
onCreate
onUpdate
onDelete
onWrite
(more here: https://firebase.google.com/docs/firestore/extend-with-functions)
To use the get() method, you'll want to use the firebase-admin package - you can then access firestore from admin.firestore()
(firebase-admin: https://www.npmjs.com/package/firebase-admin)

Related

Calling an API using Axios and Firebase Cloud Functions

I want to make a Google Cloud Function calling an external API for me. After some research on Google I found the way using Axios. The call is actually working, when I'm using it on my own nodejs but when I want to deploy the function to Google Cloud functions I'm always getting an error (Function cannot be initialized. Error: function terminated.)
I'm on the Blaze plan.
const functions = require("firebase-functions");
const axios = require("axios");
exports.getData = functions.https.onRequest((req, res) => {
return axios.get("http://api.marketstack.com/v1/eod?access_key='myAccessKey'&symbols=AAPL")
.then((response) => {
const apiResponse = response.data;
if (Array.isArray(apiResponse["data"])) {
apiResponse["data"].forEach((stockData) => {
console.log(stockData["symbol"]);
});
}
}).catch((error) => {
console.log(error);
});
});
Could someone please help me?
EDIT: I finally fixed it: the mistake was, that I ended up with two package.json files (one in the directory where it should be and one which I actually didn't need). When I was installing the dependencies with npm install, axios was added into the wrong package.json file. Unfortunately the other package.json file made it up to the server and I ended up with a package.json file without the necessary dependencies on the server and thus this made the error occur.
I didn’t test your code but you should return "something" (a value, null, a Promise, etc.) in the then() block to indicate to the Cloud Function platform that the asynchronous work is complete. See here in the doc for more details.
exports.getData = functions.https.onRequest((req, res) => {
return axios.get("http://api.marketstack.com/v1/eod?access_key='myAccessKey'&symbols=AAPL")
.then((response) => {
const apiResponse = response.data;
if (Array.isArray(apiResponse["data"])) {
apiResponse["data"].forEach((stockData) => {
console.log(stockData["symbol"]);
});
}
return null;
}).catch((error) => {
console.log(error);
});
});
You probably want do more than just logging values in the then() e.g. call an asynchronous Firebase method to write to a database (Firestore or the RTDB): in this case take care to return the Promise returned by this method.

Firebase Cloud Functions: TypeError snapshot.forEach is not a function

I've been struggling to understand why my Firebase cloud function isn't working.
I'm deleting a reserved number in a collection called 'anglerNumbers' when a new user has registered and when that users' document has been created. I use this on the client to make sure a reserved number can't be used twice. I'm following the documentation here: https://firebase.google.com/docs/firestore/query-data/queries?authuser=0 (Using Node.js)
But I keep getting the Error: TypeError: snapshot.forEach is not a function
Here's the function:
exports.newUser = functions.firestore.document('users/{userId}')
.onCreate((snap, context) => {
const newUserNumber = snap.data().anglerNumber;
const anglersRef = admin.firestore().collection('anglerNumbers');
const snapshot = anglersRef.where('anglerNumber', '==', newUserNumber).get();
if (snapshot.empty) {
console.log('No matching documents.');
return;
}
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
doc.delete();
});
})
It does not console log 'No matching documents'. So there are documents but I can't perform the forEach as indicated by the documentation. What am I missing? Thanks!
in this line in your code:
const snapshot = anglersRef.where('anglerNumber', '==', newUserNumber).get();
You assume that get resolves immediately to a snapshot but in fact get() returns a promise that will resolve into a snap shot. You need to wait for this async function.
Either use await if that is possible in your context or use:
anglersRef.where('anglerNumber', '==', newUserNumber).get().then((snapshot)=>{
//do you processing
});

How do I "finish" a cloud function

I'm writing my first Firebase Cloud Function in TypeScript. The function queries Firestore looking for a document that matches a parameter. The documentation says that when using a promise I need to "finish" the promise so that the function knows when it can complete. How do I do that? Here is my function.
BTW my function only returns the "not found" result right now. And it does work. It does send the response to my client app, but only after the function times out.
export const validateMemberPin = functions.https.onRequest((request, response) => {
console.log('pin: ' + request.query.pin);
const query = admin.firestore().collection('access').where('memberPin', '==', request.query.pin);
return query.get().then((snapshot) => {
if (snapshot.empty)
response.json({'result': 'false'});
});
});
Cloud functions have a NodeJS environment, so to end a function, you just need to add a return statement, in your case just add a return before response.json like so:
return query.get().then((snapshot) => {
if (snapshot.empty)
return response.json({'result': 'false'});
});
However, it would be better if you handle both cases:
return query.get().then((snapshot) => response.json({'result': !snapshot.empty});

Reading firestore parents from firebase's function

Here is my code:
exports.onCreateLog = functions
.firestore
.document('/accounts/{userId}/account/{accountId}/logs/{logId}')
.onCreate((event) => {
let documentRef = functions
.firestore
.doc(`/accounts/${event.params.userId}/account/${event.params.accountId}`);
return documentRef
.get()
.then(documentSnapshot => {
if (documentSnapshot.exists) {
console.log(`Document retrieved successfully. ${JSON.stringify(documentSnapshot.data())}`);
}
});
});
What I would like to do is read the value from the parent, but I tried with this error:
TypeError: Cannot read property 'firestore' of undefined
I tried to get the parent, and get the value with no luck. Is there any advise? Thanks.
You can't use your functions object reference to query your database. You can only use it to build your Cloud Function trigger.
If you want to query your database within a Cloud Function, you need to use the Admin SDK.
const admin = require('firebase-admin')
admin.firestore().doc(...).get()

Trying to run authy-client with Firebase Cloud Functions

I've been trying to get authy-client to run with Firebase Cloud Functions but I keep running into a ValidationFailedError. I've been testing the examples the author supplied at https://www.npmjs.com/package/authy-client with no luck.
For my Firebase function, I've been trying this:
const Client = require('authy-client').Client;
const client = new Client({ key: 'my API key here' });
exports.sendVerificationCode = functions.database.ref('users/{userId}/verify/status')
.onCreate(event => {
const sender = client.registerUser({
countryCode: 'US',
email: 'test#tester.com',
phone: '4035555555'
}).then( response => {
return response.user.id;
}).then( authyId => {
return client.requestSms({ authyId: authyId });
}).then( response => {
console.log(`SMS requested to ${response.cellphone}`);
throw Promise;
});
return Promise.all([sender]);
});
But I get this error:
ValidationFailedError: Validation Failed
at validate (/user_code/node_modules/authy-client/dist/src/validator.js:74:11)
at _bluebird2.default.try (/user_code/node_modules/authy-client/dist/src/client.js:632:31)
at tryCatcher (/user_code/node_modules/authy-client/node_modules/bluebird/js/release/util.js:16:23)
at Function.Promise.attempt.Promise.try (/user_code/node_modules/authy-client/node_modules/bluebird/js/release/method.js:39:29)
at Client.registerUser (/user_code/node_modules/authy-client/dist/src/client.js:617:34)
at exports.sendVerificationCode.functions.database.ref.onCreate.event (/user_code/index.js:24:25)
at Object.<anonymous> (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:59:27)
at next (native)
at /user_code/node_modules/firebase-functions/lib/cloud-functions.js:28:71
at __awaiter (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:24:12)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:53:36)
at /var/tmp/worker/worker.js:695:26
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
I am new to Firebase' cloud functions so I may be overlooking something obvious but from what I've read, then() statements and the function itself needs to return/throw a Promise so I hacked that together.
I've also made sure that authy-client is included in the dependencies in the package.json file.
I originally signed up for the Twilio trial which gave me an option to create an application with Authy. Needing to be sure, I also signed in to Authy to check if the API key is the same, and they are. So I don't think the validation error is due to the API key.
Any help would be appreciated. Thank you.
Thank you all for your answers. I was finally able to figure out a solution. It had nothing to do with the code, well throw Promise was a mistake but it was apparently a problem with DNS resolutions that was resolved when I set up billing on Firebase.
As for my final code, as I originally wanted to use authy-client for phone verification, it looks something like this:
exports.sendVerificationCode = functions.database.ref('users/{userId}/verify')
.onCreate(event => {
const status = event.data.child('status').val();
const phoneNum = event.data.child('phone').val();
const countryCode = event.data.child('countryCode').val();
var method;
if(status === 'pendingSMS')
method = 'sms';
else
method = 'call';
// send code to phone
const sender = authy.startPhoneVerification({ countryCode: countryCode, phone: phoneNum, via: method })
.then( response => {
return response;
}).catch( error => {
throw error;
});
return Promise.all([sender]);
});
throw Promise doesn't really mean anything. If you want to capture problems that can occur anywhere in your sequence of promises, you should have a catch section. The general form is this:
return someAsyncFunction()
.then(...)
.then(...)
.catch(error => { console.error(error) })
That doesn't necessarily fix your error, though. That could be coming from the API you called.
Twilio developer evangelist here.
Doug is right about throw Promise, that will definitely need to be changed.
However, the error seems to be coming before that and from the API. Specifically, the stack trace tells us:
at Client.registerUser (/user_code/node_modules/authy-client/dist/src/client.js:617:34)
So the issue is within the registerUser function. The best thing to do is try to expose more of the error that is being generated from the API. That should give you the information you need.
Something like this should help:
const Client = require('authy-client').Client;
const client = new Client({ key: 'my API key here' });
exports.sendVerificationCode = functions.database.ref('users/{userId}/verify/status')
.onCreate(event => {
const sender = client.registerUser({
countryCode: 'US',
email: 'test#tester.com',
phone: '4035555555'
}).then( response => {
return response.user.id;
}).then( authyId => {
return client.requestSms({ authyId: authyId });
}).then( response => {
console.log(`SMS requested to ${response.cellphone}`);
}).catch( error => {
console.error(error.code);
console.error(error.message);
throw error;
});
});
Let me know if that helps at all.

Resources