Calling cloud function admin.auth().createUser() from android does not work - android

I am trying to call a Google cloud function from an Android app that does not work (First call just after deployment works 90 % of the times but subsequent calls fails, nothing is displayed on firebase log console either).
public Task<String> myCloudFunction() {
return FirebaseFunctions.getInstance()
.getHttpsCallable("createUser")
.call(data)
.continueWith(task -> {
String result = (String) task.getResult().getData();
return result;
});
}
Endpoint in Functions Dashboard
https://us-central1-xyz:555.cloudfunctions.net/createUser
This is how I call it.
public void callCloudFunction() {
createFirebaseUserAccount.myCloudFunction()
.addOnCompleteListener(new OnCompleteListener<String>() {
#Override
public void onComplete(#NonNull Task<String> task) {
if (!task.isSuccessful()) {
Exception e = task.getException();
if (e instanceof FirebaseFunctionsException) {
FirebaseFunctionsException ffe = (FirebaseFunctionsException) e;
FirebaseFunctionsException.Code code = ffe.getCode();
Object details = ffe.getDetails();
} else {
Timber.d(task.getResult());
}
}
}
});
}
Here is the cloud function:
$GOOGLE_APPLICATION_CREDENTIALS is pointing to service_key.json file which contains the private key.
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: "https://XYZ.firebaseio.com"
});
exports.createUser = functions.https.onCall((data, context) => {
const callerEmail = data.email;
const callerPassword = data.password;
const callerDisplayName = data.displayName;
return admin.auth().createUser({
email: callerEmail,
emailVerified: false,
password: callerPassword,
displayName: callerDisplayName,
disabled: false
}).then(userRecord => {
// See the UserRecord reference doc for the contents of userRecord.
console.log("Successfully created new user:", userRecord.uid);
return userRecord.uid;
}).catch(error => {
console.log("Error creating new user ", error);
return error;
});
});
Thanks for reading! :)

You're not returning a promise from the the function that contains the data to send to the client. Actually, you're not passing anything at all. You should instead return the promise chain from your async work:
return admin.auth().createUser({
email: callerEmail,
emailVerified: false,
password: callerPassword,
displayName: callerDisplayName,
disabled: false
}).then(userRecord => {
// See the UserRecord reference doc for the contents of userRecord.
console.log("Successfully created new user:", userRecord.uid);
return userRecord.uid;
}).catch(error => {
console.log("Error creating new user ", error);
return error;
});
Note the new return before the whole thing. You should definitely take some time to learn about how JavaScript promises work in order to make effective use of Cloud Functions, and they will not work correctly without observing their rules and conventions.

Related

Return data to Android from Firebase Function [duplicate]

