diff --git a/Gateway/Request/AdditionalDataLevel23DataBuilder.php b/Gateway/Request/AdditionalDataLevel23DataBuilder.php index c07d049e1..2e88014b9 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->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 9d7aa7c21..8b8df25ed 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->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,9 +417,12 @@ public function buildDonationData($buildSubject, $storeId): array * @param string $orderIncrementId * @return string */ - public function getShopperReference($customerId, $orderIncrementId): string + public function getShopperReference($customerId, $orderIncrementId, $orderShopperReference): string { - if ($customerId) { + if ($orderShopperReference) { + $shopperReference = $orderShopperReference; + } + else if ($customerId) { $shopperReference = $this->adyenHelper->padShopperReference($customerId); } else { $uuid = Uuid::generateV4(); diff --git a/Model/Api/AdyenDonations.php b/Model/Api/AdyenDonations.php index 46080f40a..3439ae835 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 6168deebb..b407cac95 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/Plugin/PaymentVaultDeleteToken.php b/Plugin/PaymentVaultDeleteToken.php index 2daeac50d..6657f815d 100644 --- a/Plugin/PaymentVaultDeleteToken.php +++ b/Plugin/PaymentVaultDeleteToken.php @@ -124,7 +124,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 318ef93cc..c532e1b3a 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; + $orderShopperReference = 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, $orderShopperReference)->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); + $paymentMock->method('getAdditionalInformation')->willReturn($orderShopperReference); + $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; diff --git a/Test/Unit/Model/Api/AdyenDonationsTest.php b/Test/Unit/Model/Api/AdyenDonationsTest.php index a019fe3a4..4d33eaf26 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(); diff --git a/etc/schema.graphqls b/etc/schema.graphqls index 0a4d824e4..3d8157742 100644 --- a/etc/schema.graphqls +++ b/etc/schema.graphqls @@ -167,6 +167,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 { @@ -176,6 +177,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 {