Skip to content

Commit

Permalink
Emit paymentMethodRequestable event after 3DS Challenge is completed (#…
Browse files Browse the repository at this point in the history
…917)

* Delay sending paymentMethodRequestable even until after 3ds Challenge is
completed for 3DS flows.

* Add test for verifyCardReady

* Test to not call  paymentMethodRequestable until after 3ds verification

* Add test for 3ds.verify

* Refactor self = this in 3ds module

* Add test for 3ds module

* Update changelog

* Add GH issue to changelog entry

* Update test/unit/dropin.js

Co-authored-by: Holly Richko <hollis65@me.com>

* Refactor based on PR feedback

---------

Co-authored-by: Holly Richko <hollis65@me.com>
  • Loading branch information
jplukarski and hollabaq86 committed Dec 20, 2023
1 parent 9851442 commit aab0587
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 1 deletion.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# CHANGELOG

## UNRELEASED
- Apple Pay: add error message prompting the customer to click the Apple Pay button when `requestPaymentMethod` is called.
- Apple Pay
- add error message prompting the customer to click the Apple Pay button when `requestPaymentMethod` is called.
- 3D Secure
- Fix issue where `paymentMethodRequestable` event would fire before 3DS challenge has been completed. (closes [#805](https://github.com/braintree/braintree-web-drop-in/issues/805))

## 1.41.0
- Update braintree-web to 3.97.4
Expand Down
5 changes: 5 additions & 0 deletions src/dropin-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ function DropinModel(options) {
this.failedDependencies = {};
this._options = options;
this._setupComplete = false;
this.shouldWaitForVerifyCard = false;

while (this.rootNode.parentNode) {
this.rootNode = this.rootNode.parentNode;
Expand Down Expand Up @@ -223,6 +224,10 @@ DropinModel.prototype._shouldEmitRequestableEvent = function (options) {
return false;
}

if (this.shouldWaitForVerifyCard) {
return false;
}

if (requestableStateHasNotChanged && (!options.isRequestable || nonceHasNotChanged)) {
return false;
}
Expand Down
6 changes: 6 additions & 0 deletions src/dropin.js
Original file line number Diff line number Diff line change
Expand Up @@ -876,10 +876,16 @@ Dropin.prototype.requestPaymentMethod = function (options) {
self._mainView.showLoadingIndicator();

return self._threeDSecure.verify(payload, options.threeDSecure).then(function (newPayload) {
self._model.shouldWaitForVerifyCard = false;
payload.nonce = newPayload.nonce;
payload.liabilityShifted = newPayload.liabilityShifted;
payload.liabilityShiftPossible = newPayload.liabilityShiftPossible;
payload.threeDSecureInfo = newPayload.threeDSecureInfo;
self._model.setPaymentMethodRequestable({
isRequestable: Boolean(newPayload),
type: newPayload.type,
selectedPaymentMethod: payload
});

self._mainView.hideLoadingIndicator();

Expand Down
1 change: 1 addition & 0 deletions src/lib/three-d-secure.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ ThreeDSecure.prototype.verify = function (payload, merchantProvidedData) {

verifyOptions.additionalInformation = verifyOptions.additionalInformation || {};
verifyOptions.additionalInformation.acsWindowSize = verifyOptions.additionalInformation.acsWindowSize || DEFAULT_ACS_WINDOW_SIZE;
this._model.shouldWaitForVerifyCard = true;

return this._instance.verifyCard(verifyOptions);
};
Expand Down
25 changes: 25 additions & 0 deletions test/unit/dropin-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,31 @@ describe('DropinModel', () => {
}
);

test(
'does not emit paymentMethodRequestable event until after three D secure verification has been completed',
() => {
testContext.model.shouldWaitForVerifyCard = true;
testContext.model.setPaymentMethodRequestable({
isRequestable: true,
type: 'card'
});

expect(testContext.model._emit).not.toBeCalled();

testContext.model.shouldWaitForVerifyCard = false;

testContext.model.setPaymentMethodRequestable({
isRequestable: true,
type: 'card',
selectedPaymentMethod: {
nonce: 'fake-nonce'
}
});

expect(testContext.model._emit).toBeCalled();
}
);

test(
'sets isPaymentMethodRequestable to false when isRequestable is false',
() => {
Expand Down
37 changes: 37 additions & 0 deletions test/unit/dropin.js
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,43 @@ describe('Dropin', () => {
}
);

test('sets shouldWaitForVerifyCard to false and calls setPaymentMethodRequestable when 3D secure is complete', done => {
let instance;
const fakePayload = {
nonce: 'cool-nonce',
type: 'CreditCard'
};
const fakeNewPayload = {
nonce: 'new-nonce',
liabilityShifted: true,
liabilityShiftPossible: true,
type: fakePayload.type
};

testContext.dropinOptions.merchantConfiguration.threeDSecure = {};

instance = new Dropin(testContext.dropinOptions);

instance._initialize(() => {
jest.spyOn(instance._mainView, 'requestPaymentMethod').mockResolvedValue(fakePayload);
jest.spyOn(instance._model, 'setPaymentMethodRequestable').mockResolvedValue();
instance._threeDSecure = {
verify: jest.fn().mockResolvedValue(fakeNewPayload)
};

instance.requestPaymentMethod(() => {
expect(instance._model.shouldWaitForVerifyCard).toBe(false);
expect(instance._model.setPaymentMethodRequestable).toBeCalledWith({
isRequestable: true,
type: fakeNewPayload.type,
selectedPaymentMethod: fakeNewPayload
});

done();
});
});
});

test(
'does not call 3D Secure if network tokenized google pay',
done => {
Expand Down
13 changes: 13 additions & 0 deletions test/unit/lib/three-d-secure.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,19 @@ describe('ThreeDSecure', () => {
});
});

test('sets shouldWaitForVerifyCard to true', () => {
expect(testContext.model.shouldWaitForVerifyCard).toBe(false);

return testContext.tds.verify({
nonce: 'old-nonce',
details: {
bin: '123456'
}
}).then(() => {
expect(testContext.model.shouldWaitForVerifyCard).toBe(true);
});
});

test('rejects if verifyCard rejects', () => {
testContext.threeDSecureInstance.verifyCard.mockRejectedValue({
message: 'A message'
Expand Down

0 comments on commit aab0587

Please sign in to comment.