This question already has answers here:
Why does my function that calls an API or launches a coroutine return an empty or null value?
(4 answers)
Closed 1 year ago.
What I am trying to do: Simply return data from Firebase Cloud Function.
The function is used to create a payment order in the payment gateway's server.
My required data about the order's details are present in the function(err,data) (see below), but I need this data sent back to my Android app.
Problem I faced: I could see the data printed in the Firebase console's log but it doesn't return to my Android app.
My Firebase Cloud Function:
const functions = require("firebase-functions");
exports.order = functions.https.onCall((amnt, response) => {
const Ippopay = require('node-ippopay');
const ippopay_instance = new Ippopay({
public_key: 'YOUR_PUBLIC_KEY',
secret_key: 'YOUR_SECRET_KEY',
});
ippopay_instance.createOrder({
amount: amnt,
currency: 'DOLLAR',
payment_modes: "cc,dc,nb,cheque",
customer: {
name: "Test",
email: "test#gmail.com",
phone: {
country_code: "42",
national_number: "4376543210"
}
}
}, function (err, data) {
return data.order.order_id;
});
});
My Android client-side code:
public class Payment extends AppCompatActivity implements IppoPayListener {
Button pay;
EditText amount;
private FirebaseFunctions mFunctions;
TextView order_data;
String data;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_payment);
}
#Override
protected void onPostCreate(#Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
pay=findViewById(R.id.pay_button);
amount=findViewById(R.id.user_amount);
order_data=findViewById(R.id.data_text);
pay.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d("PAY Button clicked", "yes");
mFunctions = FirebaseFunctions.getInstance("us-central1");
mFunctions.getHttpsCallable("order").call(5).continueWith(new Continuation<HttpsCallableResult, Object>() {
#Override
public Object then(#NonNull Task<HttpsCallableResult> task) throws Exception {
HttpsCallableResult result=task.getResult();
if(result !=null)
{
data=result.getData().toString();
return result.getData().toString();
}
return null;
}
});
order_data.setText(data);
onPaymentClick();
}
});
}
/* ... */
}
I'm a Beginner so there's a high possibility of some dead silly mistakes. :)
Based on what your code looks like at the moment, you have a mix of code from a Callable Cloud Function and the older HTTP Request Cloud Function.
To return data from a callable Cloud Function, you should return a Promise, a method of running asynchronous code that returns a value. Older JavaScript and many other languages use callbacks instead, which is what you have here.
In it's simplest form, this callback-based method:
someModule.doSomething(input, function (err, result) {
// check for errors and handle result
});
would be converted to use Promises using:
new Promise((resolve, reject) => {
someModule.doSomething(
input,
(err, result) => err ? reject(err) : resolve(result) // short form of "if error, reject with an error, otherwise resolve (succeed) with result"
)
});
For errors to be handled correctly by clients, you need to wrap any errors in a functions.https.HttpsError.
Combining this together gives:
const functions = require("firebase-functions");
exports.order = functions.https.onCall((amnt, context) => {
const Ippopay = require('node-ippopay');
return new Promise((resolve, reject) => {
const ippopay_instance = new Ippopay({
public_key: 'YOUR_PUBLIC_KEY',
secret_key: 'YOUR_SECRET_KEY',
});
ippopay_instance.createOrder({
amount: amnt,
currency: 'DOLLAR',
payment_modes: "cc,dc,nb,cheque",
customer: {
name: "Test",
email: "test#gmail.com",
phone: {
country_code: "42",
national_number: "4376543210"
}
}
}, function (err, data) {
if (err) {
// something went wrong, send error back to caller
reject(new functions.https.HttpsError('unknown', 'Ippopay threw an unexpected error', err));
return;
}
// successful, send data back to caller
resolve(data.order.order_id);
});
});
});
You should also make sure you make use of context.auth to restrict access to this function. You wouldn't want to bill the wrong customer.

How to get data returned from Cloud Functions on Android

I am trying to authenticate an user with a custom token using cloud functions. The code for the token generation is:
export const test = functions.https.onCall(() => {
const uid = 'test_uid'
admin.auth().createCustomToken(uid)
.then((customtoken) => {
console.log(customtoken)
return customtoken
}).catch((error) => {
console.log(error)
})
})
The code on the client side is:
private void getmessage() {
FirebaseFunctions.getInstance()
.getHttpsCallable("test")
.call()
.addOnCompleteListener(this, new OnCompleteListener<HttpsCallableResult>() {
#Override
public void onComplete(#NonNull Task<HttpsCallableResult> task) {
if(task.isSuccessful()){
Toast.makeText(getApplicationContext(), task.getResult().getData().toString(), Toast.LENGTH_LONG).show();
}else{
Toast.makeText(getApplicationContext(), "Task is NOT Successful", Toast.LENGTH_LONG).show();
}
}
});
}
The token is successfully logged in the console, but returns null value on the client side. Is there something which I am doing wrong?
A callable function needs to return a promise that resolves when the async work is complete. That promise should resolve with the data to send to the client. Right now, your function is returning nothing.
Try this instead:
return admin.auth()
.createCustomToken(...)
.then(...)
.catch(...)

Firebase cloud function gets triggered twice. First time 'error', second time 'ok'

