Firebase Functions error when calling Stripe - javascript

I'm an iOS developer with very little experience in both Javascript and server code so I'm a little lost here.
I'm getting an error when I create a new user in firebase and trigger a function to create a new user in stripe. Here is my firebase function straight from Stripe's docs.
exports.createStripeCustomer = functions.auth.user().onCreate(async (user) => {
const customer = await stripe.customers.create({email: user.email});
return admin.firestore().collection('stripe_customers').doc(user.uid).set({customer_id: customerId});
});
I successfully create a new user in Stripe with a customer ID.
I get is this error in my firebase logs and don't capture the customer ID so I can save it in firestore.
I'm not sure what I'm doing wrong or how to interpret this message. Any pointers would be greatly appreciated.
createStripeCustomer
ReferenceError: customerId is not defined at exports.createStripeCustomer.functions.auth.user.onCreate (/srv/index.js:120:93) at <anonymous> at process._tickDomainCallback (internal/process/next_tick.js:229:7)
I have also tried this return changing customerId to ID
return admin.firestore().collection('stripe_customers').doc(user.uid).set({customer_id: ID});

It looks like, from the documentation, that the response object contains an id property. Perhaps you meant to write this line instead:
return admin.firestore()
.collection('stripe_customers')
.doc(user.uid)
.set({customer_id: customer.id}); // use the ID property here

Here is where I finally ended up. I used a promise instead of await.
exports.createStripeCustomer = functions.auth.user().onCreate(async (user) => {
const stripePromise = new Promise((resolve, reject) => {
stripe.customers.create({
email: user.email
}, (err, customer) => {
if (err) {
reject(err)
} else {
resolve(customer);
stripePromise
.then(customer => {
return admin.firestore()
.collection('stripe_customers')
.doc(user.uid)
.set({customer_id: customer.id});
})
.catch(error => {
console.log(`error resolving promise ${error}`)
})
}
});
})
});

Related

How to prevent Firebase Cloud Function from crashing and how to send error message as response?

I've created a simple createUser function which is executed on call. I have one problem though. The function is crashing when the user is trying to register with an already existing email. I mean, it's ok, since no one wants to have 2 users with the same email address but I want to prevent crushing function, instead, I want to send an error message as a response.
export const createUserTest = functions.https.onCall((data, context) => {
const {email, password} = data;
return new Promise((resolve, reject)=>{
try{
admin
.auth()
.createUser({
email: email,
emailVerified: false,
password: password,
disabled: false,
})
.then((user) => {
resolve({
result: 'success',
user: user,
}) ;
})
.catch((error) => {
reject(error) ;
});
}catch(error) {
reject (error)
}
})
});
I tried to put the function in to try/catch block but it didn't help. Do you have an idea of how I can achieve my goal?
As explained in the doc for Callable Cloud Functions, "to ensure the client gets useful error details, return errors from a callable by throwing (or returning a Promise rejected with) an instance of functions.https.HttpsError".
The error has a code attribute that can be one of the values listed here. In your case, the most appropriate seems to be already-exists.
On, the other hand, you'll find here the Admin SDK Authentication errors list and you'll see that in case the provided email is already in use by an existing user the error code is auth/email-already-exists.
So you can adapt your code as follows:
export const createUserTest = functions.https.onCall((data, context) => {
const { email, password } = data;
return admin
.auth()
.createUser({
email: email,
emailVerified: false,
password: password,
disabled: false,
})
.then((user) => {
return {
result: 'success',
user: user,
}
})
.catch((error) => {
if (error.code === 'auth/email-already-exists') {
throw new functions.https.HttpsError('already-exists', 'The provided email is already in use by an existing user');
} else {
throw new functions.https.HttpsError('...other code....', '...');
// If an error other than HttpsError is thrown, your client instead receives an error with the message INTERNAL and the code internal.
}
});
});
See here in the doc, how to handle errors on the client side. If error.code == 'already-exists' you know that it's because the email is already in use.

Adding document to a collection within .then of firebase auth.createUserWithEmailAndPassword

With VueJS and Firebase, I want to create a user and then if it succeed add more info to a users collection.
Problem is my variable usersCollection is undefined when I get in the .then. I know I can take that exact code out of the .then and it works. Also, the auth function works as it is supposed to. It would seem that the problem is that I'm trying to access the collection inside the .then. But then again, I need to do this only if I successfully create a new user for authentication to avoid having users info from unregistered users. I don't enter the .catch either and I don't get an error of any kind in the chrome console. Any idea how to get this logic to work?
I initialize everything about firebase with this :
import * as firebase from 'firebase/app'
import 'firebase/auth'
import 'firebase/firestore'
import 'firebase/analytics'
const firebaseConfig = {
//configs
};
firebase.initializeApp(firebaseConfig);
firebase.analytics();
const db = firebase.firestore();
const auth = firebase.auth();
const usersCollection = db.collection('users');
export {
db,
auth,
usersCollection
}
The code is located in the main store of the app :
import * as types from './types';
import {
auth,
usersCollection,
} from '../../../../config/firebase';
//...
[types.ADD]: ({commit}, user) => {
auth.createUserWithEmailAndPassword(user.email, user.password)
.then((e) => {
usersCollection.add(user)
.then((docRef) => {
commit(types.MUTATE_ADD, user);
console.log("Document written with ID: ", docRef.id);
})
.catch((error) => {
console.error("Error adding document: ", error);
});
})
.catch((e) => {
//...
alert('An error occured while creating employee.\n' + e.code + '\n' + e.message);
return false;
});
}
Above, the the user I use for authentication is created, but when I get to the .then usersCollection is undefined, yet I get no error in the Chrome console and the user is not created.
As explained earlier, if I take the block where I add the user to the collection out of the .then I get to add the user to the collection :
[types.ADD]: ({commit}, user) => {
auth.createUserWithEmailAndPassword(user.email, employeeHelper.makePassword(user))
.then((e) => {
})
.catch((e) => {
var errorCode = e.code;
var errorMessage = e.message;
alert('An error occured while creating employee.\n' + e.code + '\n' + e.message);
return false;
});
usersCollection.add(user)
.catch((error) => {
console.error("Error adding document: ", error);
});
}
Using another method made it work exactly as I intended :
[types.ADD]: ({commit}, user) => {
commit(types.MUTATE_ADD, user);
auth.createUserWithEmailAndPassword(user.email, employeeHelper.makePassword(user))
.then((e) => {
usersCollection.doc(user.email).get().then((querySnapshot) => {
querySnapshot.ref.set(user).then(() => {
//log success
})
}).catch((e) => {
console.log(e);
//log error
})
})
.catch((e) => {
//log error
return false;
});
}
The difference is that instead of using .add() method on my usersCollection, I used .doc(user.email).get().then(...) and I set data afterwards instead of using .add(...). For some reason, the Chrome console still shows usersCollection as if it is undefined if I put a breakpoint there :
usersCollection.doc(user.email).get().then((querySnapshot) => {
But the data is properly pushed to firestore nonetheless. So I'm not completely comfortable with the fact that I don't know why it works this way but not the other, but the result is exactly what I needed even though I suspect it creates some overhead.

Receiving [Error: Internal] in RN app when triggering a Cloud Function

I have a Google Cloud Function which I am calling from my RN app but it is returning
[Error: Internal]
I have set the permission to Unauthenticated users so anyone can call it - for testing purposes only. When I set to Authenticated users permission, it throws another error [Error: Unauthenticated] eventhough I am authenticated and I can get the currentUser id in my app.
Tried searching for this error but it didnt send me to any possible solutions so decided to post here and hopefully recieve responses that will help me fix it.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.createUser = functions.region('europe-west1').https.onCall(async (data, context) => {
try {
//Checking that the user calling the Cloud Function is authenticated
if (!context.auth) {
throw new UnauthenticatedError('The user is not authenticated. Only authenticated Admin users can create new users.');
}
const newUser = {
email: data.email,
emailVerified: false,
password: data.password,
disabled: false
}
const role = data.role;
const userRecord = await admin
.auth()
.createUser(newUser);
const userId = userRecord.uid;
const claims = {};
claims[role] = true;
await admin.auth().setCustomUserClaims(userId, claims);
return { result: 'The new user has been successfully created.' };
} catch (error) {
if (error.type === 'UnauthenticatedError') {
throw new functions.https.HttpsError('unauthenticated', error.message);
} else if (error.type === 'NotAnAdminError' || error.type === 'InvalidRoleError') {
throw new functions.https.HttpsError('failed-precondition', error.message);
} else {
throw new functions.https.HttpsError('internal', error.message);
}
}
});
in my RN app I am calling it like this:
var user = {
role: role
}
const defaultApp = firebase.app();
const functionsForRegion = defaultApp.functions('europe-west1');
const createUser = await functionsForRegion.httpsCallable('createUser');
createUser(user)
.then((resp) => {
//Display success
});
console.log(resp.data.result);
})
.catch((error) => {
console.log("Error on register patient: ", error)
});
I think the way I am calling it in my RN app is correct because I have tested it with a testFunction and I returned a simple string. So, I believe the problem is somewhere in the function itself.
EDIT: I just tested by simply calling the function and returning the context and it always returns Internal error:
exports.registerNewPatient = functions.region('europe-west3').https.onCall((data, context) => {
return context; //this is returned as INTERNAL error.
}
I just cant get to understand whats going on here, why does it return Internal error when I am authenticated as a user and it should return the authenticated user data, isn't that right?
Try some console.log(context) ; console.log(data) statements in your registerNewPatient function and take a look at the logs. What do they say?
Some other things to consider might include that in your client code you use europe-west1 while your function code has europe-west3. Try to have those line up and see if it works? From my experience, if a specified function isn't found to exist, the client receives an INTERNAL error.

Storing user in database within Firestore

I am working with Firebase and I'm having some troubles. When creating a new user, I am able to store it in my database, but later, when accessing to another component it fails.
//Register a new user in our system
registerUserByEmail(email: string, pass: string) {
return this.afAuth.auth
.createUserWithEmailAndPassword(email, pass)
.then(res => {
this.email = res.user.email;
let user = {
email: this.email,
goal: "",
previousGoals: [],
progress: {
accomplishedToday: false,
completedGoals: 0,
daysInRow: 0,
unlockedBadges: []
}
};
// store in database
new Promise<any>((resolve, reject) => {
this.firestore
.collection("users")
.add(user)
.then(
res => {
console.log(res.id);
this.isAuthenticated = true;
this.router.navigate(["/dashboard"]);
},
err => reject(err)
);
});
});
}
I believe that this piece of code is basically registering as a user the email and storing it successfully into my database (checked it).
Nevertheless, when rendering home.component or /dashboard
home.component
ngOnInit() {
this.setAuthStatusListener();
this.getUser();
}
getUser() {
this.data.getUser().subscribe(user => {
this.user = user.payload.data();
});
}
data.service
getUser() {
return this.firestore
.collection("users")
.doc(this.currentUser.uid)
.snapshotChanges();
}
I get the following error
ERROR
TypeError: Cannot read property 'uid' of null
It looks like by the time you call getUser the user hasn't been authenticated yet.
The simple fix to get rid of the error is to check for this condition in your DataService's getUser:
getUser() {
if (this.currentUser) {
return this.firestore
.collection("users")
.doc(this.currentUser.uid)
.snapshotChanges();
}
}
Given this sequence of calls however, I think there may be a better way to handle your use-case:
ngOnInit() {
this.setAuthStatusListener();
this.getUser();
}
Since you're attaching an auth state listener, you probably want to only start watching the user's data in Firestore once the user has actually been authenticated.
Once simple way to do that is to call out to your DataService's getUser() method from within the auth state listener, once the user is authenticated. Something like this:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
this.getUser(); // call the DataService's getUser method
}
});

