From ba2f77b2a469bae2f43de10b5821ee60a9b2f390 Mon Sep 17 00:00:00 2001 From: Ryan Lambert Date: Wed, 22 May 2024 09:16:58 -0500 Subject: [PATCH 1/4] add optional quoteId to getShopperReference to allow tokenization for guest-carts --- .../AdditionalDataLevel23DataBuilder.php | 2 +- Helper/Requests.php | 7 ++- Plugin/PaymentVaultDeleteToken.php | 2 +- .../AdditionalDataLevel23DataBuilderTest.php | 47 +++++++++++++++++++ 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/Gateway/Request/AdditionalDataLevel23DataBuilder.php b/Gateway/Request/AdditionalDataLevel23DataBuilder.php index c07d049e18..5961a23569 100644 --- a/Gateway/Request/AdditionalDataLevel23DataBuilder.php +++ b/Gateway/Request/AdditionalDataLevel23DataBuilder.php @@ -72,7 +72,7 @@ public function build(array $buildSubject) $prefix = 'enhancedSchemeData'; $requestBody['additionalData'][$prefix . '.totalTaxAmount'] = $this->adyenHelper->formatAmount($order->getTaxAmount(), $currencyCode); - $requestBody['additionalData'][$prefix . '.customerReference'] = $this->adyenRequestHelper->getShopperReference($order->getCustomerId(), $order->getIncrementId()); + $requestBody['additionalData'][$prefix . '.customerReference'] = $this->adyenRequestHelper->getShopperReference($order->getCustomerId(), $order->getIncrementId(), $payment->getOrder()->getQuoteId()); if ($order->getIsNotVirtual()) { $requestBody['additionalData'][$prefix . '.freightAmount'] = $this->adyenHelper->formatAmount($order->getBaseShippingAmount(), $currencyCode); $requestBody['additionalData'][$prefix . '.destinationPostalCode'] = $order->getShippingAddress()->getPostcode(); diff --git a/Helper/Requests.php b/Helper/Requests.php index 9d7aa7c21e..fc127b6ed7 100644 --- a/Helper/Requests.php +++ b/Helper/Requests.php @@ -86,7 +86,7 @@ public function buildCustomerData( $additionalData = null, $request = [] ) { - $request['shopperReference'] = $this->getShopperReference($customerId, $payment->getOrder()->getIncrementId()); + $request['shopperReference'] = $this->getShopperReference($customerId, $payment->getOrder()->getIncrementId(), $payment->getOrder()->getQuoteId()); // In case of virtual product and guest checkout there is a workaround to get the guest's email address if (!empty($additionalData['guestEmail'])) { @@ -417,13 +417,12 @@ public function buildDonationData($buildSubject, $storeId): array * @param string $orderIncrementId * @return string */ - public function getShopperReference($customerId, $orderIncrementId): string + public function getShopperReference($customerId, $orderIncrementId, $quoteId): string { if ($customerId) { $shopperReference = $this->adyenHelper->padShopperReference($customerId); } else { - $uuid = Uuid::generateV4(); - $guestCustomerId = $orderIncrementId . $uuid; + $guestCustomerId = $quoteId ? "guest-cart" . quoteId : orderIncrementId . Uuid::generateV4(); $shopperReference = $guestCustomerId; } diff --git a/Plugin/PaymentVaultDeleteToken.php b/Plugin/PaymentVaultDeleteToken.php index 1712b1e02e..1f08e433a8 100644 --- a/Plugin/PaymentVaultDeleteToken.php +++ b/Plugin/PaymentVaultDeleteToken.php @@ -94,7 +94,7 @@ private function createDisableTokenRequest(PaymentTokenInterface $paymentToken): $paymentToken->getPaymentMethodCode() ), Requests::SHOPPER_REFERENCE => - $this->requestsHelper->getShopperReference($paymentToken->getCustomerId(), null), + $this->requestsHelper->getShopperReference($paymentToken->getCustomerId(), null, null), Requests::RECURRING_DETAIL_REFERENCE => $paymentToken->getGatewayToken() ]; } diff --git a/Test/Unit/Gateway/Request/AdditionalDataLevel23DataBuilderTest.php b/Test/Unit/Gateway/Request/AdditionalDataLevel23DataBuilderTest.php index 318ef93ccf..ea1938f168 100644 --- a/Test/Unit/Gateway/Request/AdditionalDataLevel23DataBuilderTest.php +++ b/Test/Unit/Gateway/Request/AdditionalDataLevel23DataBuilderTest.php @@ -181,6 +181,53 @@ public function testVirtualOrder() $this->assertEquals($expectedResult, $result); } + public function testVirtualOrderGuest() + { + $storeId = 1; + $quoteId = 123; + $currencyCode = 'USD'; + $customerId = null; + $orderIncrementId = '000000123'; + $shopperReference = 'guest-cart-123'; + $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(null, $orderIncrementId, $quoteId)->willReturn($shopperReference); + + $orderMock = $this->createConfiguredMock(Order::class, [ + 'getCustomerId' => $customerId, + 'getIncrementId' => $orderIncrementId, + 'getTaxAmount' => $taxAmount, + 'getItems' => [], + 'getIsNotVirtual' => false, + 'getShippingAddress' => null, + 'getBaseShippingAmount' => 0.00, + 'getQuoteId' => '123' + ]); + + $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' => $shopperReference + ] + ] + ]; + + $this->assertEquals($expectedResult, $result); + } + public function testNonVirtualOrder() { $storeId = 1; From 6e8d7551b3e570f1efbb37d7610b221f59c319ca Mon Sep 17 00:00:00 2001 From: Ryan Lambert Date: Wed, 22 May 2024 09:18:18 -0500 Subject: [PATCH 2/4] ensure $ properly applied --- Helper/Requests.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Helper/Requests.php b/Helper/Requests.php index fc127b6ed7..39620da509 100644 --- a/Helper/Requests.php +++ b/Helper/Requests.php @@ -422,7 +422,7 @@ public function getShopperReference($customerId, $orderIncrementId, $quoteId): s if ($customerId) { $shopperReference = $this->adyenHelper->padShopperReference($customerId); } else { - $guestCustomerId = $quoteId ? "guest-cart" . quoteId : orderIncrementId . Uuid::generateV4(); + $guestCustomerId = $quoteId ? "guest-cart" . $quoteId : $orderIncrementId . Uuid::generateV4(); $shopperReference = $guestCustomerId; } From 676d7ca6c4f49529b3c599c3a93defb3db3b2fb8 Mon Sep 17 00:00:00 2001 From: Ryan Lambert Date: Wed, 29 May 2024 11:12:02 -0500 Subject: [PATCH 3/4] allow shopperReference to be passed via additionalData --- .../Request/AdditionalDataLevel23DataBuilder.php | 2 +- Helper/Requests.php | 12 ++++++++---- Model/Api/AdyenDonations.php | 15 +++++++-------- Observer/AdyenCcDataAssignObserver.php | 2 ++ .../AdditionalDataLevel23DataBuilderTest.php | 6 +++--- etc/schema.graphqls | 2 ++ 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Gateway/Request/AdditionalDataLevel23DataBuilder.php b/Gateway/Request/AdditionalDataLevel23DataBuilder.php index 5961a23569..2e88014b9a 100644 --- a/Gateway/Request/AdditionalDataLevel23DataBuilder.php +++ b/Gateway/Request/AdditionalDataLevel23DataBuilder.php @@ -72,7 +72,7 @@ public function build(array $buildSubject) $prefix = 'enhancedSchemeData'; $requestBody['additionalData'][$prefix . '.totalTaxAmount'] = $this->adyenHelper->formatAmount($order->getTaxAmount(), $currencyCode); - $requestBody['additionalData'][$prefix . '.customerReference'] = $this->adyenRequestHelper->getShopperReference($order->getCustomerId(), $order->getIncrementId(), $payment->getOrder()->getQuoteId()); + $requestBody['additionalData'][$prefix . '.customerReference'] = $this->adyenRequestHelper->getShopperReference($order->getCustomerId(), $order->getIncrementId(), $payment->getAdditionalInformation('shopperReference')); if ($order->getIsNotVirtual()) { $requestBody['additionalData'][$prefix . '.freightAmount'] = $this->adyenHelper->formatAmount($order->getBaseShippingAmount(), $currencyCode); $requestBody['additionalData'][$prefix . '.destinationPostalCode'] = $order->getShippingAddress()->getPostcode(); diff --git a/Helper/Requests.php b/Helper/Requests.php index 39620da509..8b8df25ed5 100644 --- a/Helper/Requests.php +++ b/Helper/Requests.php @@ -86,7 +86,7 @@ public function buildCustomerData( $additionalData = null, $request = [] ) { - $request['shopperReference'] = $this->getShopperReference($customerId, $payment->getOrder()->getIncrementId(), $payment->getOrder()->getQuoteId()); + $request['shopperReference'] = $this->getShopperReference($customerId, $payment->getOrder()->getIncrementId(), $payment->getAdditionalInformation('shopperReference')); // In case of virtual product and guest checkout there is a workaround to get the guest's email address if (!empty($additionalData['guestEmail'])) { @@ -417,12 +417,16 @@ public function buildDonationData($buildSubject, $storeId): array * @param string $orderIncrementId * @return string */ - public function getShopperReference($customerId, $orderIncrementId, $quoteId): string + public function getShopperReference($customerId, $orderIncrementId, $orderShopperReference): string { - if ($customerId) { + if ($orderShopperReference) { + $shopperReference = $orderShopperReference; + } + else if ($customerId) { $shopperReference = $this->adyenHelper->padShopperReference($customerId); } else { - $guestCustomerId = $quoteId ? "guest-cart" . $quoteId : $orderIncrementId . Uuid::generateV4(); + $uuid = Uuid::generateV4(); + $guestCustomerId = $orderIncrementId . $uuid; $shopperReference = $guestCustomerId; } diff --git a/Model/Api/AdyenDonations.php b/Model/Api/AdyenDonations.php index 46080f40ae..3439ae835a 100644 --- a/Model/Api/AdyenDonations.php +++ b/Model/Api/AdyenDonations.php @@ -20,6 +20,7 @@ use Adyen\Payment\Model\Sales\OrderRepository; use Adyen\Payment\Model\Ui\AdyenCcConfigProvider; use Adyen\Util\Uuid; +use Adyen\Payment\Helper\Requests; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; @@ -37,6 +38,7 @@ class AdyenDonations implements AdyenDonationsInterface private Config $config; private PaymentMethods $paymentMethodsHelper; private OrderRepository $orderRepository; + private Requests $adyenRequestsHelper; private $donationTryCount; @@ -47,7 +49,8 @@ public function __construct( ChargedCurrency $chargedCurrency, Config $config, PaymentMethods $paymentMethodsHelper, - OrderRepository $orderRepository + OrderRepository $orderRepository, + Requests $adyenRequestsHelper ) { $this->commandPool = $commandPool; $this->jsonSerializer = $jsonSerializer; @@ -56,6 +59,7 @@ public function __construct( $this->config = $config; $this->paymentMethodsHelper = $paymentMethodsHelper; $this->orderRepository = $orderRepository; + $this->adyenRequestsHelper = $adyenRequestsHelper; } /** @@ -111,14 +115,9 @@ function ($amount) use ($formatter, $currencyCode) { } else { throw new LocalizedException(__('Donation failed!')); } - + $payment = $order->getPayment(); + $request['shopperReference'] = $this->adyenRequestsHelper->getShopperReference($customerId, $payment->getOrder()->getIncrementId(), $payment->getAdditionalInformation('shopperReference')); $customerId = $order->getCustomerId(); - if ($customerId) { - $payload['shopperReference'] = $this->dataHelper->padShopperReference($customerId); - } else { - $guestCustomerId = $order->getIncrementId() . Uuid::generateV4(); - $payload['shopperReference'] = $guestCustomerId; - } try { $donationsCaptureCommand = $this->commandPool->get('capture'); diff --git a/Observer/AdyenCcDataAssignObserver.php b/Observer/AdyenCcDataAssignObserver.php index 6168deebb3..b407cac950 100644 --- a/Observer/AdyenCcDataAssignObserver.php +++ b/Observer/AdyenCcDataAssignObserver.php @@ -32,6 +32,7 @@ class AdyenCcDataAssignObserver extends AbstractDataAssignObserver const STORE_PAYMENT_METHOD = 'storePaymentMethod'; const RETURN_URL = 'returnUrl'; const RECURRING_PROCESSING_MODEL = 'recurringProcessingModel'; + const SHOPPER_REFERENCE = 'shopperReference'; /** * Approved root level keys from additional data array @@ -46,6 +47,7 @@ class AdyenCcDataAssignObserver extends AbstractDataAssignObserver self::CC_TYPE, self::RETURN_URL, self::RECURRING_PROCESSING_MODEL, + self::SHOPPER_REFERENCE, HeaderDataBuilder::FRONTENDTYPE ]; diff --git a/Test/Unit/Gateway/Request/AdditionalDataLevel23DataBuilderTest.php b/Test/Unit/Gateway/Request/AdditionalDataLevel23DataBuilderTest.php index ea1938f168..0c9c418d45 100644 --- a/Test/Unit/Gateway/Request/AdditionalDataLevel23DataBuilderTest.php +++ b/Test/Unit/Gateway/Request/AdditionalDataLevel23DataBuilderTest.php @@ -184,7 +184,7 @@ public function testVirtualOrder() public function testVirtualOrderGuest() { $storeId = 1; - $quoteId = 123; + $orderShopperReference = 123; $currencyCode = 'USD'; $customerId = null; $orderIncrementId = '000000123'; @@ -196,7 +196,7 @@ public function testVirtualOrderGuest() $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(null, $orderIncrementId, $quoteId)->willReturn($shopperReference); + $this->adyenRequestHelperMock->method('getShopperReference')->with(null, $orderIncrementId, $orderShopperReference)->willReturn($shopperReference); $orderMock = $this->createConfiguredMock(Order::class, [ 'getCustomerId' => $customerId, @@ -206,7 +206,7 @@ public function testVirtualOrderGuest() 'getIsNotVirtual' => false, 'getShippingAddress' => null, 'getBaseShippingAmount' => 0.00, - 'getQuoteId' => '123' + 'getAdditionalInformation' => '123' ]); $orderAdapterMock = $this->createMock(OrderAdapterInterface::class); diff --git a/etc/schema.graphqls b/etc/schema.graphqls index 8f1d58c8e6..bcdee01f81 100644 --- a/etc/schema.graphqls +++ b/etc/schema.graphqls @@ -166,6 +166,7 @@ input AdyenAdditionalDataCc { stateData: String @doc(description: "JSON string of filled fields.") returnUrl: String @doc(description: "The URL to return to in case of a redirection. The format depends on the channel. This URL can have a maximum of 1024 characters. It can include a placeholder `:merchantReference` to identify the order e.g. `https://your-company.com/checkout?shopperOrder=:merchantReference`.") recurringProcessingModel: String @doc(description: "Recurring processing model to tokenize the payment method.") + shopperReference: String @doc(description: "Recurring processing model shopperReference value; defaults to customerID.") } input AdyenAdditionalData { @@ -175,6 +176,7 @@ input AdyenAdditionalData { guestEmail: String @doc(description: "Email address if customer is guest.") returnUrl: String @doc(description: "The URL to return to in case of a redirection. The format depends on the channel. This URL can have a maximum of 1024 characters. It can include a placeholder `:merchantReference` to identify the order e.g. `https://your-company.com/checkout?shopperOrder=:merchantReference`.") recurringProcessingModel: String @doc(description: "Recurring processing model to tokenize the payment method.") + shopperReference: String @doc(description: "Recurring processing model shopperReference value; defaults to customerID.") } input AdyenAdditionalDataPosCloud { From 1c1729f00da4d5063b7b663007ff21c7f22817f6 Mon Sep 17 00:00:00 2001 From: Ryan Lambert Date: Wed, 29 May 2024 11:26:01 -0500 Subject: [PATCH 4/4] refactor tests for shopperReference-to-cartId --- .../Gateway/Request/AdditionalDataLevel23DataBuilderTest.php | 2 +- Test/Unit/Model/Api/AdyenDonationsTest.php | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Test/Unit/Gateway/Request/AdditionalDataLevel23DataBuilderTest.php b/Test/Unit/Gateway/Request/AdditionalDataLevel23DataBuilderTest.php index 0c9c418d45..c532e1b3a3 100644 --- a/Test/Unit/Gateway/Request/AdditionalDataLevel23DataBuilderTest.php +++ b/Test/Unit/Gateway/Request/AdditionalDataLevel23DataBuilderTest.php @@ -206,12 +206,12 @@ public function testVirtualOrderGuest() 'getIsNotVirtual' => false, 'getShippingAddress' => null, 'getBaseShippingAmount' => 0.00, - 'getAdditionalInformation' => '123' ]); $orderAdapterMock = $this->createMock(OrderAdapterInterface::class); $paymentMock = $this->createMock(Payment::class); $paymentMock->method('getOrder')->willReturn($orderMock); + $paymentMock->method('getAdditionalInformation')->willReturn($orderShopperReference); $paymentDataObject = new PaymentDataObject($orderAdapterMock, $paymentMock); $buildSubject = ['payment' => $paymentDataObject, 'order' => $orderMock]; $result = $this->additionalDataBuilder->build($buildSubject); diff --git a/Test/Unit/Model/Api/AdyenDonationsTest.php b/Test/Unit/Model/Api/AdyenDonationsTest.php index a019fe3a40..4d33eaf261 100644 --- a/Test/Unit/Model/Api/AdyenDonationsTest.php +++ b/Test/Unit/Model/Api/AdyenDonationsTest.php @@ -15,6 +15,7 @@ use Adyen\Payment\Helper\Config; use Adyen\Payment\Helper\Data; use Adyen\Payment\Helper\PaymentMethods; +use Adyen\Payment\Helper\Requests; use Adyen\Payment\Model\Api\AdyenDonations; use Adyen\Payment\Model\Sales\OrderRepository; use Adyen\Payment\Test\Unit\AbstractAdyenTestCase; @@ -48,7 +49,8 @@ public function testDonate() $this->createMock(ChargedCurrency::class), $this->createMock(Config::class), $this->createMock(PaymentMethods::class), - $orderRepositoryMock + $orderRepositoryMock, + $this->createMock(Requests::class) ]) ->getMock();