Just trying to impletment Stripe Payment into my Android App.
The trouble i have is that my cloud function is triggered twice when i enter a credit card in my app. the first trigger returns an "error" status and the second trigger returns an "ok" status
Here is the code i use to save the Stripe token to my firebase realtime database:
if (cardToSave != null) {
stripe.createToken(
cardToSave,
object:TokenCallback {
override fun onSuccess(token: Token?) {
val currentUser = FirebaseAuth.getInstance().currentUser?.uid
val database = FirebaseDatabase.getInstance()
val pushId = database.getReference("stripe_customers/$currentUser/sources/").push().key
val ref = database.getReference("stripe_customers/$currentUser/sources/$pushId/token/")
//save the token id from the "token" object we received from Stripe
ref.setValue(token?.id)
.addOnSuccessListener {
Log.d(TAG, "Added Stripe Token to database successfully")
}
.addOnFailureListener {
Log.d(TAG, "Failed to add Token to database")
}
}
...
Here is the cloud function i copied straight from Stripe's example in their github repo:
// Add a payment source (card) for a user by writing a stripe payment source token to Realtime database
exports.addPaymentSource = functions.database
.ref('/stripe_customers/{userId}/sources/{pushId}/token').onWrite((change, context) => {
const source = change.after.val();
if (source === null){
return null;
}
return admin.database().ref(`/stripe_customers/${context.params.userId}/customer_id`)
.once('value').then((snapshot) => {
return snapshot.val();
}).then((customer) => {
return stripe.customers.createSource(customer, {source});
}).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});
});
});
Any help would be appreciated!
EDIT:
index.js
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const logging = require('#google-cloud/logging');
//functions.config() is firebase's environment variables
const stripe = require('stripe')(functions.config().stripe.token);
const currency = functions.config().stripe.currency || 'USD';
// [START chargecustomer]
// Charge the Stripe customer whenever an amount is written to the Realtime database
exports.createStripeCharge = functions.database.ref('/stripe_customers/{userId}/charges/{id}')
.onCreate((snap, context) => {
const val = snap.val();
// Look up the Stripe customer id written in createStripeCustomer
return admin.database().ref(`/stripe_customers/${context.params.userId}/customer_id`)
.once('value').then((snapshot) => {
return snapshot.val();
}).then((customer) => {
// Create a charge using the pushId as the idempotency key
// protecting against double charges
const amount = val.amount;
const idempotencyKey = context.params.id;
const charge = {amount, currency, customer};
if (val.source !== null) {
charge.source = val.source;
}
return stripe.charges.create(charge, {idempotency_key: idempotencyKey});
}).then((response) => {
// If the result is successful, write it back to the database
return snap.ref.set(response);
}).catch((error) => {
// We want to capture errors and render them in a user-friendly way, while
// still logging an exception with StackDriver
return snap.ref.child('error').set(userFacingMessage(error));
}).then(() => {
return reportError(error, {user: context.params.userId});
});
});
// [END chargecustomer]]
// When a user is created, register them with Stripe
exports.createStripeCustomer = functions.auth.user().onCreate((user) => {
return stripe.customers.create({
email: user.email,
}).then((customer) => {
return admin.database().ref(`/stripe_customers/${user.uid}/customer_id`).set(customer.id);
});
});
// Add a payment source (card) for a user by writing a stripe payment source token to Realtime database
exports.addPaymentSource = functions.database
.ref('/stripe_customers/{userId}/sources/{pushId}/token').onWrite((change, context) => {
const source = change.after.val();
if (source === null){
return null;
}
return admin.database().ref(`/stripe_customers/${context.params.userId}/customer_id`)
.once('value').then((snapshot) => {
return snapshot.val();
}).then((customer) => {
return stripe.customers.createSource(customer, {source:source});
}).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});
});
});
// When a user deletes their account, clean up after them
exports.cleanupUser = functions.auth.user().onDelete((user) => {
return admin.database().ref(`/stripe_customers/${user.uid}`).once('value').then(
(snapshot) => {
return snapshot.val();
}).then((customer) => {
return stripe.customers.del(customer.customer_id);
}).then(() => {
return admin.database().ref(`/stripe_customers/${user.uid}`).remove();
});
});
// To keep on top of errors, we should raise a verbose error report with Stackdriver rather
// than simply relying on console.error. This will calculate users affected + send you email
// alerts, if you've opted into receiving them.
// [START reporterror]
function reportError(err, context = {}) {
// This is the name of the StackDriver log stream that will receive the log
// entry. This name can be any valid log stream name, but must contain "err"
// in order for the error to be picked up by StackDriver Error Reporting.
const logName = 'errors';
const log = logging.log(logName);
// https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/MonitoredResource
const metadata = {
resource: {
type: 'cloud_function',
labels: {function_name: process.env.FUNCTION_NAME},
},
};
// https://cloud.google.com/error-reporting/reference/rest/v1beta1/ErrorEvent
const errorEvent = {
message: err.stack,
serviceContext: {
service: process.env.FUNCTION_NAME,
resourceType: 'cloud_function',
},
context: context,
};
// Write the error log entry
return new Promise((resolve, reject) => {
log.write(log.entry(metadata, errorEvent), (error) => {
if (error) {
return reject(error);
}
return resolve();
});
});
}
// [END reporterror]
// Sanitize the error message for the user
function userFacingMessage(error) {
return error.type ? error.message : 'An error occurred, developers have been alerted';
}

How to pass an object from android app to a firebase cloud function to complete Paypal payment functions?

