Search code examples
javascriptflutterwebpaymentbraintree

Flutter - waiting for JavaScript Promise (promiseToFuture)


I'm trying to integrate Braintree payments for my Flutter application. I have a plugin to handle iOS/Android and am writing a custom implementation for web.

It's a fairly simple solution I think, I'm using the Braintree Javascript SDK/Drop in UI (GitHub tutorial) to render the payment screen using HTML. This part I got working, however to properly process these payments I need to retrieve the response of the JavaScript function - specifically a string the payment nonce.

To do this i used some channel methods and dart magic to call this javascript function

async function payment(auth) {
 braintree.dropin.create({
   authorization: auth,
   selector: '#dropin-container'
 }, function (errCreate, instance) {
    document.getElementById("submit-button").addEventListener('click', function () {
        if(errCreate) {
            console.log("Error", errCreate);
            return;
        }
        instance.requestPaymentMethod(function (requestPaymentMethodErr, payload) {
            if (requestPaymentMethodErr) {
                console.log('Error', requestPaymentMethodErr);
                return;
            }
            return payload.nonce;
        });
    });
});
}

This function seems to be working, returning data and payment nonces and all that. In a (separate) dart file where I'm actually rendering the widget, I declared the function like so:

// EXTERNAL JAVASCRIPT ==========================================

@JS()
external void initBraintree(auth);

@JS()
external payment(String auth);

and this seems to be working, it's calling the function on button click so that's good. The issue is when I call this async function it returns a promise, theoretically dart should not have any idea how to handle this however, they thankfully have a method called promiseToFuture that should handle just this. I've used it like so:

  Future<BraintreeDropInResult> start(BuildContext context, BraintreeDropInRequest request) async {
    // create div with html embedded
    String htmlL = """<div id="checkout-message"></div>
        <div id="dropin-container"></div>
    <button id="submit-button">Submit payment</button>""";
    var paymentDiv = html.DivElement()..appendHtml(htmlL);

    // attach to payment container
    ui.platformViewRegistry.registerViewFactory('braintree-container', (int viewId) => paymentDiv);

    // call js function
    var promise = payment(request.clientToken);
    String nonce = await promiseToFuture(promise);
 ...

However it just does not work. The JavaScript function seems to be returning a promise object however the promiseToFuture function never waits for it, just instantly returns null everytime.

Been working on this a while and finally feel really close but this is being a real thorn in my side so any help is really appreciated!


Solution

  • You could do something like this. Ie create a promise, and once everything is ok -- and the button is clicked -- it will resolve with the nonce.

    Of course, this will only work for the first click of the button. Because, once a promise is either resolved or rejected, it won't change it's status and result anymore.

    async function payment(auth) {
      return new Promise((resolve, reject) => {
        braintree.dropin.create({
            authorization: auth,
            selector: '#dropin-container'
          },
          (createError, instance) => {
            //probably there is no need to attach the eventhandler
            //if the was an error 
            if (createError) {
              console.log("Error", createError);
              return reject(createError);
            }
            document.getElementById("submit-button").addEventListener("click", 
              () => {
                instance.requestPaymentMethod((rpmError, payload) => {
                  if (rpmError) {
                    console.log("Error", rpmError);
                    return reject(rpmError);
                  }
     
                  resolve(payload.nonce);
                });
              });
          }
        );
      });
    }