We are regularly getting this error in the logs every day, but have been unable to reproduce it ourselves.

#3153914: Intermittent payment method creation errors on missing data was a great help because people are not getting the WSOD any more. However, they are still unable to pay and we get a lot of complaints about this. From the user's point of view they are just stuck on the payment page without an error.

What could be causing this? Something particular to the card being used? User error in some way?

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

Rob230 created an issue. See original summary.

rob230’s picture

This 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.

rob230’s picture

Title: $payment_details must contain the payment_method_nonce key » Invalid 3DS2 implementation
Status: Active » Needs review
StatusFileSize
new5.44 KB

After 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():

$payment_method = $this->order->get('payment_method')->entity;
// The payment method nonce should be used for this one time purchase and
// the previous tokenized payment method should be kept for future
// purchases.
$three_d_payment_method = $payment_method->createDuplicate();
$three_d_payment_method->setRemoteId($values['payment_method_nonce']);
$three_d_payment_method->setReusable(FALSE);
$three_d_payment_method->save();
$this->order->set('payment_method', $payment_method);

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():

if ($payment_method->isReusable()) {
  $transaction_data['paymentMethodToken'] = $payment_method->getRemoteId();
}
else {
  $transaction_data['paymentMethodNonce'] = $payment_method->getRemoteId();
}

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() in HostedFields::createPayment(). So I've also added some code which passes the currency code to HostedFields::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.

rob230’s picture

Priority: Normal » Major

I would say this is major since 3DS2 isn't implemented properly and payments larger than a certain amount fail.

rob230’s picture

Sorry, 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.

jsacksick’s picture

Status: Needs review » Needs work

Braintree 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.

jsacksick’s picture

If 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.

rob230’s picture

Good thinking, I've changed it to use setData() instead.

jsacksick’s picture

This can't be committed until #3345876: Allow specifying merchant id when generating client token. is resolved (similar changes to the generateClientToken()) are implemented there.

vince.rowe’s picture

Hi, 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

jeromebeckett’s picture

Thanks 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?

rob230’s picture

Hi 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.

guy_schneerson’s picture

Status: Needs work » Needs review
StatusFileSize
new1.64 KB

Updated 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.

<?php
-    $this->order->set('payment_method', $payment_method);
+
+    $this->order->set('payment_method', $three_d_payment_method);
+    // We will need to change the payment method back after payment so that
+    // recurring orders or failed payments won't use the nonce.
+    $this->order->setData('payment_method_reusable', $payment_method->id());
?>

and

<?php
       $transaction_data['paymentMethodNonce'] = $payment_method->getRemoteId();
+      $order = $payment->getOrder();
+      $reusable_payment_method_id = $order->getData('payment_method_reusable');
+      if ($reusable_payment_method_id) {
+        $order->set('payment_method', $reusable_payment_method_id);
+        $order->save();
+      }
?>
pandepoulus’s picture

Hello, 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.

joe huggans’s picture

Tested #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.

TomTech made their first commit to this issue’s fork.

tomtech’s picture

Assigned: Unassigned » tomtech
tomtech’s picture

  • guy_schneerson authored e435774e on 8.x-1.x
    Issue #3324779 by Rob230, TomTech, guy_schneerson, jsacksick, vince.rowe...
tomtech’s picture

Status: Needs review » Fixed

Thanks, 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.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.