Adyen / adyen-magento2

Adyen Payment plugin for Magento2
MIT License
155 stars 212 forks source link

[PW-8507] Call to undefined getQuoteId() in RecurringVaultDataBuilder - v8.18 #2053

Closed lrotherfield-function closed 1 year ago

lrotherfield-function commented 1 year ago

Describe the bug When making a request to pay for an order using a 3DS enabled vault card, an error is thrown:

Call to undefined method PayPal\Braintree\Gateway\Data\Order\OrderAdapter::getQuoteId()

By code on line 51 of \Adyen\Payment\Gateway\Request\RecurringVaultDataBuilder

To Reproduce Steps to reproduce the behavior:

  1. Go to checkout
  2. Submit payment using 3DS enabled vault card (stored in the vault)
  3. See error

Expected behavior Order should be placed with no errors

Magento version 2.4.4-p3

Plugin version 8.18.0

Additional context This was previously an issue in another part of the code that had the PayPal OrderAdapter injected and was resolved in https://github.com/Adyen/adyen-magento2/issues/1168

The change that causes the issue was a patch in 8.17.0: https://github.com/Adyen/adyen-magento2/commit/b660e9dac3f7d5bb0d45e68a3145df6a1f8cde27#diff-0e0587b967adaa49d80ffcf95d7ace94ee9c4d963f5a139a84954c9a0db890ccL36

RokPopov commented 1 year ago

Hi @lrotherfield-function,

Thank you for opening this issue. I have opened an internal ticket for us to look into this.

Kind regards, Rok

ebaschiera commented 1 year ago

I am having this issue also on a fresh Magento 2.4.5-p1.

lrotherfield-function commented 1 year ago

Hey @RokPopov any news on this one chap?

devetus commented 1 year ago

Can confirm this is causing 500 Internal Server Error on payment-information request when trying to pay with a card saved in the Vault (saved previously via Adyen CC method) on 2.4.5-p1

MikeParkin commented 1 year ago

@RokPopov would it be possible to get an update on this bug please? It's preventing us being able to upgrade at the moment.

RokPopov commented 1 year ago

Hey @ebaschiera, @lrotherfield-function, @devetus and @MikeParkin,

First of all apologies for the delay in my reply. I picked up this investigation and I would like to share the outcome of my investigation so far:

Testing on:

At this point, I would like to ask you for more information:

Thank you in advance and please let me know on the above!

Cheers, Rok

lrotherfield-function commented 1 year ago

Hey @RokPopov can I just confirm you have PayPal\Braintree installed in your test environment? This is the module that conflicts with your module to cause the error.

I am running 2.4.4 with adyen 8.18 as above.

RokPopov commented 1 year ago

Hey @lrotherfield-function thanks for that catch! However, it's not that straightforward because our environment doesn't have the Braintree module installed by default, so I have to make sure to require and enable the correct one. Is it this one?

Or is it the one that already comes pre-installed when one sets up a fresh instance of Magento, called PayPal Express Checkout? (see the screenshot bellow):

Screenshot 2023-06-28 at 16 47 22

Thanks in advance!

lrotherfield-function commented 1 year ago

Hi @RokPopov it is paypal/module-braintree-core which should include \PayPal\Braintree\Gateway\Data\Order\OrderAdapter in your project

That is included by "paypal/module-braintree" which is included by "magento/product-community-edition"

RokPopov commented 1 year ago

Thanks @lrotherfield-function!

The annoying thing is I opened the gates to all sorts of composer issues, can I ask you which version of PHP are you using? I fetched the correct plugin, the class you referred to is there, I just can't run any bin/magento commands 🤷‍♂️

Cheers!

lrotherfield-function commented 1 year ago

Hey @RokPopov,

I am running PHP 8.1 using warden to boot up a docker dev environment.

Is there a reason you exclude modules that are included by Magento core in your dev environment?

RokPopov commented 1 year ago

Hey @lrotherfield-function! I think for the same reason outlined here which resulted in a still open feature request here => For a custom project, requiring magento/product-community-edition directly is not viable because unwanted packages are added by it, so instead we copy the require section into our own composer.json and remove what we don't need.

Probably it has something to do with our environment, as we use a completely different one to spin up a new instance of Magento with our plugin installed then you do.

I'll update you as soon as I have something to update with!

Cheers, Rok

RokPopov commented 1 year ago

Hey @lrotherfield-function,

Good news, we got some progress! Could you please check this PR and confirm it solves the issue for you? I had success in our environment, but just to make sure I would like to see the outcome on your end.

