From 17bf7e47092719461280e14618a953ee1234630a Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Wed, 18 Oct 2023 15:57:19 +0200 Subject: [PATCH 1/2] Check 422 fraud exception and bail --- .../ApplePayButton/ApplePayDataObjectHttp.php | 2 +- src/Payment/PaymentService.php | 25 +++++++++++++++++++ src/PaymentMethods/Kbc.php | 14 +++++------ src/SDK/Api.php | 11 ++++++++ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/Buttons/ApplePayButton/ApplePayDataObjectHttp.php b/src/Buttons/ApplePayButton/ApplePayDataObjectHttp.php index 38f91294..db7f6919 100644 --- a/src/Buttons/ApplePayButton/ApplePayDataObjectHttp.php +++ b/src/Buttons/ApplePayButton/ApplePayDataObjectHttp.php @@ -326,7 +326,7 @@ protected function addressHasRequiredFieldsValues( sprintf('ApplePay Data Error: Missing value for %s', $requiredField) ); $this->errors[] - = [ + = [ 'errorCode' => $errorCode, 'contactField' => $errorValue, ]; diff --git a/src/Payment/PaymentService.php b/src/Payment/PaymentService.php index ea7b53f3..1e56d636 100644 --- a/src/Payment/PaymentService.php +++ b/src/Payment/PaymentService.php @@ -387,6 +387,8 @@ protected function processAsMollieOrder( } } catch (ApiException $e) { $this->handleMollieOutage($e); + //if exception is 422 do not try to create a payment + $this->handleMollieFraudRejection($e); // Don't try to create a Mollie Payment for Klarna payment methods $order_payment_method = $order->get_payment_method(); $orderMandatoryPaymentMethods = [ @@ -873,4 +875,27 @@ public function handleMollieOutage(ApiException $e): void ); } } + + /** + * Check if the exception is a fraud rejection, if so bail, log and inform user + * @param ApiException $e + * @return void + * @throws ApiException + */ + public function handleMollieFraudRejection(ApiException $e): void + { + $isMollieFraudException = $this->apiHelper->isMollieFraudException($e); + if ($isMollieFraudException) { + $this->logger->debug( + "Creating payment object: The payment was declined due to suspected fraud, stopping process." + ); + + throw new ApiException( + __( + 'Payment failed due to: The payment was declined due to suspected fraud.', + 'mollie-payments-for-woocommerce' + ) + ); + } + } } diff --git a/src/PaymentMethods/Kbc.php b/src/PaymentMethods/Kbc.php index 361116ed..5c220cac 100644 --- a/src/PaymentMethods/Kbc.php +++ b/src/PaymentMethods/Kbc.php @@ -6,10 +6,10 @@ class Kbc extends AbstractPaymentMethod implements PaymentMethodI { -protected const DEFAULT_ISSUERS_DROPDOWN = 'yes'; -protected function getConfig(): array -{ - return [ + protected const DEFAULT_ISSUERS_DROPDOWN = 'yes'; + protected function getConfig(): array + { + return [ 'id' => 'kbc', 'defaultTitle' => __('KBC/CBC Payment Button', 'mollie-payments-for-woocommerce'), 'settingsDescription' => '', @@ -23,10 +23,10 @@ protected function getConfig(): array 'filtersOnBuild' => false, 'confirmationDelayed' => true, 'SEPA' => true, - ]; -} + ]; + } -public function getFormFields($generalFormFields): array + public function getFormFields($generalFormFields): array { $paymentMethodFormFieds = [ 'issuers_dropdown_shown' => [ diff --git a/src/SDK/Api.php b/src/SDK/Api.php index 7b7cd5fb..d3526fb7 100644 --- a/src/SDK/Api.php +++ b/src/SDK/Api.php @@ -82,4 +82,15 @@ public function isMollieOutageException(\Mollie\Api\Exceptions\ApiException $e): } return false; } + + public function isMollieFraudException(\Mollie\Api\Exceptions\ApiException $e): bool + { + $isFraudCode = $e->getCode() === 422; + $isFraudMessage = strpos($e->getMessage(), 'The payment was declined due to suspected fraud') !== false; + + if ($isFraudCode && $isFraudMessage) { + return true; + } + return false; + } } From ddd3d2cbcebb55268eb17444391a648fd71576e6 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Fri, 20 Oct 2023 17:45:20 +0200 Subject: [PATCH 2/2] Add unit test to check exception --- tests/php/Functional/HelperMocks.php | 7 ++ .../Functional/Payment/PaymentServiceTest.php | 69 +++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/tests/php/Functional/HelperMocks.php b/tests/php/Functional/HelperMocks.php index 9ea1a44a..61a80371 100644 --- a/tests/php/Functional/HelperMocks.php +++ b/tests/php/Functional/HelperMocks.php @@ -7,6 +7,7 @@ use Mollie\Api\MollieApiClient; use Mollie\WooCommerce\Gateway\MolliePaymentGateway; use Mollie\WooCommerce\Notice\AdminNotice; +use Mollie\WooCommerce\Payment\MollieOrder; use Mollie\WooCommerce\Payment\MollieOrderService; use Mollie\WooCommerce\Payment\OrderInstructionsService; use Mollie\WooCommerce\Payment\OrderLines; @@ -59,6 +60,12 @@ public function paymentFactory($apiClientMock){ ); } + public function mollieOrderMock() + { + return $this->getMockBuilder(MollieOrder::class) + ->disableOriginalConstructor() + ->getMock(); + } public function noticeMock() { return $this->getMockBuilder(AdminNotice::class) diff --git a/tests/php/Functional/Payment/PaymentServiceTest.php b/tests/php/Functional/Payment/PaymentServiceTest.php index b8c536b3..3ef13e93 100644 --- a/tests/php/Functional/Payment/PaymentServiceTest.php +++ b/tests/php/Functional/Payment/PaymentServiceTest.php @@ -3,6 +3,7 @@ namespace Mollie\WooCommerceTests\Functional\Payment; use Mollie\Api\Endpoints\OrderEndpoint; +use Mollie\Api\Exceptions\ApiException; use Mollie\Api\MollieApiClient; use Mollie\WooCommerce\Gateway\MolliePaymentGateway; use Mollie\WooCommerce\Payment\PaymentCheckoutRedirectService; @@ -129,6 +130,64 @@ public function processPayment_Order_success(){ self::assertEquals($expectedResult, $arrayResult); } + /** + * @test + * @throws ApiException + */ + public function processAsMollieOrder_BailsIf_FraudException() + { + stubs([ + 'array_filter' => [], + ]); + $mockedException = new TestApiException(); + $mockedException->setTestCode(422); + $mockedException->setTestMessage('The payment was declined due to suspected fraud'); + $orderEndpointsMock = $this->createConfiguredMock( + OrderEndpoint::class, + [ + 'create' => new MollieOrderResponse(), + ] + ); + $paymentMethodId = 'Ideal'; + $orderEndpointsMock->method('create')->with([]) + ->will($this->throwException($mockedException)); + + $apiClientMock = $this->createMock(MollieApiClient::class); + $apiClientMock->orders = $orderEndpointsMock; + $voucherDefaultCategory = Voucher::NO_CATEGORY; + + $testee = new PaymentService( + $this->helperMocks->noticeMock(), + $this->helperMocks->loggerMock(), + $this->helperMocks->paymentFactory($apiClientMock), + $this->helperMocks->dataHelper($apiClientMock), + $this->helperMocks->apiHelper($apiClientMock), + $this->helperMocks->settingsHelper(), + $this->helperMocks->pluginId(), + $this->paymentCheckoutService($apiClientMock), + $voucherDefaultCategory + ); + $gateway = $this->mollieGateway($paymentMethodId, $testee); + $testee->setGateway($gateway); + $wcOrderId = 1; + $wcOrderKey = 'wc_order_hxZniP1zDcnM8'; + $wcOrder = $this->wcOrder($wcOrderId, $wcOrderKey); + $cusomerId = 1; + $apiKey = 'test_test'; + $method = new \ReflectionMethod(PaymentService::class, 'processAsMollieOrder'); + $method->setAccessible(true); + + $this->expectException(ApiException::class); + + $method->invoke( + $testee, + $this->helperMocks->mollieOrderMock(), + $wcOrder, + $cusomerId, + $apiKey + ); + } + protected function setUp(): void { @@ -451,5 +510,15 @@ public function getCheckoutUrl() } +class TestApiException extends \Mollie\Api\Exceptions\ApiException { + public function setTestCode($code) { + $this->code = $code; + } + + public function setTestMessage($message) { + $this->message = $message; + } +} +