I am using firebase cloud functions as serverside for Paypal payment. Documentations are not obvious to understand.
when I am trying to send an object from android app to firebase cloud functions, nothing has happened. I think I added it wrong. so how can I pass an object from android app to the function??
public void payout(String PayerID,String paymentId) {
// Create the arguments to the callable function.
JSONObject postData = new JSONObject();
try {
postData.put("PayerID", PayerID);
postData.put("paymentId",paymentId);
} catch (JSONException e) {
e.printStackTrace();
}
mFunctions
.getHttpsCallable("payout")
.call(postData)
.continueWith(new Continuation<HttpsCallableResult, Object>() {
#Override
public Object then(#NonNull Task<HttpsCallableResult> task)
throws Exception {
return null;
}
});
}
///////////////////////////////////////////
exports.payout=functions.https.onRequest((req,res)=>{
const sender_batch_id = Math.random().toString(36).substring(9);
const payReq=JSON.stringify({
sender_batch_header: {
sender_batch_id: sender_batch_id,
email_subject: "You have a nice payment"
},
items: [
{
recipient_type: "EMAIL",
amount: {
value: 0.90,
currency: "USD"
},
receiver: "amrmahmoudM#app.com",
note: "Thank you very much.",
sender_item_id: "item_3"
}
]
});
paypal.payout.create(payReq,(error, payout)=>{
if (error) {
console.warn(error.res);
res.status('500').end();
throw error;
}else{
console.info("payout created");
console.info(payout);
res.status('200').end();
}
});
});
exports.process = functions.https.onRequest((req, res) => {
const paymentId = req.body.paymentId;
var payerId = {
payer_id: req.body.PayerID
};
return paypal.payout.execute(paymentId, payerId, (error, payout) => {
if (error) {
console.error(error);
} else {
if (payout.state === 'approved') {
console.info('payment completed successfully, description: ',
payout.transactions[0].description);
const ref=admin.firestore().collection("Users").doc(payerId);
ref.set({'paid': true});
} else {
console.warn('payment.state: not approved ?');
}
}
}).then(r =>
console.info('promise: ', r));
});
The problem comes from the fact that in your Android app you call an HTTPS Callable Function (via mFunctions.getHttpsCallable("payout")) but your Cloud Function is not an HTTPS Callable Function but a "simple" HTTPS Function.
HTTPS Callable Functions are written like:
exports.payout = functions.https.onCall((data, context) => {
// ...
});
while HTTPS Functions are written like:
exports.payout = functions.https.onRequest((req,res)=> {
// ...
})
So you should adapt the code of your Cloud Function according to the documentation: https://firebase.google.com/docs/functions/callable
Note that another option could be to write to the database (Real Time database or Firestore) and trigger the Cloud Function with an onWrite or onCreate trigger. The advantage of this approach is that you directly save the information of the payment in the database.

Getting “INTERNAL” exception when an async Firebase function is called from android app

I’m trying to call an async Firebase function from android app and getting “INTERNAL” exception when the function returns.
Android:
private Task<String> fetchData() {
// Create the arguments to the callable function, which is just one string
Map<String, Object> data = new HashMap<>();
data.put(“id”, “abc”);
return FirebaseFunctions.getInstance()
.getHttpsCallable(“calculate”)
.call(data)
.continueWith(new Continuation<HttpsCallableResult, String>() {
#Override
public String then(#NonNull Task<HttpsCallableResult> task) throws Exception {
Map<String, Object> result = (Map<String, Object>) task.getResult().getData();
return (String)result.get(“data”);
}
});
}
Firebase Function:
exports.calculate = functions.https.onCall((data, context) => {
const text = data.id;
return calc.calculate( (err, response) => {
if(err) {
// handle error
} else {
const data = response.dataValue;
}
}).then(() => {
return {“data”: data};
});
});
Exception:
com.google.firebase.functions.FirebaseFunctionsException: INTERNAL
The documentation for handling errors in callable functions indicates that an instance of functions.https.HttpsError must be returned:
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... If an error other than
HttpsError is thrown from your functions, your client instead receives
an error with the message INTERNAL and the code internal.
It seems likely that your calc.calculate() call is returning an error that is not being handled correctly, resulting in a returned error status of INTERNAL.
Following the example in the document linked above, your code should be something like:
if(err) {
// handle error
throw new functions.https.HttpsError('calc-error', 'some error message');
} else {
const data = response.dataValue;
}
When you call httpCallable ,you will get an exception called FirebaseFunctionsExceptions. You have to handled this exceptions. Wrap your code with try and catch .
Example:-
try {
final result = await FirebaseFunctions.instance
.httpsCallable('deleteUser')
.call({});
} on FirebaseFunctionsException catch (error) {
print(error.message);
}
For more info follow this link.

Categories

Resources