Cannot parse Firebase url. Please use https://<YOUR FIREBASE>.firebaseio.com

While i click on the login button i get this error :
[19:49:11] [2018-12-25T20:49:57.389Z] #firebase/database:, FIREBASE
FATAL ERROR: Cannot parse Firebase url. Please use https://<YOUR
FIREBASE>.firebaseio.com
- node_modules/#firebase/logger/dist/index.cjs.js:69:32 in
defaultLogHandler
- node_modules/#firebase/logger/dist/index.cjs.js:159:31 in error
- node_modules/#firebase/database/dist/index.cjs.js:333:20 in fatal
- node_modules/#firebase/database/dist/index.cjs.js:1256:14 in
parseRepoInfo
- node_modules/#firebase/database/dist/index.cjs.js:15103:38 in
refFromURL
* src/modules/auth/api.js:24:24 in getUser
* src/modules/auth/api.js:19:32 in <unknown>
- node_modules/#firebase/auth/dist/auth.js:17:105 in <unknown>
- node_modules/#firebase/auth/dist/auth.js:20:199 in Fb
- ... 13 more stack frames from framework internals
I copied and pasted the config stuff directly from Firebase, so it should be correct, but I get this error anyway. What could be causing this? Is there any way the URL I'm copying from my database could be wrong somehow?
As you you can see in the error shown are in my file api.js in
.then((user) => getUser(user, callback))
and in
database.refFromURL('users').child(user.uid).once('value')
So here is my code from api.js is like this :
import { auth, database, provider } from "../../config/firebase";
export function register(data, callback) {
const { email, password } = data;
auth.createUserWithEmailAndPassword(email, password)
.then((user) => callback(true, user, null))
.catch((error) => callback(false, null, error));
}
export function createUser (user, callback) {
database.refFromURL('users').child(user.uid).update({ ...user })
.then(() => callback(true, null, null))
.catch((error) => callback(false, null, {message: error}));
}
export function login(data, callback) {
const { email, password } = data;
auth.signInWithEmailAndPassword(email, password)
.then((user) => getUser(user, callback))
.catch((error) => callback(false, null, error));
}
export function getUser(user, callback) {
database.refFromURL('users').child(user.uid).once('value')
.then(function(snapshot) {
const exists = (snapshot.val() !== null);
if (exists) user = snapshot.val();
const data = { exists, user }
callback(true, data, null);
})
.catch(error => callback(false, null, error));
}
can anyone please help where i missed up
i used
database.ref(`users/`+user.uid).once('value')
instead of
database.refFromURL('users').child(user.uid).once('value')
and it works fine for me now.
Please go through this documentation and update to new modular type or if you want to use old structure then, update to
<script src="https://www.gstatic.com/firebasejs/8.5.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.5.0/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.5.0/firebase-database.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.5.0/firebase-storage.js"></script>
update all to version 8.5.0. Will work flawless
The refFromURL method expects a fully qualified URL to the database. So something starting with https://<YOUR
FIREBASE>.firebaseio.com as the error message shows.
You're trying to access a path within the configured database, in which case you should use ref(...) instead:
database.ref('users').child(user.uid).once('value')
I think there are mainly two types of realtime db urls , one ends with ".firebaseio.com" which is for US and other like EU and asia have url which ends with "firebasedatabase.app"
"Please use https://.firebaseio.com", this error comes at line when u call firebase.database(), It can happen that firebase library or module you are using are of old versions which can only make call for db whose url ends with firebaseio.com,
so make sure to update it,
or you can just change the region of your realtime database to US region.

Categories

Resources