Closed adamhenson closed 1 year ago
Thanks for the report. We'll look into this.
Thanks! I do appreciate this library. Also, a different workaround that I'm going with is to simply call requestPaymentMethod()
on click of my button... and then creating my sale transaction in the same thread. It's a little awkward because the dropin UI changes - for example when adding a new payment - the UI changes to show the list of payments with the new one checked off. Normally, the user would click again to finalize... but I'm skipping over that part by submitting the payment immediately after success of requestPaymentMethod()
. Theoretically one could hide the changing dropin UI during this transition as a workaround.
So - just wanted to say for me - this bug isn't super critical.
Just wanted to mention that this issue still persists in version 1.14.1 of the Drop-in UI, with several developers reporting it with the Braintree Cashier Drupal module in this issue. The call to requestPaymentMethod()
is being invoked by the button click callback, the event for which has a Drop-in instance property. In some circumstances (browser / OS version combinations, of which I'm unsure) this property is undefined at the moment the event occurs, but it becomes defined about 200ms later. The button click callback succeeds if requestPaymetMethod()
is invoked 200ms after the button click. It should not be necessary to wait 200ms before calling requestPaymentMethod()
. Below is the JS integration for the Drop-in UI without the 200ms fix.
(function ($, Drupal, drupalSettings) {
'use strict';
/**
* Callback for the click event on the visible submit button.
*
* @param {jQuery.Event} event
*/
function onInitialButtonClick(event) {
event.preventDefault();
event.data.buttonInitial.prop('disabled', true)
.addClass('is-disabled');
event.data.instance.requestPaymentMethod(function (requestPaymentMethodErr, payload) {
if (requestPaymentMethodErr) {
event.data.buttonInitial.prop('disabled', false)
.removeClass('is-disabled');
return;
}
event.data.nonceField.val(payload.nonce);
event.data.buttonFinal.click();
});
}
/**
* Callback for after the Dropin UI instance is created.
*
* @param createErr
* The error generated if the Dropin UI could not be created.
* @param {object} instance
* The Braintree Dropin UI instance.
*
* @see https://braintree.github.io/braintree-web-drop-in/docs/current/Dropin.html
*/
function onInstanceCreate(createErr, instance) {
var buttonInitial = $('#submit-button');
var buttonFinal = $('#final-submit');
var nonceField = $('#payment-method-nonce');
buttonInitial.prop('disabled', false)
.removeClass('is-disabled')
.click({
instance: instance,
buttonInitial: buttonInitial,
buttonFinal: buttonFinal,
nonceField: nonceField
}, onInitialButtonClick);
}
/**
* Create the Braintree Dropin UI.
*
* @type {{attach: Drupal.behaviors.signupForm.attach}}
*/
Drupal.behaviors.signupForm = {
attach: function (context, settings) {
var createParams = {
authorization: drupalSettings.braintree_cashier.authorization,
container: '#dropin-container'
};
if (drupalSettings.braintree_cashier.acceptPaypal) {
createParams.paypal = {
flow: 'vault'
};
}
braintree.dropin.create(createParams, onInstanceCreate);
}
};
})(jQuery, Drupal, drupalSettings);
For us the solution has been to declare the dropinInstance
variable in a scope broader than the button click handler and dropin.create
. When the dropinInstance
is initialized by the dropin.create
callback, it's ready to go when the user eventually clicks on the submit button, so there's no need to use setTimeout()
.
Here is the updated version of integrating the Drop-in UI, which has fixed the issue. Example code for the Drop-in UI should be updated to show the dropinInstance
declared in a scope broader than both dropin.create
and the button click handler.
(function ($, Drupal, drupalSettings) {
'use strict';
var dropinInstance;
var buttonInitialSelector = '#submit-button';
var buttonInitial;
var buttonFinal;
var nonceField;
/**
* Callback for the click event on the visible submit button.
*
* @param {jQuery.Event} event
*/
function onInitialButtonClick(event) {
event.preventDefault();
buttonInitial.prop('disabled', true)
.addClass('is-disabled');
dropinInstance.requestPaymentMethod(function (requestPaymentMethodErr, payload) {
if (requestPaymentMethodErr) {
buttonInitial.prop('disabled', false)
.removeClass('is-disabled')
.click(onInitialButtonClick);
return;
}
nonceField.val(payload.nonce);
buttonFinal.click();
});
// Remove event handler since it was getting submitted multiple times
// during automated tests.
$.off('click', buttonInitialSelector, onInitialButtonClick);
}
/**
* Callback for after the Dropin UI instance is created.
*
* @param createErr
* The error generated if the Dropin UI could not be created.
* @param {object} instance
* The Braintree Dropin UI instance.
*
* @see https://braintree.github.io/braintree-web-drop-in/docs/current/Dropin.html
*/
function onInstanceCreate(createErr, instance) {
dropinInstance = instance;
buttonInitial.prop('disabled', false)
.removeClass('is-disabled')
.click(onInitialButtonClick);
}
/**
* Create the Braintree Dropin UI.
*
* @type {{attach: Drupal.behaviors.signupForm.attach}}
*/
Drupal.behaviors.signupForm = {
attach: function (context, settings) {
buttonInitial = $(buttonInitialSelector);
buttonFinal = $('#final-submit');
nonceField = $('#payment-method-nonce');
var createParams = {
authorization: drupalSettings.braintree_cashier.authorization,
container: '#dropin-container'
};
if (drupalSettings.braintree_cashier.acceptPaypal) {
createParams.paypal = {
flow: 'vault'
};
}
braintree.dropin.create(createParams, onInstanceCreate);
}
};
})(jQuery, Drupal, drupalSettings);
Thanks for that info. That's very helpful.
I was having a similar issue but it wasn't any sort of race condition, in my case it was a binding issue with the requestPaymentMethod
function. I had to define my handler using an es6 arrow function to not mess with the this
keyword inside the requestPaymentMethod
function.
Just in case anyone stumbles upon this.
FWIW, I'm running into this issue every so often in drop-in UI version 1.18.0. Chrome 80. Most of the time it works just fine, other times I'm seeing Cannot read property 'requestPaymentMethod' of undefined at initBraintreeAndSubmit.
var braintreeInstance;
document.getElementById("hfBraintreeNonce").value = "";
braintree.dropin.create({
authorization: "...",
container: "#braintree-container"
}, function (err, instance) { braintreeInstance = instance; });
function initBraintreeAndSubmit() {
if (braintreeInstance === null) {
alert("Braintree dropin instance is not defined.");
return;
}
braintreeInstance.requestPaymentMethod(function (err, payload) {
if (err) {
return;
}
document.getElementById("hfBraintreeNonce").value = payload.nonce;
document.getElementById("btnSubmit").click();
});
}
... also in drop-in UI version 1.22.1.
Thanks for the info. I'll take another look at this soon.
Please fix this finally!!!
hi @aleks-b, this is something we're planning to sort out for the upcoming v2 version of Drop-in.
As always, the repo is open source, and if you'd like to poke around and tackle whatever race condition is causing this bug, I'd be happy to review a PR and get this out sooner than that.
I have also been experiencing a similar issue in 1.22.1 where calling instance.requestPaymentMethod
causes this error
Unhandled Promise Rejection: SyntaxError: The string did not match the expected pattern.
If it helps, this is the more detailed error showing in chrome
Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'Window': Invalid target origin 'null' in a call to 'postMessage'. at t.reply.t.reply (https://assets.braintreegateway.com/web/3.58.0/html/hosted-fields-frame.min.html:1:19254) at https://assets.braintreegateway.com/web/3.58.0/html/hosted-fields-frame.min.html:1:85220
Don't know if anyone experiences this but I can't process any payments with this
I know this is an old thread, but if I'm going to start using BrainTree API I need this issue resolved. I'm experiencing the same issue. I'm using: dropin.js - version 1.25.0
I followed the instructions here -> https://developers.braintreepayments.com/start/tutorial-drop-in-node
It worked the first few times and suddenly stopped rendering in about a 15 minute window of my very first tests.
Getting error:
braintreeclient.html:40 Uncaught TypeError: Cannot read property 'requestPaymentMethod' of undefined
at HTMLButtonElement.
Tried clearing cache in Chrome, no luck then tried another browser, same result.
Has this issue been resolved? is the a newer version of dropin.js that is not yet documented?
Looks to me like that example is not handling any errors in the create process. I'd add:
braintree.dropin.create({
// Insert your tokenization key here
authorization: '<use_your_tokenization_key>',
container: '#dropin-container'
}, function (createErr, instance) {
if (createErr) {
console.log('something went wrong');
console.log(createErr);
return;
}
button.addEventListener('click', function () {
To debug and make sure that Drop-in was set up succesfully.
closing for inactivity. If you continue to encounter errors, please contact Support
1 year later, @armandodlvr @crookedneighbor
Open and close the dropin a couple of times in this Sandbox to see this damn error :(
Any idea on a fix bro? 👍
General information
How I Arrived at the Issue
A user who has a "requestable" payment (a payment method available from the vault) arrives on a page and is presented with the below UI (note the
Pay
button is coming from my application):This is based on the example code as shown in the
README.md
file of this repo:Below is the flow that seems awkward to me for this type of user:
instance.requestPaymentMethod()
even though the user already has a payment method... so we can get the payment nonce.This a bit awkward because nothing changes except the text of the button from "Pay" to "Confirm" and requires one extra unnecessary click to complete the order. I'm only doing that so the user sees something change.
So, I thought I could simply check if we have a requestable payment on the callback of
dropin.create()
, and if so - just executeinstance.requestPaymentMethod()
without any user interaction. Something like the below code is what could accomplish that and is where the bug is reproducible:Issue description
When calling
instance.requestPaymentMethod()
within thebraintree.dropin.create()
callback function an error intermittently occurs (I can reproduce approximately 1 out of every 5 page loads). Above is an example of code that reproduces the issue.Below is the error output from Chrome Developer Tools:
Strange (probably unreliable) workaround
I did discover that when I change this from the example above:
To this:
... The issue is no longer reproducible (in my testing with Chrome). This tells me there is probably some kind of race condition in the dropin UI library.