Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stripe - JSON Circular reference

Im using Stripe Checkout.js to create a payment. I'm creating a handler, that on success sends a token to the server:

let handler = StripeCheckout.configure({
                key: 'my_key',
                image: 'image.png',
                locale: 'auto',
                token: token => {

                    console.log(token.id);

                    // ... send token to server
                }
            });

Then I use the handler to create a token:

handler.open({
    name: 'Test',
    description: 'test',
    billingAddress: false,
    currency: 'eur',
    amount: '1200',
});

This handler triggers the test checkout.js popup, that I fill in and click Pay. It ends successfully, meaning the button displays green.

But between the moment the button shows green, and the moment that the token is printed to the console (on the handler success callback), an error is thrown:

EXCEPTION: TypeError: Converting circular structure to JSON

The main part of the stacktrace is this:

TypeError: Converting circular structure to JSON
    at Object.stringify (native)
    at Object.stringify (http://localhost:5000/dist/client/bundle.js:46294:29)
    at RPC.sendMessage (https://checkout.stripe.com/checkout.js:1:18068)
    at RPC.sendMessage (https://checkout.stripe.com/checkout.js:1:16180)
    at https://checkout.stripe.com/checkout.js:1:17137
    at RPC.ready (https://checkout.stripe.com/checkout.js:1:17416)
    at RPC.invoke (https://checkout.stripe.com/checkout.js:1:17084)
    at RPC.invoke (https://checkout.stripe.com/checkout.js:1:16180)
    at RPC.processMessage (https://checkout.stripe.com/checkout.js:1:18899)
    at RPC.processMessage (https://checkout.stripe.com/checkout.js:1:16180)

By inspecting the code, we see the issue is here:

        RPC.prototype.sendMessage = function(method, args) {
            var err, id, message, _ref;
            if (args == null ) {
                args = []
            }
            id = ++this.rpcID;
            if (typeof args[args.length - 1] === "function") {
                this.callbacks[id] = args.pop()
            }
            message = JSON.stringify({
                method: method,
                args: args,
                id: id
            });

It seems that Checkout.js creates a message object, that happens to have a circular reference, then it attempts to call JSON.stringify on it, which causes the error.

This error is non fatal and the payment goes through, but do you know what this could be and how to fix it?

Or is there a known workaround.

this is the full stack trace

Note that calling stringify before passing an object to postMessage might not be necessary according to the MDN docs.

postMessage uses a serialization mechanism that supports circular references according to this.

like image 646
Angular University Avatar asked Mar 28 '16 07:03

Angular University


1 Answers

The problem is in checkout.js (at line 780 in the de-minified file):

message = JSON.stringify({
    method: method, 
    args: args,
    id: id
});

When running in Angular2, zone.js has wrapped the callback with zone information. This means that the args parameter looks like:

[5, ZoneTask]

And, ZoneTask has a member, .zone, which contains a circular reference: zone -> _zoneDelegate -> zone

JSON.stringify(args[1].zone)
> Uncaught TypeError: Converting circular structure to JSON(…)

So, fundamentally Stripe's checkout.js makes a non-circularity assumption which is no longer valid when Zone.js has been meddling. Stripe are going to have to fix this.

In the meantime you can use the following horrendous hack. It's so horrendous and so hacky that it makes my eyes bleed, but it's the best I could come up with to fix it without modifying Stripe's code. Essentially, you globally replace JSON.stringify with a version that breaks any zone->_zoneDelegate->zone cycles in the object it is being asked to stringify:

const _stringify = JSON.stringify;
JSON.stringify = function (value, ...args) {
  if (args.length) {
    return _stringify(value, ...args);
  } else {
    return _stringify(value, function (key, value) {
      if (value && key === 'zone' && value['_zoneDelegate'] 
          && value['_zoneDelegate']['zone'] === value) {
        return undefined;
      }
      return value;
    });
  }
};  

Apologies if your eyes are now bleeding. It works.

like image 70
Rich Avatar answered Sep 22 '22 12:09

Rich