Thanks in advance!

Cheers, Rok

devetus commented 1 year ago

Hi @RokPopov Unfortunately in our case, after applying the change from your PR $requestBody remains as an empty array which leads to an error Required object 'paymentMethod' is not provided. in Adyen error.log and Error with payment method, please select a different payment method. returned in the payment request response

But the previous error with undefined getQuoteId() method does not appear anymore.

RokPopov commented 1 year ago

Hi @devetus,

Thank you for the feedback, this is much appreciated!

The approach I took for trying to solve this is already used in another part of our code, the CheckoutDataBuilder -> in these lines. Instead of using the OrderAdapter, I changed the class that fetches the order for us. Do I understand this correctly, this code is working for you when a shopper is trying a payment with a non-stored credit card, but is throwing an error when trying the payment with 3DS2 stored card?

@lrotherfield-function did you face the same issue when testing the fix?

@devetus I am asking the above information as I cannot reproduce the issue, the $stateData for me is present and accessible, which is how we are populating the $requestBody. I see no reason for it not to be, as it is being injected in the constructor. Are you absolutely sure that no modifications on your end could potentially cause the issues with the Observers fetching the stateData from the frontend?

Cheers, Rok

devetus commented 1 year ago

Hi @RokPopov, I've done one more test and the $order->getQuoteId() returns correct active quote ID. The $details array is also populated as expected ("Subscription" token type). The problem is that adyen_state_data table remains empty and the data can't be retrieved. This test was done on offline environment without webhook notifications - where the initial order used to save Recurring token was not complete yet but just authorised. Does it mean we need to have those payments captured first before using tokens for recurring payments? Previously that was not necessary.

Also checked a couple of production databases and adyen_state_data is empty everywhere but we're on 8.16.0 module version there.

So looks like the issue is caused by state data not being saved. Maybe some missing configuration or webhook events?

RokPopov commented 1 year ago

Hi @devetus,

You are indeed correct in saying that the adyen_state_data table remains unpopulated. This table was being populated in the card payment flow some versions ago, but since then we refactored the functionality in a way that state data is being fetched from the temporary storage. In our case, the observer is taking over the responsibility of fetching state data from the frontend and placing it in that temporary storage. The adyen_state_data table is still being used for giftcard implementation and for headless integrationss (to offer the merchants that want to decouple their frontend to build a two-step checkout).

For me, the state data table is also empty, however the $requestObject has all the necessary parameters and the state data method is not returning an empty value.

Could you please debug the observer and see what is happening in your case there? As I still cannot reproduce your issue.

Cheers, Rok

lrotherfield-function commented 1 year ago

Hi @RokPopov

Sorry for the slow response. The patch succeeds in getting the order, quoteId and populating the requestObject. However I am getting an error in vendor/adyen/module-payment/Gateway/Response/PaymentAuthorisationDetailsHandler.php:36 because there is no 'pspReference' key in the $response array. Instead $response contains:

{
  "resultCode": "IdentifyShopper",
  "action": {
    "paymentData": "**obfuscated**",
    "paymentMethodType": "scheme",
    "authorisationToken": "**obfuscated**",
    "subtype": "fingerprint",
    "token": "**obfuscated**",
    "type": "threeDS2"
  }
}

This seems similar to the original issue: https://github.com/Adyen/adyen-magento2/issues/1980 the fix of which caused the current issue in this ticket.

Thoughts?

lrotherfield-function commented 1 year ago

Part of the stack trace in case that is helpful:

[2023-07-05T12:40:32.414935+00:00] report.CRITICAL: Exception: Warning: Undefined array key "pspReference" in /var/www/html/vendor/adyen/module-payment/Gateway/Response/PaymentAuthorisationDetailsHandler.php on line 36 in /var/www/html/vendor/magento/framework/App/ErrorHandler.php:61
Stack trace:
#0 /var/www/html/vendor/adyen/module-payment/Gateway/Response/PaymentAuthorisationDetailsHandler.php(36): Magento\Framework\App\ErrorHandler->handler(2, 'Undefined array...', '/var/www/html/v...', 36)
#1 /var/www/html/vendor/magento/module-payment/Gateway/Response/HandlerChain.php(51): Adyen\Payment\Gateway\Response\PaymentAuthorisationDetailsHandler->handle(Array, Array)
#2 /var/www/html/vendor/magento/module-payment/Gateway/Command/GatewayCommand.php(118): Magento\Payment\Gateway\Response\HandlerChain->handle(Array, Array)
#3 /var/www/html/vendor/magento/module-payment/Gateway/Command/CommandManager.php(65): Magento\Payment\Gateway\Command\GatewayCommand->execute(Array)
#4 /var/www/html/vendor/magento/module-vault/Model/Method/Vault.php(452): Magento\Payment\Gateway\Command\CommandManager->executeByCode('vault_authorize', Object(Magento\Sales\Model\Order\Payment\Interceptor), Array)
#5 /var/www/html/vendor/magento/module-sales/Model/Order/Payment/Operations/AuthorizeOperation.php(45): Magento\Vault\Model\Method\Vault->authorize(Object(Magento\Sales\Model\Order\Payment\Interceptor), 395.0)
#6 /var/www/html/vendor/magento/module-sales/Model/Order/Payment/Processor.php(72): Magento\Sales\Model\Order\Payment\Operations\AuthorizeOperation->authorize(Object(Magento\Sales\Model\Order\Payment\Interceptor), true, 395.0)
#7 /var/www/html/vendor/magento/module-sales/Model/Order/Payment.php(1136): Magento\Sales\Model\Order\Payment\Processor->authorize(Object(Magento\Sales\Model\Order\Payment\Interceptor), true, 395.0)
#8 /var/www/html/generated/code/Magento/Sales/Model/Order/Payment/Interceptor.php(302): Magento\Sales\Model\Order\Payment->authorize(true, 395.0)
#9 /var/www/html/vendor/magento/module-sales/Model/Order/Payment.php(458): Magento\Sales\Model\Order\Payment\Interceptor->authorize(true, 395.0)
#10 /var/www/html/vendor/magento/module-sales/Model/Order/Payment.php(385): Magento\Sales\Model\Order\Payment->processAction('authorize', Object(Wyomind\PickupAtStore\Magento\Sales\Model\Order\Interceptor))
#11 /var/www/html/generated/code/Magento/Sales/Model/Order/Payment/Interceptor.php(122): Magento\Sales\Model\Order\Payment->place()
#12 /var/www/html/vendor/magento/module-sales/Model/Order.php(1002): Magento\Sales\Model\Order\Payment\Interceptor->place()
#13 /var/www/html/vendor/magento/module-sales/Model/Order.php(1228): Magento\Sales\Model\Order->_placePayment()
devetus commented 1 year ago

Hi @RokPopov so here's what's happening when I select the Vault card and try to place new order:

  1. AdyenCcDataAssignObserver calls $this->stateDataCollection->getStateDataArrayWithQuoteId($paymentInfo->getData('quote_id'));. The quote ID is present and is correct based on quote table
  2. StateData Collection then tries the query in getStateDataRowsWithQuoteId however the result is empty because adyen_state_data table is always empty.
  3. This later fails and results in the errors described in previous comments

The query is

SELECT `main_table`.* FROM `adyen_state_data` AS `main_table` WHERE (`quote_id` = '109439') ORDER BY `entity_id` DESC

where 109439 was my quote ID.

Please could you point me where data in the adyen_state_data is being saved? Should this table be even queried if it is no longer used (or used only for gifcards?)

Please find below a screenshot with my test configuration

image
lrotherfield-function commented 1 year ago

@RokPopov sorry I must have had something cached in my environment because I am no longer getting any issues when checking out with your patch in place. From my point of view it is now working.

lrotherfield-function commented 1 year ago

@devetus I am using the CardOnFile token type (only difference in our config) and the code in vendor/adyen/module-payment/Observer/AdyenCcDataAssignObserver.php does not hit the db because $addtionalData contains the stateData key which means $stateData is populated on line 115 and not 117:

        if (!empty($additionalData[self::STATE_DATA])) {
            $stateData = json_decode($additionalData[self::STATE_DATA], true);
        } else {
            $stateData = $this->stateDataCollection->getStateDataArrayWithQuoteId($paymentInfo->getData('quote_id'));
        }
devetus commented 1 year ago

Hi @lrotherfield-function In my case stateData is available, but only during initial payment by credit card. Once the order is placed, the customer trying to create another order with their saved card in the Vault, receives additionalData with some parameters but missing stateData part specifically.

The test card I am saving is 5555 4444 3333 1111

@RokPopov if you could point me to where exactly stateData is initiated, before the payment_method_assign_data_adyen_cc event is dispatched I could debug further. Currently data passed via these events don't contain stateData