From 4c4bb8948116e35a0d5b409366b459fac2e4ed80 Mon Sep 17 00:00:00 2001 From: khushboos Date: Thu, 21 Nov 2024 17:13:23 +0100 Subject: [PATCH 01/19] Updating Paybylink description --- Gateway/Request/DescriptionDataBuilder.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Gateway/Request/DescriptionDataBuilder.php b/Gateway/Request/DescriptionDataBuilder.php index 0bd7c12a26..9c6d81f23d 100644 --- a/Gateway/Request/DescriptionDataBuilder.php +++ b/Gateway/Request/DescriptionDataBuilder.php @@ -29,13 +29,7 @@ public function build(array $buildSubject) $payment = $paymentDataObject->getPayment(); /** @var Order $order */ $order = $payment->getOrder(); - - $request['body']['description'] = __( - 'Order %1 from %2', - $order->getIncrementId(), - $order->getStore()->getGroup()->getName() - ); - + $request['body']['description'] = 'Order ' . $order->getIncrementId() . ' from ' . $order->getStore()->getGroup()->getName(); return $request; } } From 720324e329df7c0b5c3603e44d55a0de8daa89b6 Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Tue, 26 Nov 2024 12:35:53 +0100 Subject: [PATCH 02/19] [ECP-9559] Create an abstract method renderer for Oney/Facilypay variants (#2815) Co-authored-by: Can Demiralp --- etc/frontend/di.xml | 4 + .../adyen-facilypay-3x-method.js | 3 + .../method-renderer/adyen-facilypay-method.js | 80 +++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 view/frontend/web/js/view/payment/method-renderer/adyen-facilypay-method.js diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml index 34576cb772..a5e4a65ee5 100755 --- a/etc/frontend/di.xml +++ b/etc/frontend/di.xml @@ -121,6 +121,10 @@ Adyen_Payment/js/view/payment/method-renderer/adyen-applepay-method Adyen_Payment/js/view/payment/method-renderer/adyen-boleto-method Adyen_Payment/js/view/payment/method-renderer/adyen-facilypay-3x-method + Adyen_Payment/js/view/payment/method-renderer/adyen-facilypay-method + Adyen_Payment/js/view/payment/method-renderer/adyen-facilypay-method + Adyen_Payment/js/view/payment/method-renderer/adyen-facilypay-method + Adyen_Payment/js/view/payment/method-renderer/adyen-facilypay-method Adyen_Payment/js/view/payment/method-renderer/adyen-googlepay-method Adyen_Payment/js/view/payment/method-renderer/adyen-paypal-method Adyen_Payment/js/view/payment/method-renderer/adyen-giftcard-method diff --git a/view/frontend/web/js/view/payment/method-renderer/adyen-facilypay-3x-method.js b/view/frontend/web/js/view/payment/method-renderer/adyen-facilypay-3x-method.js index cc4bf208c1..11e2d4ef15 100644 --- a/view/frontend/web/js/view/payment/method-renderer/adyen-facilypay-3x-method.js +++ b/view/frontend/web/js/view/payment/method-renderer/adyen-facilypay-3x-method.js @@ -6,6 +6,9 @@ * See LICENSE.txt for license details. * * Author: Adyen + * + * @deprecated This file will be removed on V10. Use `adyen-facilypay-method.js` instead. + * */ define( [ diff --git a/view/frontend/web/js/view/payment/method-renderer/adyen-facilypay-method.js b/view/frontend/web/js/view/payment/method-renderer/adyen-facilypay-method.js new file mode 100644 index 0000000000..7eb71c71d3 --- /dev/null +++ b/view/frontend/web/js/view/payment/method-renderer/adyen-facilypay-method.js @@ -0,0 +1,80 @@ +/** + * + * Adyen Payment module (https://www.adyen.com/) + * + * Copyright (c) 2024 Adyen N.V. (https://www.adyen.com/) + * See LICENSE.txt for license details. + * + * Author: Adyen + */ +define( + [ + 'Magento_Checkout/js/model/quote', + 'Adyen_Payment/js/view/payment/method-renderer/adyen-pm-method', + ], + function( + quote, + adyenPaymentMethod, + ) { + return adyenPaymentMethod.extend({ + initialize: function () { + this._super(); + }, + buildComponentConfiguration: function (paymentMethod, paymentMethodsExtraInfo) { + let baseComponentConfiguration = this._super(); + let self = this; + let formattedShippingAddress = {}; + let formattedBillingAddress = {}; + let shopperDateOfBirth = ''; + let email = {}; + + if (!!customerData.dob){ + shopperDateOfBirth = customerData.dob; + } + + if (!!customerData.email) { + email = customerData.email; + } else if (!!quote.guestEmail) { + email = quote.guestEmail; + }; + + if (!quote.isVirtual() && !!quote.shippingAddress()) { + formattedShippingAddress = self.getFormattedAddress(quote.shippingAddress()); + } + + if (!quote.isVirtual() && !!quote.billingAddress()) { + formattedBillingAddress = self.getFormattedAddress(quote.billingAddress()); + } + + if (formattedShippingAddress) { + baseComponentConfiguration.data.deliveryAddress = { + city: formattedShippingAddress.city, + country: formattedShippingAddress.country, + houseNumberOrName: formattedShippingAddress.houseNumber, + postalCode: formattedShippingAddress.postalCode, + street: formattedShippingAddress.street + } + } + + if (formattedBillingAddress){ + baseComponentConfiguration.data.personalDetails = { + firstName: formattedBillingAddress.firstName, + lastName: formattedBillingAddress.lastName, + telephoneNumber: formattedBillingAddress.telephone, + shopperEmail: email, + dateOfBirth: shopperDateOfBirth, + } + baseComponentConfiguration.data.billingAddress = { + city: formattedBillingAddress.city, + country: formattedBillingAddress.country, + houseNumberOrName: formattedBillingAddress.houseNumber, + postalCode: formattedBillingAddress.postalCode, + street: formattedBillingAddress.street, + } + } + + return baseComponentConfiguration; + } + }) + } +); From 7c787afcf95adcfbe8ffb29a89a48668044317fe Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Tue, 26 Nov 2024 12:55:42 +0100 Subject: [PATCH 03/19] [ECP-9475] Implement DOKU payment method variants (#2812) * [ECP-9475] Implement Alfamart via DOKU * [ECP-9475] Implement ATM Permata VA LITE via DOKU * [ECP-9475] Implement ATM BCA VA via DOKU * [ECP-9475] BNI VA via DOKU * [ECP-9475] BRI VA via DOKU * [ECP-9475] CIMB VA via DOKU * [ECP-9475] Danamon VA via DOKU * [ECP-9475] Danamon VA via DOKU * [ECP-9475] Indomaret via DOKU * [ECP-9475] Mandiri VA via DOKU --------- Co-authored-by: Can Demiralp --- etc/config.xml | 200 +++++++++++++- etc/di.xml | 243 ++++++++++++++++++ etc/events.xml | 27 ++ etc/frontend/di.xml | 9 + etc/graphql/di.xml | 9 + etc/payment.xml | 27 ++ view/base/web/images/logos/doku_alfamart.svg | 1 + view/base/web/images/logos/doku_bca_va.svg | 1 + view/base/web/images/logos/doku_bni_va.svg | 1 + view/base/web/images/logos/doku_bri_va.svg | 1 + view/base/web/images/logos/doku_cimb_va.svg | 1 + .../base/web/images/logos/doku_danamon_va.svg | 1 + view/base/web/images/logos/doku_indomaret.svg | 1 + .../base/web/images/logos/doku_mandiri_va.svg | 1 + .../images/logos/doku_permata_lite_atm.svg | 1 + view/frontend/layout/checkout_index_index.xml | 27 ++ .../layout/multishipping_checkout_billing.xml | 9 + 17 files changed, 559 insertions(+), 1 deletion(-) create mode 100644 view/base/web/images/logos/doku_alfamart.svg create mode 100644 view/base/web/images/logos/doku_bca_va.svg create mode 100644 view/base/web/images/logos/doku_bni_va.svg create mode 100644 view/base/web/images/logos/doku_bri_va.svg create mode 100644 view/base/web/images/logos/doku_cimb_va.svg create mode 100644 view/base/web/images/logos/doku_danamon_va.svg create mode 100644 view/base/web/images/logos/doku_indomaret.svg create mode 100644 view/base/web/images/logos/doku_mandiri_va.svg create mode 100644 view/base/web/images/logos/doku_permata_lite_atm.svg diff --git a/etc/config.xml b/etc/config.xml index d61cff0b2c..b432bd83ff 100755 --- a/etc/config.xml +++ b/etc/config.xml @@ -2274,6 +2274,204 @@ 0 adyen-alternative-payment-method + + 0 + AdyenPaymentDokuAlfamartFacade + Alfamart via DOKU + 0 + 0 + authorize + 1 + 1 + 1 + 0 + 0 + 1 + 1 + 1 + 1 + 0 + 0 + 1 + 0 + adyen-alternative-payment-method + + + 0 + AdyenPaymentDokuPermataLiteAtmFacade + ATM Permata VA LITE via DOKU + 0 + 0 + authorize + 1 + 1 + 1 + 0 + 0 + 1 + 1 + 1 + 1 + 0 + 0 + 1 + 0 + adyen-alternative-payment-method + + + 0 + AdyenPaymentDokuBcaVaFacade + ATM BCA VA via DOKU + 0 + 0 + authorize + 1 + 1 + 1 + 0 + 0 + 1 + 1 + 1 + 1 + 0 + 0 + 1 + 0 + adyen-alternative-payment-method + + + 0 + AdyenPaymentDokuBniVaFacade + BNI VA via DOKU + 0 + 0 + authorize + 1 + 1 + 1 + 0 + 0 + 1 + 1 + 1 + 1 + 0 + 0 + 1 + 0 + adyen-alternative-payment-method + + + 0 + AdyenPaymentDokuBriVaFacade + BRI VA via DOKU + 0 + 0 + authorize + 1 + 1 + 1 + 0 + 0 + 1 + 1 + 1 + 1 + 0 + 0 + 1 + 0 + adyen-alternative-payment-method + + + 0 + AdyenPaymentDokuCimbVaFacade + CIMB VA via DOKU + 0 + 0 + authorize + 1 + 1 + 1 + 0 + 0 + 1 + 1 + 1 + 1 + 0 + 0 + 1 + 0 + adyen-alternative-payment-method + + + 0 + AdyenPaymentDokuDanamonVaFacade + Danamon VA via DOKU + 0 + 0 + authorize + 1 + 1 + 1 + 0 + 0 + 1 + 1 + 1 + 1 + 0 + 0 + 1 + 0 + adyen-alternative-payment-method + + + 0 + AdyenPaymentDokuIndomaretFacade + Indomaret via DOKU + 0 + 0 + authorize + 1 + 1 + 1 + 0 + 0 + 1 + 1 + 1 + 1 + 0 + 0 + 1 + 0 + adyen-alternative-payment-method + + + 0 + AdyenPaymentDokuMandiriVaFacade + Mandiri VA via DOKU + 0 + 0 + authorize + 1 + 1 + 1 + 0 + 0 + 1 + 1 + 1 + 1 + 0 + 0 + 1 + 0 + adyen-alternative-payment-method + - + \ No newline at end of file diff --git a/etc/di.xml b/etc/di.xml index 32896b3ff2..aefb9b552a 100755 --- a/etc/di.xml +++ b/etc/di.xml @@ -4316,4 +4316,247 @@ adyen_kcp_creditcard + + + adyen_doku_alfamart + Magento\Payment\Block\Form + Adyen\Payment\Block\Info\PaymentMethodInfo + AdyenPaymentDokuAlfamartValueHandlerPool + AdyenPaymentValidatorPool + AdyenPaymentCommandPool + + + + + + AdyenPaymentDokuAlfamartConfigValueHandler + + + + + + AdyenPaymentDokuAlfamartConfig + + + + + adyen_doku_alfamart + + + + + adyen_doku_permata_lite_atm + Magento\Payment\Block\Form + Adyen\Payment\Block\Info\PaymentMethodInfo + AdyenPaymentDokuPermataLiteAtmValueHandlerPool + AdyenPaymentValidatorPool + AdyenPaymentCommandPool + + + + + + AdyenPaymentDokuPermataLiteAtmConfigValueHandler + + + + + + AdyenPaymentDokuPermataLiteAtmConfig + + + + + adyen_doku_permata_lite_atm + + + + + adyen_doku_bca_va + Magento\Payment\Block\Form + Adyen\Payment\Block\Info\PaymentMethodInfo + AdyenPaymentDokuBcaVaValueHandlerPool + AdyenPaymentValidatorPool + AdyenPaymentCommandPool + + + + + + AdyenPaymentDokuBcaVaConfigValueHandler + + + + + + AdyenPaymentDokuBcaVaConfig + + + + + adyen_doku_bca_va + + + + + adyen_doku_bni_va + Magento\Payment\Block\Form + Adyen\Payment\Block\Info\PaymentMethodInfo + AdyenPaymentDokuBniVaValueHandlerPool + AdyenPaymentValidatorPool + AdyenPaymentCommandPool + + + + + + AdyenPaymentDokuBniVaConfigValueHandler + + + + + + AdyenPaymentDokuBniVaConfig + + + + + adyen_doku_bni_va + + + + + adyen_doku_bri_va + Magento\Payment\Block\Form + Adyen\Payment\Block\Info\PaymentMethodInfo + AdyenPaymentDokuBriVaValueHandlerPool + AdyenPaymentValidatorPool + AdyenPaymentCommandPool + + + + + + AdyenPaymentDokuBriVaConfigValueHandler + + + + + + AdyenPaymentDokuBriVaConfig + + + + + adyen_doku_bri_va + + + + + adyen_doku_cimb_va + Magento\Payment\Block\Form + Adyen\Payment\Block\Info\PaymentMethodInfo + AdyenPaymentDokuCimbVaValueHandlerPool + AdyenPaymentValidatorPool + AdyenPaymentCommandPool + + + + + + AdyenPaymentDokuCimbVaConfigValueHandler + + + + + + AdyenPaymentDokuCimbVaConfig + + + + + adyen_doku_cimb_va + + + + + adyen_doku_danamon_va + Magento\Payment\Block\Form + Adyen\Payment\Block\Info\PaymentMethodInfo + AdyenPaymentDokuDanamonVaValueHandlerPool + AdyenPaymentValidatorPool + AdyenPaymentCommandPool + + + + + + AdyenPaymentDokuDanamonVaConfigValueHandler + + + + + + AdyenPaymentDokuDanamonVaConfig + + + + + adyen_doku_danamon_va + + + + + adyen_doku_indomaret + Magento\Payment\Block\Form + Adyen\Payment\Block\Info\PaymentMethodInfo + AdyenPaymentDokuIndomaretValueHandlerPool + AdyenPaymentValidatorPool + AdyenPaymentCommandPool + + + + + + AdyenPaymentDokuIndomaretConfigValueHandler + + + + + + AdyenPaymentDokuIndomaretConfig + + + + + adyen_doku_indomaret + + + + + adyen_doku_mandiri_va + Magento\Payment\Block\Form + Adyen\Payment\Block\Info\PaymentMethodInfo + AdyenPaymentDokuMandiriVaValueHandlerPool + AdyenPaymentValidatorPool + AdyenPaymentCommandPool + + + + + + AdyenPaymentDokuMandiriVaConfigValueHandler + + + + + + AdyenPaymentDokuMandiriVaConfig + + + + + adyen_doku_mandiri_va + + \ No newline at end of file diff --git a/etc/events.xml b/etc/events.xml index 1520613215..8953d82977 100644 --- a/etc/events.xml +++ b/etc/events.xml @@ -311,4 +311,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml index a5e4a65ee5..9553e01b20 100755 --- a/etc/frontend/di.xml +++ b/etc/frontend/di.xml @@ -114,6 +114,15 @@ kcp_naverpay kcp_payco kcp_creditcard + doku_alfamart + doku_permata_lite_atm + doku_bca_va + doku_bni_va + doku_bri_va + doku_cimb_va + doku_danamon_va + doku_indomaret + doku_mandiri_va Adyen_Payment/js/view/payment/method-renderer/adyen-cc-method diff --git a/etc/graphql/di.xml b/etc/graphql/di.xml index ca8deea49d..ce48e3017e 100644 --- a/etc/graphql/di.xml +++ b/etc/graphql/di.xml @@ -95,6 +95,15 @@ Adyen\Payment\Model\Cart\Payment\AdditionalDataProvider\AdyenPm Adyen\Payment\Model\Cart\Payment\AdditionalDataProvider\AdyenPm Adyen\Payment\Model\Cart\Payment\AdditionalDataProvider\AdyenPm + Adyen\Payment\Model\Cart\Payment\AdditionalDataProvider\AdyenPm + Adyen\Payment\Model\Cart\Payment\AdditionalDataProvider\AdyenPm + Adyen\Payment\Model\Cart\Payment\AdditionalDataProvider\AdyenPm + Adyen\Payment\Model\Cart\Payment\AdditionalDataProvider\AdyenPm + Adyen\Payment\Model\Cart\Payment\AdditionalDataProvider\AdyenPm + Adyen\Payment\Model\Cart\Payment\AdditionalDataProvider\AdyenPm + Adyen\Payment\Model\Cart\Payment\AdditionalDataProvider\AdyenPm + Adyen\Payment\Model\Cart\Payment\AdditionalDataProvider\AdyenPm + Adyen\Payment\Model\Cart\Payment\AdditionalDataProvider\AdyenPm diff --git a/etc/payment.xml b/etc/payment.xml index 5c9920a180..e8efc7767d 100755 --- a/etc/payment.xml +++ b/etc/payment.xml @@ -272,5 +272,32 @@ 1 + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + \ No newline at end of file diff --git a/view/base/web/images/logos/doku_alfamart.svg b/view/base/web/images/logos/doku_alfamart.svg new file mode 100644 index 0000000000..76f4a967e2 --- /dev/null +++ b/view/base/web/images/logos/doku_alfamart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/view/base/web/images/logos/doku_bca_va.svg b/view/base/web/images/logos/doku_bca_va.svg new file mode 100644 index 0000000000..45f42f70ca --- /dev/null +++ b/view/base/web/images/logos/doku_bca_va.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/view/base/web/images/logos/doku_bni_va.svg b/view/base/web/images/logos/doku_bni_va.svg new file mode 100644 index 0000000000..e0bb021e63 --- /dev/null +++ b/view/base/web/images/logos/doku_bni_va.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/view/base/web/images/logos/doku_bri_va.svg b/view/base/web/images/logos/doku_bri_va.svg new file mode 100644 index 0000000000..30951b99a2 --- /dev/null +++ b/view/base/web/images/logos/doku_bri_va.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/view/base/web/images/logos/doku_cimb_va.svg b/view/base/web/images/logos/doku_cimb_va.svg new file mode 100644 index 0000000000..46e66acab1 --- /dev/null +++ b/view/base/web/images/logos/doku_cimb_va.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/view/base/web/images/logos/doku_danamon_va.svg b/view/base/web/images/logos/doku_danamon_va.svg new file mode 100644 index 0000000000..b5ee7fdfe5 --- /dev/null +++ b/view/base/web/images/logos/doku_danamon_va.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/view/base/web/images/logos/doku_indomaret.svg b/view/base/web/images/logos/doku_indomaret.svg new file mode 100644 index 0000000000..8d792a9560 --- /dev/null +++ b/view/base/web/images/logos/doku_indomaret.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/view/base/web/images/logos/doku_mandiri_va.svg b/view/base/web/images/logos/doku_mandiri_va.svg new file mode 100644 index 0000000000..c223e79269 --- /dev/null +++ b/view/base/web/images/logos/doku_mandiri_va.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/view/base/web/images/logos/doku_permata_lite_atm.svg b/view/base/web/images/logos/doku_permata_lite_atm.svg new file mode 100644 index 0000000000..d9747002da --- /dev/null +++ b/view/base/web/images/logos/doku_permata_lite_atm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/view/frontend/layout/checkout_index_index.xml b/view/frontend/layout/checkout_index_index.xml index 076a3d1ca0..d592dcf8f5 100755 --- a/view/frontend/layout/checkout_index_index.xml +++ b/view/frontend/layout/checkout_index_index.xml @@ -326,6 +326,33 @@ true + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + diff --git a/view/frontend/layout/multishipping_checkout_billing.xml b/view/frontend/layout/multishipping_checkout_billing.xml index 64d663f2be..5841e52395 100644 --- a/view/frontend/layout/multishipping_checkout_billing.xml +++ b/view/frontend/layout/multishipping_checkout_billing.xml @@ -104,6 +104,15 @@ Adyen_Payment::form/multishipping/abstract-form.phtml Adyen_Payment::form/multishipping/abstract-form.phtml Adyen_Payment::form/multishipping/abstract-form.phtml + Adyen_Payment::form/multishipping/abstract-form.phtml + Adyen_Payment::form/multishipping/abstract-form.phtml + Adyen_Payment::form/multishipping/abstract-form.phtml + Adyen_Payment::form/multishipping/abstract-form.phtml + Adyen_Payment::form/multishipping/abstract-form.phtml + Adyen_Payment::form/multishipping/abstract-form.phtml + Adyen_Payment::form/multishipping/abstract-form.phtml + Adyen_Payment::form/multishipping/abstract-form.phtml + Adyen_Payment::form/multishipping/abstract-form.phtml false From 6bfabbf389e296506f5809351f0f3c9c6eaf0bd3 Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Tue, 26 Nov 2024 13:22:01 +0100 Subject: [PATCH 04/19] [ECP-9285] Fix Level 2 / Level 3 ESD additional data builder (#2808) * [ECP-9285] Update docs link and the label of the config field * [ECP-9285] Revisit the fields for L2/L3 ESD in the data builder * [ECP-9285] Fix required fields and refactor the class * [ECP-9285] Write unit tests * [ECP-9285] Fix unit tests --------- Co-authored-by: Can Demiralp --- .../AdditionalDataLevel23DataBuilder.php | 163 +++++--- .../AdditionalDataLevel23DataBuilderTest.php | 349 +++++++----------- etc/adminhtml/system/adyen_card_payments.xml | 4 +- 3 files changed, 247 insertions(+), 269 deletions(-) diff --git a/Gateway/Request/AdditionalDataLevel23DataBuilder.php b/Gateway/Request/AdditionalDataLevel23DataBuilder.php index c07d049e18..0ed6a23059 100644 --- a/Gateway/Request/AdditionalDataLevel23DataBuilder.php +++ b/Gateway/Request/AdditionalDataLevel23DataBuilder.php @@ -3,7 +3,7 @@ * * Adyen Payment Module * - * Copyright (c) 2022 Adyen N.V. + * Copyright (c) 2024 Adyen N.V. * This file is open source and available under the MIT license. * See the LICENSE file for more info. * @@ -16,90 +16,145 @@ use Adyen\Payment\Helper\Config; use Adyen\Payment\Helper\Data; use Adyen\Payment\Helper\Requests; +use Adyen\Payment\Logger\AdyenLogger; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Payment\Gateway\Helper\SubjectReader; use Magento\Payment\Gateway\Request\BuilderInterface; +use Magento\Sales\Api\Data\OrderItemInterface; use Magento\Sales\Model\Order; -use Magento\Sales\Model\Order\Item; use Magento\Store\Model\StoreManagerInterface; class AdditionalDataLevel23DataBuilder implements BuilderInterface { + const ENHANCED_SCHEME_DATA_PREFIX = 'enhancedSchemeData'; + const ITEM_DETAIL_LINE_PREFIX = 'itemDetailLine'; + const UNIT_OF_MEASURE_PCS = 'pcs'; + /** - * @var Config - */ - private $config; - /** - * @var StoreManagerInterface - */ - private $storeManager; - /** - * @var Data - */ - private $adyenHelper; - /** - * @var ChargedCurrency + * @param Config $config + * @param StoreManagerInterface $storeManager + * @param ChargedCurrency $chargedCurrency + * @param Requests $adyenRequestHelper + * @param Data $adyenHelper + * @param AdyenLogger $adyenLogger */ - private $chargedCurrency; + public function __construct( + public Config $config, + public StoreManagerInterface $storeManager, + public ChargedCurrency $chargedCurrency, + public Requests $adyenRequestHelper, + public Data $adyenHelper, + public AdyenLogger $adyenLogger + ) { } + /** - * @var Requests + * This data builder creates `additionalData` object for Level 2/3 enhanced scheme data. + * For more information refer to https://docs.adyen.com/payment-methods/cards/enhanced-scheme-data/l2-l3 + * + * @param array $buildSubject + * @return array|array[] + * @throws NoSuchEntityException */ - private $adyenRequestHelper; - - public function __construct( - Config $config, - StoreManagerInterface $storeManager, - ChargedCurrency $chargedCurrency, - Requests $adyenRequestHelper, - Data $adyenHelper - ) + public function build(array $buildSubject): array { - $this->config = $config; - $this->storeManager = $storeManager; - $this->adyenHelper = $adyenHelper; - $this->chargedCurrency = $chargedCurrency; - $this->adyenRequestHelper = $adyenRequestHelper; - } + $request = []; - public function build(array $buildSubject) - { - $requestBody = []; if ($this->config->sendLevel23AdditionalData($this->storeManager->getStore()->getId())) { $paymentDataObject = SubjectReader::readPayment($buildSubject); $payment = $paymentDataObject->getPayment(); + /** @var Order $order */ $order = $payment->getOrder(); $currencyCode = $this->chargedCurrency->getOrderAmountCurrency($order)->getCurrencyCode(); - $prefix = 'enhancedSchemeData'; - $requestBody['additionalData'][$prefix . '.totalTaxAmount'] = $this->adyenHelper->formatAmount($order->getTaxAmount(), $currencyCode); - $requestBody['additionalData'][$prefix . '.customerReference'] = $this->adyenRequestHelper->getShopperReference($order->getCustomerId(), $order->getIncrementId()); + // `totalTaxAmount` field is required and L2/L3 data can not be generated without this field. + if (empty($order->getTaxAmount()) || $order->getTaxAmount() < 0 || $order->getTaxAmount() === 0) { + $this->adyenLogger->warning(__('L2/L3 data can not be generated if tax amount is zero.')); + return $request; + } + + $additionalDataLevel23 = [ + self::ENHANCED_SCHEME_DATA_PREFIX . '.orderDate' => date('dmy', time()), + self::ENHANCED_SCHEME_DATA_PREFIX . '.customerReference' => + $this->adyenRequestHelper->getShopperReference($order->getCustomerId(), $order->getIncrementId()), + self::ENHANCED_SCHEME_DATA_PREFIX . '.totalTaxAmount' => + (string) $this->adyenHelper->formatAmount($order->getTaxAmount(), $currencyCode) + ]; + if ($order->getIsNotVirtual()) { - $requestBody['additionalData'][$prefix . '.freightAmount'] = $this->adyenHelper->formatAmount($order->getBaseShippingAmount(), $currencyCode); - $requestBody['additionalData'][$prefix . '.destinationPostalCode'] = $order->getShippingAddress()->getPostcode(); - $requestBody['additionalData'][$prefix . '.destinationCountryCode'] = $order->getShippingAddress()->getCountryId(); + $additionalDataLevel23[self::ENHANCED_SCHEME_DATA_PREFIX . '.freightAmount'] = + (string) $this->adyenHelper->formatAmount($order->getBaseShippingAmount(), $currencyCode); + + $additionalDataLevel23[self::ENHANCED_SCHEME_DATA_PREFIX . '.destinationPostalCode'] = + $order->getShippingAddress()->getPostcode(); + + $additionalDataLevel23[self::ENHANCED_SCHEME_DATA_PREFIX . '.destinationCountryCode'] = + $order->getShippingAddress()->getCountryId(); + + if (!empty($order->getShippingAddress()->getRegionCode())) { + $additionalDataLevel23[self::ENHANCED_SCHEME_DATA_PREFIX . '.destinationStateProvinceCode'] = + $order->getShippingAddress()->getRegionCode(); + } } - $itemIndex = 0; + $itemIndex = 1; foreach ($order->getItems() as $item) { - /** @var Item $item */ - if ($item->getPrice() == 0 && !empty($item->getParentItem())) { - // Products variants get added to the order as separate items, filter out the variants. + if (!$this->validateLineItem($item)) { continue; } - $itemPrefix = $prefix . '.itemDetailLine'; - $requestBody['additionalData'][$itemPrefix . $itemIndex . '.description'] = $item->getName(); - $requestBody['additionalData'][$itemPrefix . $itemIndex . '.unitPrice'] = $this->adyenHelper->formatAmount($item->getPrice(), $currencyCode); - $requestBody['additionalData'][$itemPrefix . $itemIndex . '.discountAmount'] = $this->adyenHelper->formatAmount($item->getDiscountAmount(), $currencyCode); - $requestBody['additionalData'][$itemPrefix . $itemIndex . '.commodityCode'] = $item->getQuoteItemId(); - $requestBody['additionalData'][$itemPrefix . $itemIndex . '.quantity'] = $item->getQtyOrdered(); - $requestBody['additionalData'][$itemPrefix . $itemIndex . '.productCode'] = $item->getSku(); - $requestBody['additionalData'][$itemPrefix . $itemIndex . '.totalAmount'] = $this->adyenHelper->formatAmount($item->getRowTotal(), $currencyCode); + $itemPrefix = self::ENHANCED_SCHEME_DATA_PREFIX . '.' . self::ITEM_DETAIL_LINE_PREFIX; + + $additionalDataLevel23[$itemPrefix . $itemIndex . '.description'] = $item->getName(); + $additionalDataLevel23[$itemPrefix . $itemIndex . '.discountAmount'] = + (string) $this->adyenHelper->formatAmount($item->getDiscountAmount(), $currencyCode); + $additionalDataLevel23[$itemPrefix . $itemIndex . '.commodityCode'] = (string) $item->getQuoteItemId(); + $additionalDataLevel23[$itemPrefix . $itemIndex . '.productCode'] = $item->getSku(); + $additionalDataLevel23[$itemPrefix . $itemIndex . '.unitOfMeasure'] = self::UNIT_OF_MEASURE_PCS; + $additionalDataLevel23[$itemPrefix . $itemIndex . '.quantity'] = (string) $item->getQtyOrdered(); + $additionalDataLevel23[$itemPrefix . $itemIndex . '.unitPrice'] = + (string) $this->adyenHelper->formatAmount($item->getPrice(), $currencyCode); + $additionalDataLevel23[$itemPrefix . $itemIndex . '.totalAmount'] = + (string) $this->adyenHelper->formatAmount($item->getRowTotal(), $currencyCode); $itemIndex++; } + + $request = [ + 'body' => [ + 'additionalData' => $additionalDataLevel23 + ] + ]; + } + + return $request; + } + + /** + * Required fields `unitPrice`, `totalAmount` or `quantity` can not be null or zero in the line items. + * + * @param OrderItemInterface $orderItem + * @return bool + */ + private function validateLineItem(OrderItemInterface $orderItem): bool + { + $validationResult = true; + + // `unitPrice` should be a non-zero numeric value. + if ($orderItem->getPrice() === 0) { + $validationResult = false; + } + + // `totalAmount` should be a non-zero numeric value. + if ($orderItem->getRowTotal() === 0) { + $validationResult = false; + } + + // `quantity` should be a positive integer. If not, skip the line item. + if ($orderItem->getQtyOrdered() < 1) { + $validationResult = false; } - return ['body' => $requestBody]; + return $validationResult; } } diff --git a/Test/Unit/Gateway/Request/AdditionalDataLevel23DataBuilderTest.php b/Test/Unit/Gateway/Request/AdditionalDataLevel23DataBuilderTest.php index 318ef93ccf..77ce4ab6eb 100644 --- a/Test/Unit/Gateway/Request/AdditionalDataLevel23DataBuilderTest.php +++ b/Test/Unit/Gateway/Request/AdditionalDataLevel23DataBuilderTest.php @@ -2,6 +2,8 @@ namespace Adyen\Payment\Test\Gateway\Request; +use Adyen\Payment\Logger\AdyenLogger; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Payment\Gateway\Data\OrderAdapterInterface; use Magento\Payment\Gateway\Data\PaymentDataObject; use Magento\Sales\Model\Order; @@ -17,19 +19,23 @@ use Adyen\Payment\Helper\Data; use Adyen\Payment\Helper\ChargedCurrency; use Adyen\Payment\Helper\Requests; +use PHPUnit\Framework\MockObject\MockObject; class AdditionalDataLevel23DataBuilderTest extends AbstractAdyenTestCase { - private $storeMock; - private $configMock; - private $storeManagerMock; - private $adyenHelperMock; - private $chargedCurrencyMock; - private $adyenRequestHelperMock; - - - protected function setUp(): void + protected ?AdditionalDataLevel23DataBuilder $additionalDataBuilder; + protected MockObject|StoreInterface $storeMock; + protected MockObject|Config $configMock; + protected MockObject|StoreManagerInterface $storeManagerMock; + protected MockObject|Data $adyenHelperMock; + protected MockObject|ChargedCurrency $chargedCurrencyMock; + protected MockObject|Requests $adyenRequestHelperMock; + protected MockObject|AdyenLogger $adyenLoggerMock; + + public function setUp(): void { + parent::setUp(); + $this->configMock = $this->createMock(Config::class); $this->storeManagerMock = $this->createMock(StoreManagerInterface::class); $this->chargedCurrencyMock = $this->createMock(ChargedCurrency::class); @@ -37,17 +43,41 @@ protected function setUp(): void $this->adyenHelperMock = $this->createMock(Data::class); $this->storeMock = $this->createMock(StoreInterface::class); $this->storeManagerMock->method('getStore')->willReturn($this->storeMock); + $this->adyenLoggerMock = $this->createMock(AdyenLogger::class); $this->additionalDataBuilder = new AdditionalDataLevel23DataBuilder( $this->configMock, $this->storeManagerMock, $this->chargedCurrencyMock, $this->adyenRequestHelperMock, - $this->adyenHelperMock + $this->adyenHelperMock, + $this->adyenLoggerMock ); } - public function testLevel23DataConfigurationEnabled() + public function tearDown(): void + { + parent::tearDown(); + + $this->additionalDataBuilder = null; + } + + protected static function orderTypeDataProvider(): array + { + return [ + ['isVirtual' => true], + ['isVirtual' => false], + ]; + } + + /** + * @dataProvider orderTypeDataProvider + * + * @param $isVirtual + * @return void + * @throws NoSuchEntityException + */ + public function testLevel23DataConfigurationEnabled($isVirtual) { $storeId = 1; $currencyCode = 'USD'; @@ -56,257 +86,150 @@ public function testLevel23DataConfigurationEnabled() $shopperReference = '000123'; $taxAmount = 10.00; $formattedTaxAmount = '1000'; + $shippingAmount = 5.00; $this->storeMock->method('getId')->willReturn($storeId); - $this->configMock->method('sendLevel23AdditionalData')->with($storeId)->willReturn(true); - $this->chargedCurrencyMock->method('getOrderAmountCurrency')->willReturn(new AdyenAmountCurrency(null, $currencyCode)); + $this->configMock->method('sendLevel23AdditionalData') + ->with($storeId) + ->willReturn(true); + + $this->chargedCurrencyMock->method('getOrderAmountCurrency') + ->willReturn(new AdyenAmountCurrency(null, $currencyCode)); + $this->adyenHelperMock->method('formatAmount')->willReturn($formattedTaxAmount); - $this->adyenRequestHelperMock->method('getShopperReference')->with($customerId, $orderIncrementId)->willReturn($shopperReference); + + $this->adyenRequestHelperMock->method('getShopperReference') + ->with($customerId, $orderIncrementId) + ->willReturn($shopperReference); + $itemMock1 = $this->createMock(Item::class); + $itemMock1->method('getPrice')->willReturn(0); + $itemMock2 = $this->createMock(Item::class); + $itemMock2->method('getPrice')->willReturn(10); + $itemMock2->method('getRowTotal')->willReturn(0); + + $itemMock3 = $this->createMock(Item::class); + $itemMock3->method('getPrice')->willReturn(10); + $itemMock3->method('getRowTotal')->willReturn(5); + $itemMock3->method('getQtyOrdered')->willReturn(0.5); + + $itemMock4 = $this->createMock(Item::class); + $itemMock4->method('getPrice')->willReturn(15); + $itemMock4->method('getRowTotal')->willReturn(30); + $itemMock4->method('getQtyOrdered')->willReturn(2); + $itemMock4->method('getSku')->willReturn('ABC123'); + $itemMock4->method('getDescription')->willReturn('Mock Product Description'); $orderMock = $this->createConfiguredMock(Order::class, [ 'getCustomerId' => $customerId, 'getIncrementId' => $orderIncrementId, 'getTaxAmount' => $taxAmount, - 'getItems' => [$itemMock1, $itemMock2] + 'getItems' => [$itemMock1, $itemMock2, $itemMock3, $itemMock4], + 'getIsNotVirtual' => !$isVirtual, + 'getBaseShippingAmount' => $shippingAmount ]); + $shippingAddressMock = $this->createMock(Address::class); + $shippingAddressMock->method('getPostcode')->willReturn('12345'); + $shippingAddressMock->method('getCountryId')->willReturn('US'); + $shippingAddressMock->method('getRegionCode')->willReturn('MI'); + + $orderMock->method('getShippingAddress')->willReturn($shippingAddressMock); + $orderAdapterMock = $this->createMock(OrderAdapterInterface::class); $paymentMock = $this->createMock(Payment::class); $paymentMock->method('getOrder')->willReturn($orderMock); $paymentDataObject = new PaymentDataObject($orderAdapterMock, $paymentMock); $buildSubject = ['payment' => $paymentDataObject, 'order' => $orderMock]; $result = $this->additionalDataBuilder->build($buildSubject); - $expectedResult = [ - 'body' => [ - 'additionalData' => [ - 'enhancedSchemeData.totalTaxAmount' => '1000', - 'enhancedSchemeData.customerReference' => '000123', - 'enhancedSchemeData.itemDetailLine0.description' => null, - 'enhancedSchemeData.itemDetailLine0.unitPrice' => '1000', - 'enhancedSchemeData.itemDetailLine0.discountAmount' => '1000', - 'enhancedSchemeData.itemDetailLine0.commodityCode' => null, - 'enhancedSchemeData.itemDetailLine0.quantity' => null, - 'enhancedSchemeData.itemDetailLine0.productCode' => null, - 'enhancedSchemeData.itemDetailLine0.totalAmount' => '1000', - 'enhancedSchemeData.itemDetailLine1.description' => null, - 'enhancedSchemeData.itemDetailLine1.unitPrice' => '1000', - 'enhancedSchemeData.itemDetailLine1.discountAmount' => '1000', - 'enhancedSchemeData.itemDetailLine1.commodityCode' => null, - 'enhancedSchemeData.itemDetailLine1.quantity' => null, - 'enhancedSchemeData.itemDetailLine1.productCode' => null, - 'enhancedSchemeData.itemDetailLine1.totalAmount' => '1000', - ] - ] - ]; - $this->assertEquals($expectedResult, $result); + $this->assertArrayHasKey('additionalData', $result['body']); + $this->assertArrayHasKey(AdditionalDataLevel23DataBuilder::ENHANCED_SCHEME_DATA_PREFIX . '.orderDate', $result['body']['additionalData']); + $this->assertArrayHasKey(AdditionalDataLevel23DataBuilder::ENHANCED_SCHEME_DATA_PREFIX . '.customerReference', $result['body']['additionalData']); + $this->assertArrayHasKey(AdditionalDataLevel23DataBuilder::ENHANCED_SCHEME_DATA_PREFIX . '.totalTaxAmount', $result['body']['additionalData']); + + if (!$isVirtual) { + $this->assertArrayHasKey(AdditionalDataLevel23DataBuilder::ENHANCED_SCHEME_DATA_PREFIX . '.freightAmount', $result['body']['additionalData']); + $this->assertArrayHasKey(AdditionalDataLevel23DataBuilder::ENHANCED_SCHEME_DATA_PREFIX . '.destinationPostalCode', $result['body']['additionalData']); + $this->assertArrayHasKey(AdditionalDataLevel23DataBuilder::ENHANCED_SCHEME_DATA_PREFIX . '.destinationCountryCode', $result['body']['additionalData']); + $this->assertArrayHasKey(AdditionalDataLevel23DataBuilder::ENHANCED_SCHEME_DATA_PREFIX . '.destinationStateProvinceCode', $result['body']['additionalData']); + } + + $itemArrayKey = AdditionalDataLevel23DataBuilder::ENHANCED_SCHEME_DATA_PREFIX . '.' . + AdditionalDataLevel23DataBuilder::ITEM_DETAIL_LINE_PREFIX; + + $this->assertArrayHasKey($itemArrayKey . '1.productCode', $result['body']['additionalData']); + $this->assertArrayHasKey($itemArrayKey . '1.description', $result['body']['additionalData']); + $this->assertArrayHasKey($itemArrayKey . '1.quantity', $result['body']['additionalData']); + $this->assertArrayHasKey($itemArrayKey . '1.unitOfMeasure', $result['body']['additionalData']); + $this->assertArrayHasKey($itemArrayKey . '1.commodityCode', $result['body']['additionalData']); + $this->assertArrayHasKey($itemArrayKey . '1.totalAmount', $result['body']['additionalData']); + $this->assertArrayHasKey($itemArrayKey . '1.unitPrice', $result['body']['additionalData']); + + // Index starts from 1 + $this->assertArrayNotHasKey($itemArrayKey . '0.productCode', $result['body']['additionalData']); + + // Only one line item is valid, others should be cleaned up + $this->assertArrayNotHasKey($itemArrayKey . '2.productCode', $result['body']['additionalData']); + $this->assertArrayNotHasKey($itemArrayKey . '3.productCode', $result['body']['additionalData']); } + public function testLevel23DataConfigurationDisabled() { $storeId = 1; - $orderIncrementId = '000000123'; - $customerId = 123; - $taxAmount = 10.00; $this->storeMock->method('getId')->willReturn($storeId); $this->configMock->method('sendLevel23AdditionalData')->with($storeId)->willReturn(false); - $itemMock1 = $this->createMock(Item::class); - $itemMock2 = $this->createMock(Item::class); - $orderMock = $this->createConfiguredMock(Order::class, [ - 'getCustomerId' => $customerId, - 'getIncrementId' => $orderIncrementId, - 'getTaxAmount' => $taxAmount, - 'getItems' => [$itemMock1, $itemMock2] - ]); + $paymentDataObjectMock = $this->createMock(PaymentDataObject::class); + $orderMock = $this->createMock(Order::class); + $buildSubject = ['payment' => $paymentDataObjectMock, 'order' => $orderMock]; - $orderAdapterMock = $this->createMock(OrderAdapterInterface::class); - $paymentMock = $this->createMock(Payment::class); - $paymentMock->method('getOrder')->willReturn($orderMock); - $paymentDataObject = new PaymentDataObject($orderAdapterMock, $paymentMock); - $buildSubject = ['payment' => $paymentDataObject, 'order' => $orderMock]; $result = $this->additionalDataBuilder->build($buildSubject); - $expectedResult = [ - 'body' => [] - ]; - - $this->assertEquals($expectedResult, $result); + $this->assertEmpty($result); } - public function testVirtualOrder() + protected static function taxAmountDataProvider(): array { - $storeId = 1; - $currencyCode = 'USD'; - $customerId = 123; - $orderIncrementId = '000000123'; - $shopperReference = '000123'; - $taxAmount = 10.00; - $formattedTaxAmount = '1000'; - - $this->storeMock->method('getId')->willReturn($storeId); - $this->configMock->method('sendLevel23AdditionalData')->with($storeId)->willReturn(true); - $this->chargedCurrencyMock->method('getOrderAmountCurrency')->willReturn(new AdyenAmountCurrency(null, $currencyCode)); - $this->adyenHelperMock->method('formatAmount')->willReturn($formattedTaxAmount); - $this->adyenRequestHelperMock->method('getShopperReference')->with($customerId, $orderIncrementId)->willReturn($shopperReference); - - $orderMock = $this->createConfiguredMock(Order::class, [ - 'getCustomerId' => $customerId, - 'getIncrementId' => $orderIncrementId, - 'getTaxAmount' => $taxAmount, - 'getItems' => [], - 'getIsNotVirtual' => false, - 'getShippingAddress' => null, - 'getBaseShippingAmount' => 0.00 - ]); - - $orderAdapterMock = $this->createMock(OrderAdapterInterface::class); - $paymentMock = $this->createMock(Payment::class); - $paymentMock->method('getOrder')->willReturn($orderMock); - $paymentDataObject = new PaymentDataObject($orderAdapterMock, $paymentMock); - $buildSubject = ['payment' => $paymentDataObject, 'order' => $orderMock]; - $result = $this->additionalDataBuilder->build($buildSubject); - - $expectedResult = [ - 'body' => [ - 'additionalData' => [ - 'enhancedSchemeData.totalTaxAmount' => '1000', - 'enhancedSchemeData.customerReference' => '000123' - ] - ] + return [ + ['taxAmount' => 0], + ['taxAmount' => null], + ['taxAmount' => -1] ]; - - $this->assertEquals($expectedResult, $result); } - public function testNonVirtualOrder() + /** + * @dataProvider taxAmountDataProvider + * + * @param $taxAmount + * @return void + * @throws NoSuchEntityException + */ + public function testLevel23DataInvalidTaxAmounts($taxAmount) { $storeId = 1; $currencyCode = 'USD'; - $customerId = 123; - $orderIncrementId = '000000123'; - $shopperReference = '000123'; - $taxAmount = 10.00; - $shippingAmount = 5.00; - $formattedAmount = '1000'; - $postalCode = '12345'; - $countryId = 'US'; + + $this->chargedCurrencyMock->method('getOrderAmountCurrency') + ->willReturn(new AdyenAmountCurrency(null, $currencyCode)); $this->storeMock->method('getId')->willReturn($storeId); $this->configMock->method('sendLevel23AdditionalData')->with($storeId)->willReturn(true); - $this->chargedCurrencyMock->method('getOrderAmountCurrency')->willReturn(new AdyenAmountCurrency(null, $currencyCode)); - $this->adyenHelperMock->method('formatAmount')->willReturn($formattedAmount); - $this->adyenRequestHelperMock->method('getShopperReference')->with($customerId, $orderIncrementId)->willReturn($shopperReference); - $shippingAddressMock = $this->createMock(Address::class); - $shippingAddressMock->method('getPostcode')->willReturn($postalCode); - $shippingAddressMock->method('getCountryId')->willReturn($countryId); - $orderMock = $this->createConfiguredMock(Order::class, [ - 'getCustomerId' => $customerId, - 'getIncrementId' => $orderIncrementId, - 'getTaxAmount' => $taxAmount, - 'getIsNotVirtual' => true, - 'getShippingAddress' => $shippingAddressMock, - 'getBaseShippingAmount' => $shippingAmount, - 'getItems' => [] - ]); + $orderMock = $this->createMock(Order::class); + $orderMock->method('getTaxAmount')->willReturn($taxAmount); - $orderAdapterMock = $this->createMock(OrderAdapterInterface::class); $paymentMock = $this->createMock(Payment::class); $paymentMock->method('getOrder')->willReturn($orderMock); - $paymentDataObject = new PaymentDataObject($orderAdapterMock, $paymentMock); - $buildSubject = ['payment' => $paymentDataObject, 'order' => $orderMock]; - $result = $this->additionalDataBuilder->build($buildSubject); - $expectedResult = [ - 'body' => [ - 'additionalData' => [ - 'enhancedSchemeData.totalTaxAmount' => $formattedAmount, - 'enhancedSchemeData.customerReference' => $shopperReference, - 'enhancedSchemeData.freightAmount' => $formattedAmount, - 'enhancedSchemeData.destinationPostalCode' => $postalCode, - 'enhancedSchemeData.destinationCountryCode' => $countryId - ] - ] - ]; - - $this->assertEquals($expectedResult, $result); - } - - public function testOrderWithDiscount() - { - $storeId = 1; - $currencyCode = 'USD'; - $customerId = 123; - $orderIncrementId = '000000123'; - $shopperReference = '000123'; - $taxAmount = 10.00; - $formattedAmount = '1000'; - $discountAmount = 2.00; - - $this->storeMock->method('getId')->willReturn($storeId); - $this->configMock->method('sendLevel23AdditionalData')->with($storeId)->willReturn(true); - $this->chargedCurrencyMock->method('getOrderAmountCurrency')->willReturn(new AdyenAmountCurrency(null, $currencyCode)); - $this->adyenHelperMock->method('formatAmount')->willReturn($formattedAmount); - $this->adyenRequestHelperMock->method('getShopperReference')->with($customerId, $orderIncrementId)->willReturn($shopperReference); - - $itemMock1 = $this->createConfiguredMock(Item::class, [ - 'getPrice' => 10.00, - 'getDiscountAmount' => $discountAmount, - 'getName' => 'Item 1', - 'getQuoteItemId' => 101, - 'getQtyOrdered' => 1, - 'getSku' => 'sku-1', - 'getRowTotal' => 8.00 - ]); - $itemMock2 = $this->createConfiguredMock(Item::class, [ - 'getPrice' => 20.00, - 'getDiscountAmount' => $discountAmount, - 'getName' => 'Item 2', - 'getQuoteItemId' => 102, - 'getQtyOrdered' => 1, - 'getSku' => 'sku-2', - 'getRowTotal' => 18.00 - ]); + $paymentDataObjectMock = $this->createMock(PaymentDataObject::class); + $paymentDataObjectMock->method('getPayment')->willReturn($paymentMock); - $orderMock = $this->createConfiguredMock(Order::class, [ - 'getCustomerId' => $customerId, - 'getIncrementId' => $orderIncrementId, - 'getTaxAmount' => $taxAmount, - 'getItems' => [$itemMock1, $itemMock2] - ]); + $buildSubject = ['payment' => $paymentDataObjectMock, 'order' => $orderMock]; - $orderAdapterMock = $this->createMock(OrderAdapterInterface::class); - $paymentMock = $this->createMock(Payment::class); - $paymentMock->method('getOrder')->willReturn($orderMock); - $paymentDataObject = new PaymentDataObject($orderAdapterMock, $paymentMock); - $buildSubject = ['payment' => $paymentDataObject, 'order' => $orderMock]; $result = $this->additionalDataBuilder->build($buildSubject); - $expectedResult = [ - 'body' => [ - 'additionalData' => [ - 'enhancedSchemeData.totalTaxAmount' => '1000', - 'enhancedSchemeData.customerReference' => '000123', - 'enhancedSchemeData.itemDetailLine0.description' => 'Item 1', - 'enhancedSchemeData.itemDetailLine0.unitPrice' => '1000', - 'enhancedSchemeData.itemDetailLine0.discountAmount' => '1000', - 'enhancedSchemeData.itemDetailLine0.commodityCode' => 101, - 'enhancedSchemeData.itemDetailLine0.quantity' => 1, - 'enhancedSchemeData.itemDetailLine0.productCode' => 'sku-1', - 'enhancedSchemeData.itemDetailLine0.totalAmount' => '1000', - 'enhancedSchemeData.itemDetailLine1.description' => 'Item 2', - 'enhancedSchemeData.itemDetailLine1.unitPrice' => '1000', - 'enhancedSchemeData.itemDetailLine1.discountAmount' => '1000', - 'enhancedSchemeData.itemDetailLine1.commodityCode' => 102, - 'enhancedSchemeData.itemDetailLine1.quantity' => 1, - 'enhancedSchemeData.itemDetailLine1.productCode' => 'sku-2', - 'enhancedSchemeData.itemDetailLine1.totalAmount' => '1000', - ] - ] - ]; - - $this->assertEquals($expectedResult, $result); + $this->assertEmpty($result); } } diff --git a/etc/adminhtml/system/adyen_card_payments.xml b/etc/adminhtml/system/adyen_card_payments.xml index b356ad22a9..f3daaa5609 100755 --- a/etc/adminhtml/system/adyen_card_payments.xml +++ b/etc/adminhtml/system/adyen_card_payments.xml @@ -58,13 +58,13 @@ payment/adyen_cc/installments - + Magento\Config\Model\Config\Source\Yesno payment/adyen_abstract/send_level23_data Adyen documentation + Adyen documentation for more information. ]]> From 0191930ba314e23f42763dc708715f256908ad5a Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Tue, 26 Nov 2024 13:24:47 +0100 Subject: [PATCH 05/19] Bump Playwright Docker image version (#2817) Co-authored-by: Can Demiralp --- .github/docker-compose.e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/docker-compose.e2e.yml b/.github/docker-compose.e2e.yml index d9cea1b6ec..9f8135523c 100644 --- a/.github/docker-compose.e2e.yml +++ b/.github/docker-compose.e2e.yml @@ -1,7 +1,7 @@ version: '3' services: playwright: - image: mcr.microsoft.com/playwright:v1.47.2 + image: mcr.microsoft.com/playwright:v1.49.2 shm_size: 1gb ipc: host cap_add: From 9a2e26cae40c3cb6ec578dd8d69656063035ed61 Mon Sep 17 00:00:00 2001 From: candemiralp <20255503+candemiralp@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:25:13 +0000 Subject: [PATCH 06/19] chore(release): bump to 9.12.0 --- VERSION | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index ea5a459926..2f0dbe6f68 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -9.11.0 +9.12.0 diff --git a/composer.json b/composer.json index b49c34c77b..6f63b7be15 100755 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "adyen/module-payment", "description": "Official Magento2 Plugin to connect to Payment Service Provider Adyen.", "type": "magento2-module", - "version": "9.11.0", + "version": "9.12.0", "license": "MIT", "repositories": [ { From b54cd0f18ab5ff7db52bb07acc0bf0bba2493851 Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Tue, 26 Nov 2024 15:06:49 +0100 Subject: [PATCH 07/19] Update docker-compose.e2e.yml --- .github/docker-compose.e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/docker-compose.e2e.yml b/.github/docker-compose.e2e.yml index 9f8135523c..468be5840b 100644 --- a/.github/docker-compose.e2e.yml +++ b/.github/docker-compose.e2e.yml @@ -1,7 +1,7 @@ version: '3' services: playwright: - image: mcr.microsoft.com/playwright:v1.49.2 + image: mcr.microsoft.com/playwright:v1.49.0 shm_size: 1gb ipc: host cap_add: From e69b12f3c72d64558f33575969315edbe2f08c71 Mon Sep 17 00:00:00 2001 From: khushboos Date: Mon, 2 Dec 2024 15:02:46 +0100 Subject: [PATCH 08/19] Updating Paybylink description by typecasting the Description --- Gateway/Request/DescriptionDataBuilder.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Gateway/Request/DescriptionDataBuilder.php b/Gateway/Request/DescriptionDataBuilder.php index 9c6d81f23d..ed67a5ca6c 100644 --- a/Gateway/Request/DescriptionDataBuilder.php +++ b/Gateway/Request/DescriptionDataBuilder.php @@ -29,7 +29,13 @@ public function build(array $buildSubject) $payment = $paymentDataObject->getPayment(); /** @var Order $order */ $order = $payment->getOrder(); - $request['body']['description'] = 'Order ' . $order->getIncrementId() . ' from ' . $order->getStore()->getGroup()->getName(); + + $request['body']['description'] = (string)__( + 'Order %1 from %2', + $order->getIncrementId(), + $order->getStore()->getGroup()->getName() + ); + return $request; } } From 3da5f6d11d4007e719229cc4194e682b172dfa1f Mon Sep 17 00:00:00 2001 From: khushboos Date: Mon, 2 Dec 2024 16:10:16 +0100 Subject: [PATCH 09/19] Updating Paybylink description by rendering the translated the Description --- Gateway/Request/DescriptionDataBuilder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gateway/Request/DescriptionDataBuilder.php b/Gateway/Request/DescriptionDataBuilder.php index ed67a5ca6c..3bd2c252e9 100644 --- a/Gateway/Request/DescriptionDataBuilder.php +++ b/Gateway/Request/DescriptionDataBuilder.php @@ -30,11 +30,11 @@ public function build(array $buildSubject) /** @var Order $order */ $order = $payment->getOrder(); - $request['body']['description'] = (string)__( + $request['body']['description'] = __( 'Order %1 from %2', $order->getIncrementId(), $order->getStore()->getGroup()->getName() - ); + )->render(); return $request; } From e8bb4ec73b9b8fe42238f7b641e326ec4ff7c432 Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Thu, 5 Dec 2024 14:00:34 +0100 Subject: [PATCH 10/19] [ECP-9543] Enable Dependabot with basic configuration (#2820) * [ECP-9543] Enable Dependabot with basic configuration * [ECP-9543] Update Dependabot rules * [ECP-9543] Formatting --------- Co-authored-by: Can Demiralp --- .github/dependabot.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..9c9805543f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,20 @@ +version: 2 + +updates: + - package-ecosystem: "composer" + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 3 + labels: + - "dependencies" + target-branch: "main" + + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 3 + labels: + - "dependencies" + target-branch: "main" From 6c33a35bddbc21b406230d14ca4aa0d0293c6826 Mon Sep 17 00:00:00 2001 From: khushboos Date: Mon, 9 Dec 2024 12:31:59 +0530 Subject: [PATCH 11/19] Fixing refund response handler if an exception is thrown --- Gateway/Http/Client/TransactionRefund.php | 2 + .../Http/Client/TransactionRefundTest.php | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/Gateway/Http/Client/TransactionRefund.php b/Gateway/Http/Client/TransactionRefund.php index 3484c030d0..238f18fb79 100644 --- a/Gateway/Http/Client/TransactionRefund.php +++ b/Gateway/Http/Client/TransactionRefund.php @@ -88,6 +88,8 @@ public function placeRequest(TransferInterface $transferObject): array $this->adyenHelper->logResponse($responseData); } catch (AdyenException $e) { $this->adyenHelper->logAdyenException($e); + $responseData['error'] = $e->getMessage(); + $responseData['errorCode'] = $e->getAdyenErrorCode(); } $responses[] = $responseData; } diff --git a/Test/Unit/Gateway/Http/Client/TransactionRefundTest.php b/Test/Unit/Gateway/Http/Client/TransactionRefundTest.php index 3d98825999..21b2730451 100644 --- a/Test/Unit/Gateway/Http/Client/TransactionRefundTest.php +++ b/Test/Unit/Gateway/Http/Client/TransactionRefundTest.php @@ -21,6 +21,7 @@ use Adyen\Service\Checkout\ModificationsApi; use Magento\Payment\Gateway\Http\TransferInterface; use PHPUnit\Framework\MockObject\MockObject; +use Adyen\AdyenException; class TransactionRefundTest extends AbstractAdyenTestCase { @@ -102,4 +103,46 @@ public function testPlaceRequestIncludesHeadersInRequest() $this->assertCount(1, $responses); $this->assertArrayHasKey('pspReference', $responses[0]); } + + public function testPlaceRequestHandlesException() + { + $requestBody = [ + 'amount' => ['value' => 1000, 'currency' => 'EUR'], + 'paymentPspReference' => '123456789' + ]; + + $headers = ['idempotencyExtraData' => ['order_id' => '1001']]; + + $transferObjectMock = $this->createConfiguredMock(TransferInterface::class, [ + 'getBody' => [$requestBody], + 'getHeaders' => $headers, + 'getClientConfig' => [] + ]); + + $serviceMock = $this->createMock(ModificationsApi::class); + $adyenClientMock = $this->createMock(Client::class); + + $this->adyenHelperMock->method('initializeAdyenClientWithClientConfig')->willReturn($adyenClientMock); + $this->adyenHelperMock->method('initializeModificationsApi')->willReturn($serviceMock); + $this->adyenHelperMock->method('buildRequestHeaders')->willReturn(['custom-header' => 'value']); + + $this->idempotencyHelperMock->expects($this->once()) + ->method('generateIdempotencyKey') + ->with($requestBody, $headers['idempotencyExtraData']) + ->willReturn('generated_idempotency_key'); + + $serviceMock->expects($this->once()) + ->method('refundCapturedPayment') + ->willThrowException(new AdyenException()); + + $this->adyenHelperMock->expects($this->once()) + ->method('logAdyenException') + ->with($this->isInstanceOf(AdyenException::class)); + + $responses = $this->transactionRefund->placeRequest($transferObjectMock); + $this->assertIsArray($responses); + $this->assertCount(1, $responses); + $this->assertArrayHasKey('error', $responses[0]); + $this->assertArrayHasKey('errorCode', $responses[0]); + } } From 01e805750bd10f4c4386220feb820f69bd0cc029 Mon Sep 17 00:00:00 2001 From: khushboo-singhvi <7098889+khushboo-singhvi@users.noreply.github.com> Date: Tue, 10 Dec 2024 09:26:31 +0000 Subject: [PATCH 12/19] chore(release): bump to 9.12.1 --- VERSION | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 2f0dbe6f68..69dd356299 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -9.12.0 +9.12.1 diff --git a/composer.json b/composer.json index 6f63b7be15..ed7b5a459b 100755 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "adyen/module-payment", "description": "Official Magento2 Plugin to connect to Payment Service Provider Adyen.", "type": "magento2-module", - "version": "9.12.0", + "version": "9.12.1", "license": "MIT", "repositories": [ { From 8e6a869c4b7fff007c4908afe6b70ba8f1891b70 Mon Sep 17 00:00:00 2001 From: Pieter Zandbergen Date: Fri, 13 Dec 2024 15:06:21 +0100 Subject: [PATCH 13/19] fix: Handle errors on placeOrder mutations (#2799) The PlaceOrder resolver does not necessary return an order, in case of errors. See https://github.com/magento/magento2/blob/2.4-develop/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php#L106 Co-authored-by: Can Demiralp --- Plugin/GraphQlPlaceOrderAddCartId.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Plugin/GraphQlPlaceOrderAddCartId.php b/Plugin/GraphQlPlaceOrderAddCartId.php index ee360eb04f..bf88af05b8 100644 --- a/Plugin/GraphQlPlaceOrderAddCartId.php +++ b/Plugin/GraphQlPlaceOrderAddCartId.php @@ -59,6 +59,10 @@ public function __construct( */ public function afterResolve(PlaceOrder $placeOrder, array $result): array { + if (!isset($result['order'])) { + return $result; + } + try { $cart = $this->quoteHelper->getQuoteByOrderIncrementId($result['order']['order_number']); $maskedId = $this->quoteIdToMaskedQuoteId->execute($cart->getId()); From ea6e8fc242761ce02e6c07bb03c4e1aa19ff3413 Mon Sep 17 00:00:00 2001 From: Pieter Zandbergen Date: Fri, 13 Dec 2024 15:06:58 +0100 Subject: [PATCH 14/19] fix: Sort Search Result when fetching Order by Quote ID (#2824) * fix: Sort Search Result when fetching Order by Quote ID Multiple orders might exist for the same Quote. We should be using the most recent Order when dealing with the Payment. * fix: Updated Unit Test for OrderRepository * fix: Missing create call on Sort Order Builder --------- Co-authored-by: Khushboo Co-authored-by: Can Demiralp --- Model/Sales/OrderRepository.php | 9 +++++++++ Test/Unit/Model/Sales/OrderRepositoryTest.php | 20 +++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/Model/Sales/OrderRepository.php b/Model/Sales/OrderRepository.php index d51680b268..0919294075 100644 --- a/Model/Sales/OrderRepository.php +++ b/Model/Sales/OrderRepository.php @@ -16,6 +16,7 @@ use Magento\Framework\Api\Search\FilterGroupBuilder; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Api\SortOrderBuilder; use Magento\Framework\Serialize\Serializer\Json as JsonSerializer; use Magento\Payment\Api\Data\PaymentAdditionalInfoInterfaceFactory; use Magento\Sales\Api\Data\OrderExtensionFactory; @@ -28,6 +29,7 @@ class OrderRepository extends SalesOrderRepository { private SearchCriteriaBuilder $searchCriteriaBuilder; + private SortOrderBuilder $sortOrderBuilder; private FilterBuilder $filterBuilder; private FilterGroupBuilder $filterGroupBuilder; @@ -35,6 +37,7 @@ public function __construct( SearchCriteriaBuilder $searchCriteriaBuilder, FilterBuilder $filterBuilder, FilterGroupBuilder $filterGroupBuilder, + SortOrderBuilder $sortOrderBuilder, Metadata $metadata, SearchResultFactory $searchResultFactory, CollectionProcessorInterface $collectionProcessor = null, @@ -56,6 +59,7 @@ public function __construct( ); $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->sortOrderBuilder = $sortOrderBuilder; $this->filterBuilder = $filterBuilder; $this->filterGroupBuilder = $filterGroupBuilder; } @@ -68,9 +72,14 @@ public function getOrderByQuoteId(int $quoteId): OrderInterface|false ->create(); $quoteIdFilterGroup = $this->filterGroupBuilder->setFilters([$quoteIdFilter])->create(); + $sortOrder = $this->sortOrderBuilder->setField('entity_id') + ->setDescendingDirection() + ->create(); $searchCriteria = $this->searchCriteriaBuilder ->setFilterGroups([$quoteIdFilterGroup]) + ->setSortOrders([$sortOrder]) + ->setPageSize(1) ->create(); $orders = $this->getList($searchCriteria)->getItems(); diff --git a/Test/Unit/Model/Sales/OrderRepositoryTest.php b/Test/Unit/Model/Sales/OrderRepositoryTest.php index 261640f068..a0e9714810 100644 --- a/Test/Unit/Model/Sales/OrderRepositoryTest.php +++ b/Test/Unit/Model/Sales/OrderRepositoryTest.php @@ -19,6 +19,8 @@ use Magento\Framework\Api\Search\FilterGroupBuilder; use Magento\Framework\Api\SearchCriteria; use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Api\SortOrder; +use Magento\Framework\Api\SortOrderBuilder; use Magento\Sales\Api\Data\OrderInterface; use Magento\Sales\Api\Data\OrderSearchResultInterface; use ReflectionClass; @@ -41,10 +43,15 @@ public function testGetOrderByQuoteId() $filterGroupBuilderMock->method('create') ->willReturn($this->createMock(FilterGroup::class)); + $sortOrderBuilderMock = $this->createPartialMock(SortOrderBuilder::class, ['create']); + $sortOrderBuilderMock->method('create') + ->willReturn($this->createMock(SortOrder::class)); + $orderRepository = $this->buildOrderRepositoryClass( $searchCriteriaBuilderMock, $filterBuilderMock, - $filterGroupBuilderMock + $filterGroupBuilderMock, + $sortOrderBuilderMock ); $order = $orderRepository->getOrderByQuoteId($quoteId); @@ -54,7 +61,8 @@ public function testGetOrderByQuoteId() public function buildOrderRepositoryClass( $searchCriteriaBuilderMock = null, $filterBuilderMock = null, - $filterGroupBuilderMock = null + $filterGroupBuilderMock = null, + $sortOrderBuilderMock = null ): OrderRepository { if (is_null($searchCriteriaBuilderMock)) { $searchCriteriaBuilderMock = $this->createMock(SearchCriteriaBuilder::class); @@ -68,6 +76,10 @@ public function buildOrderRepositoryClass( $filterGroupBuilderMock = $this->createMock(FilterGroupBuilder::class); } + if (is_null($sortOrderBuilderMock)) { + $sortOrderBuilderMock = $this->createMock(SortOrderBuilder::class); + } + $orderRepositoryPartialMock = $this->getMockBuilder(OrderRepository::class) ->setMethods(['getList']) ->disableOriginalConstructor() @@ -87,6 +99,10 @@ public function buildOrderRepositoryClass( $filterGroupBuilderProperty->setAccessible(true); $filterGroupBuilderProperty->setValue($orderRepositoryPartialMock, $filterGroupBuilderMock); + $sortOrderBuilderProperty = $reflection->getProperty('sortOrderBuilder'); + $sortOrderBuilderProperty->setAccessible(true); + $sortOrderBuilderProperty->setValue($orderRepositoryPartialMock, $sortOrderBuilderMock); + $orderSearchResultMock = $this->createConfiguredMock(OrderSearchResultInterface::class, [ 'getItems' => [$this->createMock(OrderInterface::class)] ]); From 218c5918add2377e855c0c856e4d93e20712c5d7 Mon Sep 17 00:00:00 2001 From: khushboos Date: Mon, 16 Dec 2024 16:40:36 +0530 Subject: [PATCH 15/19] Fixing errors from security point of view --- view/frontend/web/template/payment/cc-form.html | 2 +- view/frontend/web/template/payment/cc-vault-form.html | 4 ++-- view/frontend/web/template/payment/pm-vault-form.html | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/view/frontend/web/template/payment/cc-form.html b/view/frontend/web/template/payment/cc-form.html index b93b74ee04..a0df9a907b 100755 --- a/view/frontend/web/template/payment/cc-form.html +++ b/view/frontend/web/template/payment/cc-form.html @@ -63,7 +63,7 @@ -
+
diff --git a/view/frontend/web/template/payment/cc-vault-form.html b/view/frontend/web/template/payment/cc-vault-form.html index ddc824d3c9..515ed6ba91 100644 --- a/view/frontend/web/template/payment/cc-vault-form.html +++ b/view/frontend/web/template/payment/cc-vault-form.html @@ -21,7 +21,7 @@ value: getId(), click: selectPaymentMethod, checked: isChecked, - visible: isRadioButtonVisible()"/> + visible: isRadioButtonVisible()">