Closed (fixed)
Project:
Commerce Braintree
Version:
8.x-1.x-dev
Component:
Code
Priority:
Major
Category:
Bug report
Assigned:
Reporter:
Created:
1 Dec 2022 at 11:56 UTC
Updated:
14 Mar 2024 at 04:59 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
rob230 commentedThis issue I have at least partially solved with the following patch.
Basically it appears to be a mistake/confusion with the implementation of 3D Secure by some banks, insofar as they don't ask for 3D Secure (or Braintree doesn't?) but then they reject payments without it.
The solution therefore is to force it. I'm not sure if this will have knock-on effects for any banks that do not support 3D Secure (e.g. in the US?). But in Europe at least it is a legal requirement and all banks now require 3DS2.
We are still having some people unable to pay, specifically when paying with a saved payment method which was used before the 3D Secure implementation.
Comment #3
rob230 commentedAfter many more investigations I've discovered the problem. There is a bug in the 3DS2 implementation which means although the user has to pass 3DS2, the bank is never told about it. From my tests, it seems that many banks in the UK and Europe still allow payments without 3DS2, as long as the amount isn't too large, which gives the illusion that it is working. But if you actually check in Braintree, you will not see 3DS2 information on the transaction.
The intermittent failures were occurring for amounts over a certain amount (roughly 30 GBP), but some banks were accepting it for larger amounts. After my fixes it works for all banks, the payments don't get rejected for large amounts, and you can see the 3DS2 information in the transaction in Braintree.
The main issue was in
Braintree3DSReview::submitPaneForm():Note the last line sets the payment method back to the same payment method, so it has essentially done nothing. The corresponding place where this alternative payment method should have been used is
HostedFields::createPayment():What is expected is the payment method was set to the non-reusable duplicate, and therefore this nonce will get passed in the paymentMethodNonce parameter to Braintree. But it was never happening.
After fixing this, I found an additional problem that it wasn't accepting the nonce. Braintree was giving the error: "Merchant account must match the 3D Secure authorization merchant account". After digging through Braintree's documentation, it seems that at the time the token is created you must pass the same merchant ID as the one passed during the Transaction: sale() call to Braintree.
If you have multiple merchant accounts (for different currencies), during token creation it will use the default one, and that might be different to the one specified when calling
sale()inHostedFields::createPayment(). So I've also added some code which passes the currency code toHostedFields::generateClientToken(). I made it optional because you don't know what the currency is on the PaymentMethodAddForm, but you don't use 3DS on that form anyway so it shouldn't matter.The final problem was that because a nonce is now being stored as the payment method on the order instead of a remote ID, it means recurring payments (for people using the commerce_recurring module) will fail, because the nonce can only be used once. It may also be a problem if additional payment needs to be taken or refunds.
For this reason, I've had to add another base field called
'payment_method_reusable', which is simply used to save the reusable payment method ID until the payment has been made. It is then restored afterwards, so that when commerce_recurring creates the recurring order it will have the reusable ID. I'm not sure if there could be a better way of doing that without the need for another field.Comment #4
rob230 commentedI would say this is major since 3DS2 isn't implemented properly and payments larger than a certain amount fail.
Comment #5
rob230 commentedSorry, that patch may not apply because I made it against files that have other patches applied. If I get time I'll reroll it against dev.
Comment #6
jsacksick commentedBraintree should definitely not be responsible for providing a commerce_order field.
Also, changing the method signature like this isn't an option (referring to the
generateClientToken()change).Waiting for other people from the community to chime in / report also this before digging on my side as well.
Comment #7
jsacksick commentedIf we really need to store the reusable method temporarily, in order to swap it later. Let's just use the order data for now. (
$this->order->setData('payment_method_reusable', $payment_method)...If you change that and revert the method signature change (or make the currency parameter optional), we can consider committing this. But still would appreciate if somebody else from the community could RTBC this.
Comment #8
rob230 commentedGood thinking, I've changed it to use
setData()instead.Comment #9
jsacksick commentedThis can't be committed until #3345876: Allow specifying merchant id when generating client token. is resolved (similar changes to the generateClientToken()) are implemented there.
Comment #10
vince.rowe commentedHi, the issue linked to on the last comment was committed, does this mean this patch can now be reviewed again?
Our site currently is not accepting any 3D Secure payments either through stable or dev release on commerce 2.36 on Drupal 10.0.10.
This patch is also not working when trying to apply to dev via composer.
Many thanks
Comment #11
jeromebeckett commentedThanks for your work on this Rob.
Our client has been reporting payment failures due to the same issue.
The patch in #8 applies cleanly to 8.x-1.4 so we are going to apply this to our live site. I will report back once we know if we are seeing a reduction in the amount of payment failures due to 3DS or not.
As stated in #10, the issue referred to in #9 has been fixed and merged. Now that has been done can we commit and merge the patch to 8.x-1.x-dev?
Comment #12
rob230 commentedHi Jerome, we have been using patch #8 for 9 months now and aren't getting the failed payment reports any more.
I'm surprised more people haven't reported this - broken 3DS2 implementation is pretty major. But it is up to the banks to reject and for small amounts most will allow without 3DS2. We were seeing almost every transaction over £50 be rejected by certain banks, and that no longer happens with patch #8.
Despite hundreds of people per month being rejected, only 1 or 2 of those would bother to email us to say it wasn't working, so perhaps a lot of site admins are unaware of the problem. You'd have to pay attention to the number of unsuccessful transactions in Braintree and consider it an issue.
Comment #13
guy_schneerson commentedUpdated patch.
Many of the changes made it into the module. This patch includes the remaining change.
I am not sure I fully understand why the original code created the $three_d_payment_method and not used it so hoping this will solve my issue.
Also storing the $payment_method against the order and restoring it does not feel like the best way to do this but if it works so be it.
and
Comment #14
pandepoulus commentedHello, i can confirm i had some 3ds2 verification fails before applying the patch for big transactions.
Same people could buy after applying the patch at #13.
Kind regards.
Comment #15
joe huggansTested #13 on Drupal 10.2.3 and 8.x-1.5 of this module and it seems to work, can see the 3DS data on the Braintree transaction now, which wasn't there before.
Comment #17
tomtech commentedComment #18
tomtech commentedComment #21
tomtech commentedThanks, all!
Note: the last patch handles some, but not all 3ds2 issues.
Additional work is included in the MR that was created and merged in.