Using HtmlElementView With PayPal

Issue

I have a Flutter Web project. I have the following HTML script that I need to put into HtmlElementView:

<script src="https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID"></script>
<script>paypal.Buttons().render('body');</script>

from here. I have tried this:

Widget build(BuildContext context) {
    // ignore: undefined_prefixed_name
    ui.platformViewRegistry.registerViewFactory(
      'paypal-button',
        (int viewId) => IFrameElement()
        ..width = '500'
        ..height = '500'
        ..src = '''
        <div id="paypal-button-container"></div>
        <script src="https://paypal.com/sdk/js?client-id=MY_CLIENT_ID"></script>
        <script>
        paypal.Buttons({

            // Set up the transaction
            createOrder: function(data, actions) {
                return actions.order.create({
                    purchase_units: [{
                        amount: {
                            value: '0.01'
                        }
                    }]
                });
            },

            // Finalize the transaction
            onApprove: function(data, actions) {
                return actions.order.capture().then(function(details) {
                    // Show a success message to the buyer
                    alert('Transaction completed by ' + details.payer.name.given_name + '!');
                });
            }


        }).render('#paypal-button-container');
    </script>
        '''
    );
    return SizedBox(
      height: 300,
      width: 500,
      child: Center(
        child: HtmlElementView(
          viewType: 'paypal-button',
        ),
      ),
    );
  }

Note: This code is used from https://developer.paypal.com/demo/checkout/#/pattern/client

This code doesn’t show any buttons and gives the following errors:

Bad state: Future already completed

Resource requests whose URLs contained both removed whitespace (\n, \r, \t) characters and less-than characters (<) are blocked. Please remove newlines and encode less-than characters from places like element attribute values in order to load these resources. See https://www.chromestatus.com/feature/5735596811091968 for more details.

What I need to know is how can I display this code snippet as a Widget in order for my Flutter Web project to be able to accept Paypal payments. Thanks for any help!

Solution

Currently, the only way to integrate PayPal buttons into flutter web is to wrap them in an IFrame container:

class PayPalWidget extends StatefulWidget {
  @override
  _PayPalState createState() => _PayPalState();
}

class _PayPalState extends State<PayPalWidget> {
  html.IFrameElement _element;

  @override
  void initState() {
    _element = html.IFrameElement()
      ..width = "200px"
      ..height = "200px"
      ..style.border = 'none'
      ..srcdoc = """
        <!DOCTYPE html>
        <html>
          <body>
            <script src="https://www.paypal.com/sdk/js?client-id=sb"></script>
            <script>
              paypal.Buttons(
                {
                  createOrder: function(data, actions) {
                    return actions.order.create({
                      purchase_units: parent.purchase_units
                    });
                  },
                  onApprove: function(data, actions) {
                    return actions.order.capture().then(function(details) {
                      parent.flutter_feedback('Transaction completed by ' + details.payer.name.given_name);
                    });
                  }
                }

              ).render('body');
            </script>
          </body>
        </html>
        """;

    js.context["purchase_units"] = js.JsObject.jsify([
      {
        'amount': {'value': '0.02'}
      }
    ]);
    js.context["flutter_feedback"] = (msg) {
      Scaffold.of(context).showSnackBar(SnackBar(content: Text(msg)));
    };

    // ignore:undefined_prefixed_name
    ui.platformViewRegistry.registerViewFactory(
      'PayPalButtons',
      (int viewId) => _element,
    );

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 220,
      height: 220,
      child: HtmlElementView(viewType: 'PayPalButtons'),
    );
  }
}

But keep in mind that this method is far from ideal, since the IFrame is re-created every time the widget is updated. I added code to demonstrate this downside effect:

import 'dart:ui' as ui;
import 'dart:js' as js;
import 'package:universal_html/html.dart' as html;
import 'package:flutter/material.dart';

class NextLabPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          leading: BackButton(),
          title: Text('PayPal integration'),
          centerTitle: true,
        ),
        body: Center(
          child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Noise(),
                PayPalWidget(),
              ]),
        ),
      ),
    );
  }
}

class Noise extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: RaisedButton(
        child: Text('Make Noise'),
        onPressed: () {
          Scaffold.of(context).showSnackBar(
            SnackBar(
              content:
                  Text("PayPal buttons will be re-created when I disappear"),
            ),
          );
        },
      ),
    );
  }
}

// PayPalWidget code
// ...

Push ‘Make sound’ button to show snackbar – when message disappears, the buttons disappear too. Or hover your mouse over the “back arrow” in the AppBar and move away to show the same effect.

Answered By – Spatz

Answer Checked By – Pedro (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *