cancel
Showing results forย 
Search instead forย 
Did you mean:ย 

Dynamically loading Accept.js: E_WC_03 Accept.js is not loaded correctly

I am dynamically loading Accept.js only if it's needed during our checkout process. My initial approach was to just add the script to the <head> tag, listen for the script's load event, and then call Accept.dispatchData. Unfortunately, this results in E_WC_03: Accept.js is not loaded correctly.

 

Relevant code:

 

async function loadScript(source, charset) {
  let script = document.createElement('script');
  script.setAttribute('src', source);
  if (charset) {
    script.setAttribute('charset', charset);
  }

  let onLoad = null;
  let onError = null;
  try {
    return await new Promise((resolve, reject) => {
      onLoad = e => resolve(e);
      onError = e => reject(e);
      script.addEventListener('load', onLoad);
      script.addEventListener('error', onError);
      document.head.appendChild(script);
    });
  } finally {
    if (onLoad) {
      script.removeEventListener('load', onLoad);
    }
    if (onError) {
      script.removeEventListener('error', onError);
    }
  }
}
async loadAcceptApi() {
  if (typeof Accept != 'undefined') {
    return;
  }
  await loadScript('https://jstest.authorize.net/v1/Accept.js');
}
async getPaymentNonce(paymentDetails) {
  await loadAcceptApi();
  return await new Promise((resolve, reject) => {
    Accept.dispatchData(paymentDetails, response => {
      // ...
    });
  }
}

 

Digging into the source for Accept.js, I think I see why this results in an error:

 

  • Our script loads the Accept.js script
  • Accept.js loads AcceptCore.js
  • Until AcceptCore.js is loaded, Accept.dispatchData is defined to just warn "Accept.js is not loaded correctly"
  • Loading Accept.js does not block or wait for AcceptCore.js to load
  • Since our script resumes execution immediately after loading the Accept.js script, there is a race condition where our call to Acccept.dispatchData can occur before AcceptCore.js is fully loaded, resulting in the warning

I dug into AcceptCore.js and found a workaround. AcceptCore.js appears to emit a handshake event on the body element when it is loaded. Knowing this, I updated our code to wait for Accept.js to load and for the handshake event to fire before calling Accept.dispatchData.

 

Updated relevant code:

 

async loadAcceptApi() {
  if (typeof Accept != 'undefined') {
    return;
  }

  let onHandshake = null;
  try {
    // Accept.js loads AcceptCore.js, so we have to wait for the
    // "handshake" event that AcceptCore.js emits after loading.
    let handshake = new Promise(resolve => {
      onHandshake = () => resolve();
      document.body.addEventListener('handshake', onHandshake);
    });

    // Wait for script load and handshake.
    await Promise.all([loadScript('https://jstest.authorize.net/v1/Accept.js', 'utf-8'), handshake]);
  } finally {
    if (onHandshake) {
      document.body.removeEventListener('handshake', onHandshake);
    }
  }
}

 

With the updated code, everything works as expected. We load Accept.js dynamically, wait for it to load, wait for AcceptCore.js to load via the handshake event and only then make our call through Accept.dispatchData.

 

Questions:

 

  • I don't see AcceptCore.js's handshake event documented anywhere, so should we be relying on this internal implementation detail? The obvious answer is no, but then...
  • If not listening for handshake, how else are we supposed to know that Accept has fully loaded? Since Accept.js does a non-blocking load of AcceptCore.js, I'm not sure what alternative we have. Sleeping for a small amount of time also works, but that's an even worse workaround.

Regards,

Chris

schmich
Member
3 REPLIES 3

Accept.js is designed to be loaded when page loads at the starting,

Following should be the sequence:-

  1. Page load
  2. AcceptJS loads
  3. Merchant Page shows the Payment form to customer
  4. Customer fills out the Payment form
  5. Extract the needed payment info from the form
  6. Call Accept.dispatchData with required parameters.

In this flow it takes around 1-2 mins for a customer to fill payment form.

 

Can you pls explain What is your flow, as I see you load accept.js and just after that you call dispatchData. 

 

I feel you should change the load time of Accept script.

 

Thanks,

 

 

 

Shoagraw
Authorize.Net Developer Authorize.Net Developer
Authorize.Net Developer

Thanks for the reply. I understand and appreciate what you're saying, but the design feels flawed to me.

 

We have a Vue-based single page app. It would be wasteful and would increase our vulnerability surface area to load Accept.js on the initial page since it would load for all of our pages, even though it is only used on a couple of them.

 

This isn't a problem, of course, since we can load Accept.js dynamically on just the routes that use it. Ultimately, to keep all of the Authorize.net front-end code together, I decided to only load the library right when we need it (when the user checks out). I could move it to when the route is loaded to reduce latency on checkout, but regardless, there is no clean way of knowing for sure that Accept.js is loaded.

 

The current suggested way to load Accept.js is basically:

 

  1. Load Accept.js on initial page load
  2. (wait some arbitrary amount of time, hope that Accept.js has loaded)
  3. Use the Accept.js API

I understand that this works fine in the majority of situations, I just don't care for the "hope that Accept.js has loaded" step (e.g. What about slow connections? What about prefilled forms that the user submits quickly? What about refreshing and submitting immediately? What about this scenario of loading at the last second?). There should be a simple, deterministic way of knowing that the library is ready.

 

The ways I can think of knowing when it's loaded off the top of my head:

 

  1. On Accept.js script load complete
  2. Via a global callback or promise from Accept.js
  3. Via a custom event raised from Accept.js
  4. Via setInterval and checking to see if the Accept object is present

#1 doesn't work since Accept.js loads AcceptCore.js, which is the issue I ran into. #4 is hacky, inelegant, and wasteful. #2 and #3 seem fine to me.

 

As an example, the Google Maps API accepts a callback parameter that is executed when the library is finished loading.

 

I understand that "load early, wait a while" is good enough for most scenarios, I just expect a bit more from a payment handling library.

Thanks  for your suggestion!

 

I hope you are able to solve the issue now by following currently supported integration way :-

  • Load Accept.js on page load 
  • call dispatchData after user complete filling form
  • implement response handler.

Till now we found it works fine. we want to keep it simple and easy to integrate, 

 

We are taking your feedback and will think about implementing a new event(optional), Script at merchant page can listen to this if needed. 

 

Thanks!

Shoagraw
Authorize.Net Developer Authorize.Net Developer
Authorize.Net Developer