magento / magento2

Prior to making any Submission(s), you must sign an Adobe Contributor License Agreement, available here at: https://opensource.adobe.com/cla.html. All Submissions you make to Adobe Inc. and its affiliates, assigns and subsidiaries (collectively “Adobe”) are subject to the terms of the Adobe Contributor License Agreement.
http://www.magento.com
Open Software License 3.0
11.5k stars 9.31k forks source link

Authorize.net Transaction Fails but Order goes through #22373

Closed simonlandry0 closed 5 years ago

simonlandry0 commented 5 years ago

Description of the issue(*)

We recently upgraded our site to 2.3.1 and started using the new authorize.net plugin. After testing it for error reporting, we realized that our orders are being successfully created even if authorize.net is returning that the transaction has been declined.

Successful payments go through but so do unsuccessful ones, this is a major issue and could be exploited on Magento 2.3.1 sites that are using Authorize.net to buy stuff without paying.

The old version of the authorize.net plugin (direct post) did not do this, this is new in Magento 2.3.1.

Preconditions (*)

  1. Magneto CE 2.3.1 is installed
  2. Authorize.net (new method, not the direct post) is configured correctly (Production or Sandbox, doesnt matter). Enable debugging.

Steps to reproduce (*)

  1. Add product to cart
  2. Checkout item as normal
  3. Enter shipping address and shipping type
  4. Enter billing address ([Sandbox]: use 46282 as a zip code for the payment to fail in authorize.net. See https://developer.authorize.net/hello_world/testing_guide/)
  5. Enter card details ([Sandbox]: Use 4111111111111111 as a card number and 900 as a CVV)
  6. Purchase items

Expected result (*)

  1. Some error should show up telling the user that the payment didn't go through.

Actual result (*)

  1. Order goes through without any errors image
  2. var/log/payment.log contains
    'response' => '{"transactionResponse":{"responseCode":"2","authCode":"","avsResultCode":"Y","cvvResultCode":"M","cavvResultCode":"2","transId":"11111111111","refTransID":"","transHash":"","testRequest":"0","accountNumber":"XXXX1111","accountType":"Visa","errors":[{"errorCode":"2","errorText":"This transaction has been declined."}],"userFields":[{"name":"transactionType","value":"authOnlyTransaction"}],"transHashSha2":"11111111111111111111111111111111111111111111","SupplementalDataQualificationIndicator":0},"messages":{"resultCode":"Ok","message":[{"code":"I00001","text":"Successful."}]}}'

    This indicates the transaction was declined.

m2-assistant[bot] commented 5 years ago

Hi @simonlandry0. Thank you for your report. To help us process this issue please make sure that you provided the following information:

Please make sure that the issue is reproducible on the vanilla Magento instance following Steps to reproduce. To deploy vanilla Magento instance on our environment, please, add a comment to the issue:

@magento-engcom-team give me 2.3-develop instance - upcoming 2.3.x release

For more details, please, review the Magento Contributor Assistant documentation.

@simonlandry0 do you confirm that you was able to reproduce the issue on vanilla Magento instance following steps to reproduce?

simonlandry0 commented 5 years ago

@magento-engcom-team give me 2.3-develop instance

magento-engcom-team commented 5 years ago

Hi @simonlandry0. Thank you for your request. I'm working on Magento 2.3-develop instance for you

magento-engcom-team commented 5 years ago

Hi @simonlandry0, here is your Magento instance. Admin access: https://i-22373-2-3-develop.instances.magento-community.engineering/admin Login: admin Password: 123123q Instance will be terminated in up to 3 hours.

simonlandry0 commented 5 years ago

Reproduced on vanilla 2.3.1 instance

simonlandry0 commented 5 years ago

Traced the bug to faulty logic in /module-authorizenet-acceptjs/Gateway/Validator/TransactionResponseValidator.php

    /**
     * Determines if the response code is actually an error
     *
     * @param array $transactionResponse
     * @return bool
     */
    private function isResponseCodeAnError(array $transactionResponse): bool
    {
        $code = $transactionResponse['messages']['message']['code']
            ?? $transactionResponse['messages']['message'][0]['code']
            ?? $transactionResponse['errors'][0]['errorCode']
            ?? null;

        return in_array($transactionResponse['responseCode'], [self::RESPONSE_CODE_APPROVED, self::RESPONSE_CODE_HELD])
            && $code
            && !in_array(
                $code,
                [
                    self::RESPONSE_REASON_CODE_APPROVED,
                    self::RESPONSE_REASON_CODE_PENDING_REVIEW,
                    self::RESPONSE_REASON_CODE_PENDING_REVIEW_AUTHORIZED
                ]
            );
    }

function isResponseCodeAnError will return false if the responseCode (2 for failure in this case) is in a list of successfully error codes (1 and 4), this is faulty logic and should probably be inverted with ORs (||) instead of ANDs (&&).

simonlandry0 commented 5 years ago

Pinging @nathanjosiah since he is the author of the file and this bug might be exploitable on unaware affected sites. Hotfix for 2.3.1 is probably needed.

nthink commented 5 years ago

We are experiencing the same issue, exactly as described above.

nathanjosiah commented 5 years ago

@simonlandry0 At first look, it does look as though the logic should be:

return !in_array($transactionResponse['responseCode'], [self::RESPONSE_CODE_APPROVED, self::RESPONSE_CODE_HELD])
            || ($code
                && !in_array(
                    $code,
                    [
                        self::RESPONSE_REASON_CODE_APPROVED,
                        self::RESPONSE_REASON_CODE_PENDING_REVIEW,
                        self::RESPONSE_REASON_CODE_PENDING_REVIEW_AUTHORIZED
                    ]
                )
            );

However, I could be wrong since authorize.net gives success messaging when the transaction is denied in some cases. The integration was extensively tested with countless combinations of authorize.net settings and testing data by our team so we are confused how this would have made its way all the way into a release. Either way, this issue is being investigated.

Edit: Actually, re-reading this comment and the code, these corner cases are something that this check is supposed to be handling. The response code will be approved but the reason code will be something other than a success.

simonlandry0 commented 5 years ago

@nathanjosiah This would be en example of a failed transaction I tested earlier:

{  
   "transactionResponse":{  
      "responseCode":"2",
      "authCode":"",
      "avsResultCode":"Y",
      "cvvResultCode":"M",
      "cavvResultCode":"2",
      "transId":"11111111111",
      "refTransID":"",
      "transHash":"",
      "testRequest":"0",
      "accountNumber":"XXXX1111",
      "accountType":"Visa",
      "errors":[  
         {  
            "errorCode":"2",
            "errorText":"This transaction has been declined."
         }
      ],
      "userFields":[  
         {  
            "name":"transactionType",
            "value":"authOnlyTransaction"
         }
      ],
      "transHashSha2":"11111111111111111111111111111111111111111111",
      "SupplementalDataQualificationIndicator":0
   },
   "messages":{  
      "resultCode":"Ok",
      "message":[  
         {  
            "code":"I00001",
            "text":"Successful."
         }
      ]
   }
}

It's understandable how the confusing the auth.net response is. I was reading payement logs I saw the "Successful" message at first, but apparently it returns both a success and a error message at the same time when it fails which is super confusing.

According to https://developer.authorize.net/api/reference/responseCodes.html transaction responseCode is a high level indication of if the transaction failed or not, so it should probably be the primary indicator of if the transaction was successful or not, however you probably worked more with that API than me so there might be some weird quirks I'm unaware of like you said.

nthink commented 5 years ago

For what its worth, here's our response. It seems the authorization is successful but then the card is declined. I'm not sure if that's what the "Successful" message indicates.

{
  "transactionResponse": {
    "responseCode": "2",
    "authCode": "xxx",
    "avsResultCode": "S",
    "cvvResultCode": "M",
    "cavvResultCode": "",
    "transId": "xxx",
    "refTransID": "xxx",
    "transHash": "xxx",
    "testRequest": "0",
    "accountNumber": "xxx",
    "accountType": "MasterCard",
    "errors": [
      {
        "errorCode": "27",
        "errorText": "The transaction has been declined because of an AVS mismatch. The address provided does not match billing address of cardholder."
      }
    ],
    "userFields": [
      {
        "name": "transactionType",
        "value": "authOnlyTransaction"
      }
    ],
    "transHashSha2": "xxx",
    "SupplementalDataQualificationIndicator": 0
  },
  "messages": {
    "resultCode": "Ok",
    "message": [
      {
        "code": "I00001",
        "text": "Successful."
      }
    ]
  }
}

On the Auth.net end, this is what we see alongside the transaction:

Declined  (Authorization with the card issuer was successful but the transaction was declined due to an address or ZIP code mismatch with the address on file with the card issuing bank based on the settings in the Merchant Interface.)

m2-assistant[bot] commented 5 years ago

Hi @engcom-backlog-nazar. Thank you for working on this issue. In order to make sure that issue has enough information and ready for development, please read and check the following instruction: :point_down:

magento-engcom-team commented 5 years ago

:white_check_mark: Confirmed by @engcom-backlog-nazar Thank you for verifying the issue. Based on the provided information internal tickets MAGETWO-99307 were created

Issue Available: @engcom-backlog-nazar, You will be automatically unassigned. Contributors/Maintainers can claim this issue to continue. To reclaim and continue work, reassign the ticket to yourself.

nathanjosiah commented 5 years ago

Fix was just delivered to 2.3-develop and will be included in 2.3.2

wscull commented 5 years ago

We are experiencing this same issue. Is there a temporary fix available while we wait for 2.3.2?

simonlandry0 commented 5 years ago

Hey @wscull If you wanted you could try replicating this commit that nathanjosiah did on the staging version of 2.3.2 that should resolve the issue. https://github.com/magento/magento2/commit/dd7cb583b112f82ba58ef87d612631ac0eb1c57c

You could go ahead and do those modification directly to your /vendor/magento/module-authorizenet-acceptjs/Gateway/Validator/TransactionResponseValidator.php file (writing a plugin that replaces all those virtual types is going to be really annoying). Just note that updating composer will overwrite any changes made in vendor.

Personally I haven't tried doing this and can't really recommend it, but this is probably the way I would go to making a quick fix. We are going to wait until 2.3.2 comes out to switch to accept-js (which will hopefully be released before June).

nathanjosiah commented 5 years ago

@wscull, The advice from @simonlandry0 above would work as described but I would also reinforce that since there isn't an official patch for this that we recommend against modifying any core code manually. That being said, the changes in dd7cb58 are very small should you decide apply them yourself and you would not even need to make the changes to the unit test for the functionality to be fixed.

13moons commented 5 years ago

@simonlandry0 & @nathanjosiah Thank for your reply. We will be testing it soon.

daschenbrener commented 5 years ago

anyone with any luck with this as we cannot get this to work . and how there isn't a patch is actually mind blowing , biut not the less i try this and i dont even get an error message now

nathanjosiah commented 5 years ago

@daschenbrener 2.3.2 be released soon. I can't provide more information but rest assured it's almost out. In the meantime my comment above mentions a commit that could be used as a patch if you wanted to go that route but consider the implications mentioned as well if you chose to do that.

nathanjosiah commented 5 years ago

Magento 2.3.2 has been released and the fix for this issue is included.

simonlandry0 commented 5 years ago

After testing the 2.3.2 version, I've discovered another potential issue related to the same plugin. I'll investigate further with the authorize.net api and probably create a new issue for this soon. But I'm getting this now...

'response' => '{"transactionResponse":{"SupplementalDataQualificationIndicator":0},"messages":{"resultCode":"Error","message":[{"code":"E00007","text":"User authentication failed due to invalid authentication values."}]}}',

Not sure what could have changed, tried re-submiting the auth.net API credentials without success. Wondering if anyone else has this issue. Maybe the auth.net acceptjs API was changed and requires updated auth params.

simonlandry0 commented 5 years ago

I fixed my own issue. Just sharing what fixed it in case someone else has the same issue.

Apparently Authorize.net API keys fields in the backend defaults to be different for each website configuration. While I correctly inputted the prod keys in the "default config" auth.net configuration, I had to go to each one of the my websites and mark the authorize.net fields as "Use Default" so they inherited from the global config since they were previously set to use my sandbox API keys. image

jasper0514 commented 5 years ago

Thanks for posting this solution. I have been struggling with an Invalid Gateway Credentials error message for quite some time. Once I checked the boxes for "Use Default", everything worked just fine. I was now able to turn off the old hash method and move to the next public client key method.

urgent-passport commented 5 years ago

I am experiencing the same issue on 2.3.1 with auth.net CIM. Will disabling accet.js fix the issue temporarily? Or is the code change mentioned above required to fix it unless you install the 2.3.2 update?

simonlandry0 commented 5 years ago

@bhq300 the code fix is part of the 2.3.2 update and fixes magento\module-authorizenet-acceptjs.

I don't know what third party plugin auth.net CIM you use and if it inherits from the libraries in magento\module-authorizenet-acceptjs (if this is the case, it might fix your issue). But I would recommend contacting them about the issue and they will probably be able to address it.

touqeer-verve commented 5 years ago

I am getting the following error when placing an order from admin using authorized.net payment method on 2.3.1 'response' => '{"transactionResponse":{"SupplementalDataQualificationIndicator":0},"messages":{"resultCode":"Error","message":[{"code":"E00076","text":"dataDescriptor contains invalid value."},{"code":"E00076","text":"dataValue contains invalid value."}]}}

BrokenMop commented 4 years ago

Magento 2.3.2 has been released and the fix for this issue is included.

@nathanjosiah

I am on 2.3.1 after I applied the changes in dd7cb58

If a transaction does not pass authorization, the order won't go through at checkout.

However If you have some fraud filters that decline the transaction after it is authorized. The payment won't be captured in gateway. And Magneto does not know that!

simonlandry0 commented 4 years ago

@BrokenMop That sounds like might be a separate issue, you could also make an issue tracker for this, it will hopefully get more eyes.

Hopefully they can get that fixed, it would not surprise me if I can replicate that issue in our live 2.3.3 instance.

nathanjosiah commented 4 years ago

@BrokenMop I'm not sure about filters that decline AFTER the transaction has already been authorized. However, there was specific support added to handle the default Authorize.net fraud filters and Magento even has special options that appear on the order for resolving potentially fraudulent transactions.

That said, Authorize.netAcceptJs has been deprecated along with all but two core payment methods as Magento has shifted its mentality to preferring the official payment gateways to write and support their own extensions. Authorize.net has theirs here if you are interested.

As for your bug, I would follow @simonlandry0's suggestion and open a new bug for this issue.