diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/PlaceOrder.php b/app/code/Magento/QuoteGraphQl/Model/Cart/PlaceOrder.php index b1a7ed3bdc5b6..0aed9fe1dabde 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/PlaceOrder.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/PlaceOrder.php @@ -1,7 +1,7 @@ paymentManagement = $paymentManagement; $this->cartManagement = $cartManagement; + $this->logger = $logger ?: ObjectManager::getInstance() + ->get(LoggerInterface::class); } /** @@ -58,6 +69,36 @@ public function execute(Quote $cart, string $maskedCartId, int $userId): int $cartId = (int)$cart->getId(); $paymentMethod = $this->paymentManagement->get($cartId); + // Get a list of available payment methods for the cart + $availablePaymentMethods = $this->paymentManagement->getList($cartId); + $payment = $cart->getPayment(); + $paymentMethodCode = $payment?->getMethod(); + // Check if the selected payment method is in the available methods list + if ($paymentMethodCode && $availablePaymentMethods) { + $availableCodes = array_map(fn($method) => $method->getCode(), $availablePaymentMethods); + $isPaymentMethodAvailable = in_array($paymentMethodCode, $availableCodes); + } else { + $isPaymentMethodAvailable = false; + } + + if (!$isPaymentMethodAvailable) { + // Log the attempt to use a disabled payment method + $this->logger->debug( + 'Attempt to place order with disabled payment method', + [ + 'payment_method' => $paymentMethodCode, + 'cart_id' => $cartId, + 'user_id' => $userId, + 'available_methods' => $availablePaymentMethods ? + array_map(fn($method) => $method->getCode(), $availablePaymentMethods) : [] + ] + ); + + throw new LocalizedException( + __('The requested Payment Method \'%1\' is not available.', $paymentMethodCode ?: 'unknown') + ); + } + return (int)$this->cartManagement->placeOrder($cartId, $paymentMethod); } } diff --git a/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Cart/PlaceOrderTest.php b/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Cart/PlaceOrderTest.php new file mode 100644 index 0000000000000..e7e9d31f6be97 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Cart/PlaceOrderTest.php @@ -0,0 +1,469 @@ +paymentManagementMock = $this->createMock(PaymentMethodManagementInterface::class); + $this->cartManagementMock = $this->createMock(CartManagementInterface::class); + $this->quoteMock = $this->createMock(Quote::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->paymentInterfaceMock = $this->createMock(PaymentInterface::class); + $this->loggerMock = $this->createMock(LoggerInterface::class); + + $this->placeOrder = new PlaceOrder( + $this->paymentManagementMock, + $this->cartManagementMock, + $this->loggerMock + ); + } + + /** + * Test successful order placement with available payment method + */ + public function testExecuteWithAvailablePaymentMethod(): void + { + $cartId = 123; + $maskedCartId = 'masked123'; + $userId = 456; + $paymentMethodCode = 'checkmo'; + $orderId = 789; + + $this->quoteMock->method('getId')->willReturn($cartId); + $this->quoteMock->method('getPayment')->willReturn($this->paymentMock); + + $this->paymentMock->method('getMethod')->willReturn($paymentMethodCode); + + $this->paymentManagementMock->method('get') + ->with($cartId) + ->willReturn($this->paymentInterfaceMock); + + $availableMethod1 = $this->createMock(PaymentMethodInterface::class); + $availableMethod1->method('getCode')->willReturn('paypal'); + + $availableMethod2 = $this->createMock(PaymentMethodInterface::class); + $availableMethod2->method('getCode')->willReturn($paymentMethodCode); + + $availableMethod3 = $this->createMock(PaymentMethodInterface::class); + $availableMethod3->method('getCode')->willReturn('stripe'); + + $availablePaymentMethods = [$availableMethod1, $availableMethod2, $availableMethod3]; + + $this->paymentManagementMock->method('getList') + ->with($cartId) + ->willReturn($availablePaymentMethods); + + $this->cartManagementMock->method('placeOrder') + ->with($cartId, $this->paymentInterfaceMock) + ->willReturn($orderId); + + $result = $this->placeOrder->execute($this->quoteMock, $maskedCartId, $userId); + + $this->assertEquals($orderId, $result); + } + + /** + * Test exception when payment method is not available + */ + public function testExecuteWithUnavailablePaymentMethod(): void + { + $cartId = 123; + $maskedCartId = 'masked123'; + $userId = 456; + $paymentMethodCode = 'unavailable_method'; + + $this->quoteMock->method('getId')->willReturn($cartId); + $this->quoteMock->method('getPayment')->willReturn($this->paymentMock); + + $this->paymentMock->method('getMethod')->willReturn($paymentMethodCode); + + $this->paymentManagementMock->method('get') + ->with($cartId) + ->willReturn($this->paymentInterfaceMock); + + $availableMethod1 = $this->createMock(PaymentMethodInterface::class); + $availableMethod1->method('getCode')->willReturn('paypal'); + + $availableMethod2 = $this->createMock(PaymentMethodInterface::class); + $availableMethod2->method('getCode')->willReturn('stripe'); + + $availablePaymentMethods = [$availableMethod1, $availableMethod2]; + + $this->paymentManagementMock->method('getList') + ->with($cartId) + ->willReturn($availablePaymentMethods); + + $this->loggerMock->expects($this->once()) + ->method('debug') + ->with( + 'Attempt to place order with disabled payment method', + [ + 'payment_method' => $paymentMethodCode, + 'cart_id' => $cartId, + 'user_id' => $userId, + 'available_methods' => ['paypal', 'stripe'] + ] + ); + + $this->expectException(LocalizedException::class); + $this->expectExceptionMessage("The requested Payment Method 'unavailable_method' is not available."); + + $this->placeOrder->execute($this->quoteMock, $maskedCartId, $userId); + } + + /** + * Test exception when no payment method is set on quote + */ + public function testExecuteWithNoPaymentMethodSet(): void + { + $cartId = 123; + $maskedCartId = 'masked123'; + $userId = 456; + + $this->quoteMock->method('getId')->willReturn($cartId); + $this->quoteMock->method('getPayment')->willReturn($this->paymentMock); + + $this->paymentMock->method('getMethod')->willReturn(null); + + $this->paymentManagementMock->method('get') + ->with($cartId) + ->willReturn($this->paymentInterfaceMock); + + $availableMethod = $this->createMock(PaymentMethodInterface::class); + $availableMethod->method('getCode')->willReturn('checkmo'); + $availablePaymentMethods = [$availableMethod]; + + $this->paymentManagementMock->method('getList') + ->with($cartId) + ->willReturn($availablePaymentMethods); + + $this->loggerMock->expects($this->once()) + ->method('debug') + ->with( + 'Attempt to place order with disabled payment method', + [ + 'payment_method' => null, + 'cart_id' => $cartId, + 'user_id' => $userId, + 'available_methods' => ['checkmo'] + ] + ); + + $this->expectException(LocalizedException::class); + $this->expectExceptionMessage("The requested Payment Method 'unknown' is not available."); + + $this->placeOrder->execute($this->quoteMock, $maskedCartId, $userId); + } + + /** + * Test exception when no payment methods are available + */ + public function testExecuteWithNoAvailablePaymentMethods(): void + { + $cartId = 123; + $maskedCartId = 'masked123'; + $userId = 456; + $paymentMethodCode = 'checkmo'; + + $this->quoteMock->method('getId')->willReturn($cartId); + $this->quoteMock->method('getPayment')->willReturn($this->paymentMock); + + $this->paymentMock->method('getMethod')->willReturn($paymentMethodCode); + + $this->paymentManagementMock->method('get') + ->with($cartId) + ->willReturn($this->paymentInterfaceMock); + + $this->paymentManagementMock->method('getList') + ->with($cartId) + ->willReturn([]); + + $this->loggerMock->expects($this->once()) + ->method('debug') + ->with( + 'Attempt to place order with disabled payment method', + [ + 'payment_method' => $paymentMethodCode, + 'cart_id' => $cartId, + 'user_id' => $userId, + 'available_methods' => [] + ] + ); + + $this->expectException(LocalizedException::class); + $this->expectExceptionMessage("The requested Payment Method 'checkmo' is not available."); + + $this->placeOrder->execute($this->quoteMock, $maskedCartId, $userId); + } + + /** + * Test exception when available payment methods is null + */ + public function testExecuteWithNullAvailablePaymentMethods(): void + { + $cartId = 123; + $maskedCartId = 'masked123'; + $userId = 456; + $paymentMethodCode = 'checkmo'; + + $this->quoteMock->method('getId')->willReturn($cartId); + $this->quoteMock->method('getPayment')->willReturn($this->paymentMock); + + $this->paymentMock->method('getMethod')->willReturn($paymentMethodCode); + + $this->paymentManagementMock->method('get') + ->with($cartId) + ->willReturn($this->paymentInterfaceMock); + + $this->paymentManagementMock->method('getList') + ->with($cartId) + ->willReturn(null); + + $this->loggerMock->expects($this->once()) + ->method('debug') + ->with( + 'Attempt to place order with disabled payment method', + [ + 'payment_method' => $paymentMethodCode, + 'cart_id' => $cartId, + 'user_id' => $userId, + 'available_methods' => [] + ] + ); + + $this->expectException(LocalizedException::class); + $this->expectExceptionMessage("The requested Payment Method 'checkmo' is not available."); + + $this->placeOrder->execute($this->quoteMock, $maskedCartId, $userId); + } + + /** + * Test exception when quote has no payment object + */ + public function testExecuteWithNoPaymentObject(): void + { + $cartId = 123; + $maskedCartId = 'masked123'; + $userId = 456; + + $this->quoteMock->method('getId')->willReturn($cartId); + $this->quoteMock->method('getPayment')->willReturn(null); + + $this->paymentManagementMock->method('get') + ->with($cartId) + ->willReturn($this->paymentInterfaceMock); + + $availableMethod = $this->createMock(PaymentMethodInterface::class); + $availableMethod->method('getCode')->willReturn('checkmo'); + $availablePaymentMethods = [$availableMethod]; + + $this->paymentManagementMock->method('getList') + ->with($cartId) + ->willReturn($availablePaymentMethods); + + $this->loggerMock->expects($this->once()) + ->method('debug') + ->with( + 'Attempt to place order with disabled payment method', + [ + 'payment_method' => null, + 'cart_id' => $cartId, + 'user_id' => $userId, + 'available_methods' => ['checkmo'] + ] + ); + + $this->expectException(LocalizedException::class); + $this->expectExceptionMessage("The requested Payment Method 'unknown' is not available."); + + $this->placeOrder->execute($this->quoteMock, $maskedCartId, $userId); + } + + /** + * Test that cart management exceptions are propagated + */ + public function testExecuteWithCartManagementException(): void + { + $cartId = 123; + $maskedCartId = 'masked123'; + $userId = 456; + $paymentMethodCode = 'checkmo'; + + $this->quoteMock->method('getId')->willReturn($cartId); + $this->quoteMock->method('getPayment')->willReturn($this->paymentMock); + + $this->paymentMock->method('getMethod')->willReturn($paymentMethodCode); + + $this->paymentManagementMock->method('get') + ->with($cartId) + ->willReturn($this->paymentInterfaceMock); + + $availableMethod = $this->createMock(PaymentMethodInterface::class); + $availableMethod->method('getCode')->willReturn($paymentMethodCode); + $availablePaymentMethods = [$availableMethod]; + + $this->paymentManagementMock->method('getList') + ->with($cartId) + ->willReturn($availablePaymentMethods); + + $expectedException = new NoSuchEntityException(__('Cart does not exist')); + $this->cartManagementMock->method('placeOrder') + ->with($cartId, $this->paymentInterfaceMock) + ->willThrowException($expectedException); + + $this->expectException(NoSuchEntityException::class); + $this->expectExceptionMessage('Cart does not exist'); + + $this->placeOrder->execute($this->quoteMock, $maskedCartId, $userId); + } + + /** + * Test with empty string payment method code + */ + public function testExecuteWithEmptyPaymentMethodCode(): void + { + $cartId = 123; + $maskedCartId = 'masked123'; + $userId = 456; + + $this->quoteMock->method('getId')->willReturn($cartId); + $this->quoteMock->method('getPayment')->willReturn($this->paymentMock); + + $this->paymentMock->method('getMethod')->willReturn(''); + + $this->paymentManagementMock->method('get') + ->with($cartId) + ->willReturn($this->paymentInterfaceMock); + + $availableMethod = $this->createMock(PaymentMethodInterface::class); + $availableMethod->method('getCode')->willReturn('checkmo'); + $availablePaymentMethods = [$availableMethod]; + + $this->paymentManagementMock->method('getList') + ->with($cartId) + ->willReturn($availablePaymentMethods); + + $this->loggerMock->expects($this->once()) + ->method('debug') + ->with( + 'Attempt to place order with disabled payment method', + [ + 'payment_method' => '', + 'cart_id' => $cartId, + 'user_id' => $userId, + 'available_methods' => ['checkmo'] + ] + ); + + $this->expectException(LocalizedException::class); + $this->expectExceptionMessage("The requested Payment Method 'unknown' is not available."); + + $this->placeOrder->execute($this->quoteMock, $maskedCartId, $userId); + } + + /** + * Test case sensitivity in payment method code comparison + */ + public function testExecuteWithCaseSensitivePaymentMethodCode(): void + { + $cartId = 123; + $maskedCartId = 'masked123'; + $userId = 456; + $paymentMethodCode = 'CheckMo'; // Different case + + $this->quoteMock->method('getId')->willReturn($cartId); + $this->quoteMock->method('getPayment')->willReturn($this->paymentMock); + + $this->paymentMock->method('getMethod')->willReturn($paymentMethodCode); + + $this->paymentManagementMock->method('get') + ->with($cartId) + ->willReturn($this->paymentInterfaceMock); + + $availableMethod = $this->createMock(PaymentMethodInterface::class); + $availableMethod->method('getCode')->willReturn('checkmo'); // lowercase + $availablePaymentMethods = [$availableMethod]; + + $this->paymentManagementMock->method('getList') + ->with($cartId) + ->willReturn($availablePaymentMethods); + + $this->loggerMock->expects($this->once()) + ->method('debug') + ->with( + 'Attempt to place order with disabled payment method', + [ + 'payment_method' => $paymentMethodCode, + 'cart_id' => $cartId, + 'user_id' => $userId, + 'available_methods' => ['checkmo'] + ] + ); + + $this->expectException(LocalizedException::class); + $this->expectExceptionMessage("The requested Payment Method 'CheckMo' is not available."); + + $this->placeOrder->execute($this->quoteMock, $maskedCartId, $userId); + } +}