Search code examples
javascriptnode.jsfluttergoogle-cloud-functionsbraintree

Https callable cloud function not returning value


I have a Flutter app and I'm trying to get a client nonce from braintree. Per the braintree documentation, I have this in my cloud function:

exports.getClientNonce = functions.https.onCall(async (data, context) => {
    gateway.clientToken.generate({}, function (err, response) {
        if (err) {
            throw new functions.https.HttpsError('unknown', 'Error getting client nonce');
        } else {
            console.log(`token: ${response.clientToken}`);
            return response.clientToken;
        }
    });
});

Then, in my Flutter app I call the function (again, I'm following what the plugin says):

try {
    HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(
        functionName: 'getClientNonce',
    );

    dynamic result = await callable.call({});
    final value = result.data;  

    debugPrint('token: $value');

    var data = await BraintreePayment().showDropIn(
        nonce: value,
        amount: '2.0',
        enableGooglePay: false,
        inSandbox: true);
        print("Response of the payment $data");
    } on CloudFunctionsException catch (e) {
        debugPrint('An error occurred');
    } catch (e) {
        debugPrint('An error occurred');
    }
}

I tried changing the cloud function so that it only returns a random number (as soon as the function is executed), and my Flutter app is correctly receiving the value (so the cloud function is communicating fine). And in my Firebase console, I am able to view the client nonce specified by console.log. But the function is for whatever reason unable to return the actual client nonce. (It should be should be some string hash that is >2000 characters long)


Solution

  • The callable function needs to return a promise from the top-level of the function callback that resolves with the value to return. Right now, you're returning nothing from the top-level. The return you have now is just returning a value from the inner callback function that you pass to braintree API. This isn't going to propagate to the top level.

    What you need to do is either use a version of braintree API that returns an API (if one exists), or promisify the existing call that uses a callback.

    See also "3. Node style callback" here: How do I convert an existing callback API to promises?

    I have not tested this, but the general format if you apply that pattern will look more like this:

    exports.getClientNonce = functions.https.onCall(async (data, context) => {
        return new Promise((resolve, reject) => {
            gateway.clientToken.generate({}, function (err, response) {
                if (err) {
                    reject(new functions.https.HttpsError('unknown', 'Error getting client nonce'));
                } else {
                    console.log(`token: ${response.clientToken}`);
                    resolve(response.clientToken);
                }
            });
        });
    });