From 4cd4be996243a3bc5728b1bbbdc3bccb17d37e35 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 12 Oct 2023 16:31:22 +0200 Subject: [PATCH 01/66] Updated InitalState 'processing' method --- src/Internal/Payment/State/InitialState.php | 42 ++++--------------- .../Service/PaymentProcessingService.php | 2 +- 2 files changed, 8 insertions(+), 36 deletions(-) diff --git a/src/Internal/Payment/State/InitialState.php b/src/Internal/Payment/State/InitialState.php index 061c89ce41f..c8a1010a72e 100644 --- a/src/Internal/Payment/State/InitialState.php +++ b/src/Internal/Payment/State/InitialState.php @@ -13,11 +13,7 @@ use WCPay\Internal\Service\OrderService; use WCPay\Internal\Service\Level3Service; use WCPay\Internal\Service\PaymentRequestService; -use WCPay\Core\Exceptions\Server\Request\Extend_Request_Exception; -use WCPay\Core\Exceptions\Server\Request\Immutable_Parameter_Exception; -use WCPay\Core\Exceptions\Server\Request\Invalid_Request_Parameter_Exception; use WCPay\Exceptions\Order_Not_Found_Exception; -use WCPay\Internal\Payment\PaymentContext; use WCPay\Internal\Payment\PaymentRequest; use WCPay\Internal\Payment\PaymentRequestException; @@ -46,13 +42,6 @@ class InitialState extends AbstractPaymentState { */ private $level3_service; - /** - * Payment request service. - * - * @var PaymentRequestService - */ - private $payment_request_service; - /** * Class constructor, only meant for storing dependencies. * @@ -60,55 +49,38 @@ class InitialState extends AbstractPaymentState { * @param OrderService $order_service Service for order-related actions. * @param WC_Payments_Customer_Service $customer_service Service for managing remote customers. * @param Level3Service $level3_service Service for Level3 Data. - * @param PaymentRequestService $payment_request_service Connection with the server. */ public function __construct( StateFactory $state_factory, OrderService $order_service, WC_Payments_Customer_Service $customer_service, - Level3Service $level3_service, - PaymentRequestService $payment_request_service + Level3Service $level3_service ) { parent::__construct( $state_factory ); - $this->order_service = $order_service; - $this->customer_service = $customer_service; - $this->level3_service = $level3_service; - $this->payment_request_service = $payment_request_service; + $this->order_service = $order_service; + $this->customer_service = $customer_service; + $this->level3_service = $level3_service; } /** * Initialtes the payment process. * * @param PaymentRequest $request The incoming payment processing request. - * @return CompletedState The next state. + * @return AbstractPaymentState The next state. * @throws StateTransitionException In case the completed state could not be initialized. * @throws ContainerException When the dependency container cannot instantiate the state. * @throws Order_Not_Found_Exception Order could not be found. * @throws PaymentRequestException When data is not available or invalid. */ - public function process( PaymentRequest $request ) { - $context = $this->get_context(); - $order_id = $context->get_order_id(); - + public function start_processing( PaymentRequest $request ) { // Populate basic details from the request. $this->populate_context_from_request( $request ); // Populate further details from the order. $this->populate_context_from_order(); - // Payments are currently based on intents, request one from the API. - try { - $intent = $this->payment_request_service->create_intent( $context ); - } catch ( Invalid_Request_Parameter_Exception | Extend_Request_Exception | Immutable_Parameter_Exception $e ) { - return $this->create_state( SystemErrorState::class ); - } - - // Intent available, complete processing. - $this->order_service->update_order_from_successful_intent( $order_id, $intent, $context ); - - // If everything went well, transition to the completed state. - return $this->create_state( CompletedState::class ); + return $this->create_state( VerifyState::class ); } /** diff --git a/src/Internal/Service/PaymentProcessingService.php b/src/Internal/Service/PaymentProcessingService.php index e32a823ef12..20f4faa7bf2 100644 --- a/src/Internal/Service/PaymentProcessingService.php +++ b/src/Internal/Service/PaymentProcessingService.php @@ -66,7 +66,7 @@ public function process_payment( int $order_id, bool $manual_capture = false ) { $request = new PaymentRequest( $this->legacy_proxy ); $initial_state = $this->state_factory->create_state( InitialState::class, $context ); - $completed_state = $initial_state->process( $request ); + $completed_state = $initial_state->start_processing( $request ); return $completed_state; } From c25614581486a1ba5ce8ae06b4ec1607e292cf68 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 12 Oct 2023 16:31:42 +0200 Subject: [PATCH 02/66] Added get and set intent to context object --- src/Internal/Payment/PaymentContext.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Internal/Payment/PaymentContext.php b/src/Internal/Payment/PaymentContext.php index 9979e614510..b6f7cd8d02a 100644 --- a/src/Internal/Payment/PaymentContext.php +++ b/src/Internal/Payment/PaymentContext.php @@ -7,6 +7,7 @@ namespace WCPay\Internal\Payment; +use WC_Payments_API_Payment_Intention; use WCPay\Internal\Payment\PaymentMethod\PaymentMethodInterface; /** @@ -245,4 +246,22 @@ public function set_customer_id( string $customer_id ) { public function get_customer_id(): ?string { return $this->get( 'customer_id' ); } + + /** + * Stores the payment intent. + * + * @param WC_Payments_API_Payment_Intention $intent Instance of intent. + */ + public function set_intent( WC_Payments_API_Payment_Intention $intent ) { + $this->set( 'intent', $intent ); + } + + /** + * Returns the payment intent object. + * + * @return WC_Payments_API_Payment_Intention|null + */ + public function get_intent(): ?WC_Payments_API_Payment_Intention { + return $this->get( 'intent' ); + } } From 69dc092526b6b04175a8eae4a5ccc37163989744 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 12 Oct 2023 16:32:15 +0200 Subject: [PATCH 03/66] Updated function name to match with InitialState class. --- src/Internal/Payment/State/AbstractPaymentState.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Internal/Payment/State/AbstractPaymentState.php b/src/Internal/Payment/State/AbstractPaymentState.php index 021b1e05cb3..253d56ffe58 100644 --- a/src/Internal/Payment/State/AbstractPaymentState.php +++ b/src/Internal/Payment/State/AbstractPaymentState.php @@ -94,14 +94,14 @@ protected function create_state( string $state_class ) { * Initialtes the payment process. * * @param PaymentRequest $request The incoming payment processing request. - * @return CompletedState The next state. + * @return AbstractPaymentState The next state. * @throws StateTransitionException In case the completed state could not be initialized. * @throws ContainerException When the dependency container cannot instantiate the state. * @throws Order_Not_Found_Exception Order could not be found. * @throws PaymentRequestException When data is not available or invalid. * @psalm-suppress InvalidReturnType If this method does not throw, it will return a new state. */ - public function process( PaymentRequest $request ) { + public function start_processing( PaymentRequest $request ) { $this->throw_unavailable_method_exception( __METHOD__ ); } From fa1721e9d3fbd25e4c5b63fb88d5b21bf6d8e1f6 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 12 Oct 2023 16:32:41 +0200 Subject: [PATCH 04/66] Added Verify state that will be executed after Inital state --- src/Internal/Payment/State/VerifyState.php | 85 ++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/Internal/Payment/State/VerifyState.php diff --git a/src/Internal/Payment/State/VerifyState.php b/src/Internal/Payment/State/VerifyState.php new file mode 100644 index 00000000000..a6fb137c74c --- /dev/null +++ b/src/Internal/Payment/State/VerifyState.php @@ -0,0 +1,85 @@ +order_service = $order_service; + $this->payment_request_service = $payment_request_service; + } + + /** + * Process all needed verifications. + * + * @return AbstractPaymentState + * @throws \WCPay\Internal\Payment\Exception\StateTransitionException + */ + public function process() { + $context = $this->get_context(); + + // Payments are currently based on intents, request one from the API. + try { + $intent = $this->payment_request_service->create_intent( $context ); + + // Something went wrong since the intent is not an instance of payment intent class. Transition to error state. + if ( ! $intent instanceof WC_Payments_API_Payment_Intention ) { + return $this->create_state( SystemErrorState::class ); + } + $context->set_intent( $intent ); + } catch ( Invalid_Request_Parameter_Exception | Extend_Request_Exception | Immutable_Parameter_Exception $e ) { + return $this->create_state( SystemErrorState::class ); + } + + // Intent requires authorization (3DS check). + if ( Payment_Intent_Status::REQUIRES_ACTION === $intent->get_status() ) { + return $this->create_state( AuthenticationRequiredState::class ); + } + + // All good. Proceed to processed state. + return $this->create_state( ProcessedState::class ); + } +} From 96812499ac286f9767224082ef325de6e6ff015e Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 12 Oct 2023 16:40:46 +0200 Subject: [PATCH 05/66] Lint fixes. --- .../PaymentsServiceProvider.php | 22 +++++- .../State/AuthenticationRequiredState.php | 75 +++++++++++++++++++ src/Internal/Payment/State/ProcessedState.php | 60 +++++++++++++++ .../Service/CheckoutEncryptionService.php | 44 +++++++++++ 4 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 src/Internal/Payment/State/AuthenticationRequiredState.php create mode 100644 src/Internal/Payment/State/ProcessedState.php create mode 100644 src/Internal/Service/CheckoutEncryptionService.php diff --git a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php index 8641fe728dd..5436aa8e981 100644 --- a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php +++ b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php @@ -14,12 +14,16 @@ use WCPay\Database_Cache; use WCPay\Internal\DependencyManagement\AbstractServiceProvider; use WCPay\Internal\Payment\Router; +use WCPay\Internal\Payment\State\AuthenticationRequiredState; use WCPay\Internal\Payment\State\CompletedState; use WCPay\Internal\Payment\State\InitialState; use WCPay\Internal\Payment\State\PaymentErrorState; +use WCPay\Internal\Payment\State\ProcessedState; use WCPay\Internal\Payment\State\StateFactory; use WCPay\Internal\Payment\State\SystemErrorState; +use WCPay\Internal\Payment\State\VerifyState; use WCPay\Internal\Proxy\LegacyProxy; +use WCPay\Internal\Service\CheckoutEncryptionService; use WCPay\Internal\Service\PaymentProcessingService; use WCPay\Internal\Service\ExampleService; use WCPay\Internal\Service\ExampleServiceWithDependencies; @@ -41,12 +45,15 @@ class PaymentsServiceProvider extends AbstractServiceProvider { Router::class, StateFactory::class, InitialState::class, + VerifyState::class, + ProcessedState::class, CompletedState::class, SystemErrorState::class, PaymentErrorState::class, ExampleService::class, ExampleServiceWithDependencies::class, PaymentRequestService::class, + CheckoutEncryptionService::class, ]; /** @@ -63,14 +70,27 @@ public function register(): void { ->addArgument( LegacyProxy::class ); $container->addShared( PaymentRequestService::class ); + $container->addShared( CheckoutEncryptionService::class ); $container->add( InitialState::class ) ->addArgument( StateFactory::class ) ->addArgument( OrderService::class ) ->addArgument( WC_Payments_Customer_Service::class ) - ->addArgument( Level3Service::class ) + ->addArgument( Level3Service::class ); + + $container->add( VerifyState::class ) + ->addArgument( StateFactory::class ) + ->addArgument( OrderService::class ) ->addArgument( PaymentRequestService::class ); + $container->add( ProcessedState::class ) + ->addArgument( StateFactory::class ) + ->addArgument( OrderService::class ); + + $container->add( AuthenticationRequiredState::class ) + ->addArgument( StateFactory::class ) + ->addArgument( OrderService::class ); + $container->add( CompletedState::class ) ->addArgument( StateFactory::class ); diff --git a/src/Internal/Payment/State/AuthenticationRequiredState.php b/src/Internal/Payment/State/AuthenticationRequiredState.php new file mode 100644 index 00000000000..6f6bf95453f --- /dev/null +++ b/src/Internal/Payment/State/AuthenticationRequiredState.php @@ -0,0 +1,75 @@ +checkout_encryption_service = $checkout_encryption_service; + } + /** + * Get response that will be sent to the client. + */ + public function get_response(): array { + $context = $this->get_context(); + $intent = $context->get_intent(); + $next_action = $intent->get_next_action(); + if ( isset( $next_action['type'] ) && 'redirect_to_url' === $next_action['type'] && ! empty( $next_action['redirect_to_url']['url'] ) ) { + $response = [ + 'result' => 'success', + 'redirect' => $next_action['redirect_to_url']['url'], + ]; + } else { + if ( ! $context->get_payment_method() ) { + $payment_method_id = $context->get_payment_method()->get_id(); + } else { + $payment_method_id = $intent->get_payment_method_id(); + } + $response = [ + 'result' => 'success', + // Include a new nonce for update_order_status to ensure the update order + // status call works when a guest user creates an account during checkout. + 'redirect' => sprintf( + '#wcpay-confirm-%s:%s:%s:%s', + $context->get_amount() > 0 ? 'pi' : 'si', + $context->get_order_id(), + $this->checkout_encryption_service->encrypt_client_secret( $intent->get_customer_id(), $intent->get_client_secret() ), + wp_create_nonce( 'wcpay_update_order_status_nonce' ) + ), + // Include the payment method ID so the Blocks integration can save cards. + 'payment_method' => $payment_method_id, + ]; + } + + return $response; + } + +} diff --git a/src/Internal/Payment/State/ProcessedState.php b/src/Internal/Payment/State/ProcessedState.php new file mode 100644 index 00000000000..1c39453b44d --- /dev/null +++ b/src/Internal/Payment/State/ProcessedState.php @@ -0,0 +1,60 @@ +order_service = $order_service; + } + + /** + * Process all needed verifications. + * + * @return AbstractPaymentState + * @throws \WCPay\Exceptions\Order_Not_Found_Exception + * @throws \WCPay\Internal\Payment\Exception\StateTransitionException + */ + public function complete() { + $context = $this->get_context(); + // Complete processing. + $this->order_service->update_order_from_successful_intent( $context->get_order_id(), $context->get_intent(), $context ); + + // If everything went well, transition to the completed state. + return $this->create_state( CompletedState::class ); + } +} diff --git a/src/Internal/Service/CheckoutEncryptionService.php b/src/Internal/Service/CheckoutEncryptionService.php new file mode 100644 index 00000000000..3f087d77f8e --- /dev/null +++ b/src/Internal/Service/CheckoutEncryptionService.php @@ -0,0 +1,44 @@ + Date: Thu, 12 Oct 2023 17:40:22 +0200 Subject: [PATCH 06/66] Changed class on intent getter and setter --- src/Internal/Payment/PaymentContext.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Internal/Payment/PaymentContext.php b/src/Internal/Payment/PaymentContext.php index b6f7cd8d02a..edac1bc920b 100644 --- a/src/Internal/Payment/PaymentContext.php +++ b/src/Internal/Payment/PaymentContext.php @@ -7,7 +7,7 @@ namespace WCPay\Internal\Payment; -use WC_Payments_API_Payment_Intention; +use WC_Payments_API_Abstract_Intention; use WCPay\Internal\Payment\PaymentMethod\PaymentMethodInterface; /** @@ -250,18 +250,18 @@ public function get_customer_id(): ?string { /** * Stores the payment intent. * - * @param WC_Payments_API_Payment_Intention $intent Instance of intent. + * @param WC_Payments_API_Abstract_Intention $intent Instance of intent. */ - public function set_intent( WC_Payments_API_Payment_Intention $intent ) { + public function set_intent( WC_Payments_API_Abstract_Intention $intent ) { $this->set( 'intent', $intent ); } /** * Returns the payment intent object. * - * @return WC_Payments_API_Payment_Intention|null + * @return WC_Payments_API_Abstract_Intention|null */ - public function get_intent(): ?WC_Payments_API_Payment_Intention { + public function get_intent(): ?WC_Payments_API_Abstract_Intention { return $this->get( 'intent' ); } } From 9f99f29956dec8fe26c59d2e2f69b64531e23f1e Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 12 Oct 2023 17:42:51 +0200 Subject: [PATCH 07/66] Code review fix: Fixed issue with VerifyState and process method --- src/Internal/Payment/State/InitialState.php | 3 ++- src/Internal/Payment/State/VerifyState.php | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Internal/Payment/State/InitialState.php b/src/Internal/Payment/State/InitialState.php index c8a1010a72e..53e0b3862a7 100644 --- a/src/Internal/Payment/State/InitialState.php +++ b/src/Internal/Payment/State/InitialState.php @@ -80,7 +80,8 @@ public function start_processing( PaymentRequest $request ) { // Populate further details from the order. $this->populate_context_from_order(); - return $this->create_state( VerifyState::class ); + $next_state = $this->create_state( VerifyState::class ); + return $next_state->process(); } /** diff --git a/src/Internal/Payment/State/VerifyState.php b/src/Internal/Payment/State/VerifyState.php index a6fb137c74c..ceaf2579ad3 100644 --- a/src/Internal/Payment/State/VerifyState.php +++ b/src/Internal/Payment/State/VerifyState.php @@ -7,7 +7,7 @@ namespace WCPay\Internal\Payment\State; -use WC_Payments_API_Payment_Intention; +use WC_Payments_API_Abstract_Intention; use WCPay\Constants\Payment_Intent_Status; use WCPay\Core\Exceptions\Server\Request\Extend_Request_Exception; use WCPay\Core\Exceptions\Server\Request\Immutable_Parameter_Exception; @@ -66,7 +66,7 @@ public function process() { $intent = $this->payment_request_service->create_intent( $context ); // Something went wrong since the intent is not an instance of payment intent class. Transition to error state. - if ( ! $intent instanceof WC_Payments_API_Payment_Intention ) { + if ( ! $intent instanceof WC_Payments_API_Abstract_Intention ) { return $this->create_state( SystemErrorState::class ); } $context->set_intent( $intent ); From e62825836bf2f3137e0e1cfee32b01cbe43e9b8d Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 12 Oct 2023 17:45:13 +0200 Subject: [PATCH 08/66] Code review fixes: Lint fix --- .../State/AuthenticationRequiredState.php | 3 ++- src/Internal/Payment/State/InitialState.php | 13 ++++++++----- src/Internal/Payment/State/ProcessedState.php | 6 +++--- src/Internal/Payment/State/VerifyState.php | 4 ++-- .../Service/CheckoutEncryptionService.php | 17 +++++++++-------- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/Internal/Payment/State/AuthenticationRequiredState.php b/src/Internal/Payment/State/AuthenticationRequiredState.php index 6f6bf95453f..e0072528d28 100644 --- a/src/Internal/Payment/State/AuthenticationRequiredState.php +++ b/src/Internal/Payment/State/AuthenticationRequiredState.php @@ -24,7 +24,7 @@ class AuthenticationRequiredState extends AbstractPaymentState { /** * Class constructor, only meant for storing dependencies. * - * @param StateFactory $state_factory Factory for payment states. + * @param StateFactory $state_factory Factory for payment states. * @param CheckoutEncryptionService $checkout_encryption_service Service for encrypting checkout data. */ public function __construct( @@ -35,6 +35,7 @@ public function __construct( $this->checkout_encryption_service = $checkout_encryption_service; } + /** * Get response that will be sent to the client. */ diff --git a/src/Internal/Payment/State/InitialState.php b/src/Internal/Payment/State/InitialState.php index 53e0b3862a7..4a4fc06b35e 100644 --- a/src/Internal/Payment/State/InitialState.php +++ b/src/Internal/Payment/State/InitialState.php @@ -45,10 +45,10 @@ class InitialState extends AbstractPaymentState { /** * Class constructor, only meant for storing dependencies. * - * @param StateFactory $state_factory Factory for payment states. - * @param OrderService $order_service Service for order-related actions. - * @param WC_Payments_Customer_Service $customer_service Service for managing remote customers. - * @param Level3Service $level3_service Service for Level3 Data. + * @param StateFactory $state_factory Factory for payment states. + * @param OrderService $order_service Service for order-related actions. + * @param WC_Payments_Customer_Service $customer_service Service for managing remote customers. + * @param Level3Service $level3_service Service for Level3 Data. */ public function __construct( StateFactory $state_factory, @@ -66,7 +66,8 @@ public function __construct( /** * Initialtes the payment process. * - * @param PaymentRequest $request The incoming payment processing request. + * @param PaymentRequest $request The incoming payment processing request. + * * @return AbstractPaymentState The next state. * @throws StateTransitionException In case the completed state could not be initialized. * @throws ContainerException When the dependency container cannot instantiate the state. @@ -81,6 +82,7 @@ public function start_processing( PaymentRequest $request ) { $this->populate_context_from_order(); $next_state = $this->create_state( VerifyState::class ); + return $next_state->process(); } @@ -93,6 +95,7 @@ public function start_processing( PaymentRequest $request ) { * on all needed parameters being in place. * * @param PaymentRequest $request The request to use. + * * @throws PaymentRequestException When data is not available or invalid. */ protected function populate_context_from_request( PaymentRequest $request ) { diff --git a/src/Internal/Payment/State/ProcessedState.php b/src/Internal/Payment/State/ProcessedState.php index 1c39453b44d..3ab42d67418 100644 --- a/src/Internal/Payment/State/ProcessedState.php +++ b/src/Internal/Payment/State/ProcessedState.php @@ -15,7 +15,7 @@ use WCPay\Internal\Service\PaymentRequestService; /** - * This state is used when payment is completed on the server and we need to update date on the plugin side. + * This state is used when payment is completed on the server, and we need to update date on the plugin side. */ class ProcessedState extends AbstractPaymentState { @@ -30,8 +30,8 @@ class ProcessedState extends AbstractPaymentState { /** * Class constructor, only meant for storing dependencies. * - * @param StateFactory $state_factory Factory for payment states. - * @param OrderService $order_service Service for order-related actions. + * @param StateFactory $state_factory Factory for payment states. + * @param OrderService $order_service Service for order-related actions. */ public function __construct( StateFactory $state_factory, diff --git a/src/Internal/Payment/State/VerifyState.php b/src/Internal/Payment/State/VerifyState.php index ceaf2579ad3..b2f3afb0586 100644 --- a/src/Internal/Payment/State/VerifyState.php +++ b/src/Internal/Payment/State/VerifyState.php @@ -37,8 +37,8 @@ class VerifyState extends AbstractPaymentState { /** * Class constructor, only meant for storing dependencies. * - * @param StateFactory $state_factory Factory for payment states. - * @param OrderService $order_service Service for order-related actions. + * @param StateFactory $state_factory Factory for payment states. + * @param OrderService $order_service Service for order-related actions. * @param PaymentRequestService $payment_request_service Connection with the server. */ public function __construct( diff --git a/src/Internal/Service/CheckoutEncryptionService.php b/src/Internal/Service/CheckoutEncryptionService.php index 3f087d77f8e..a4aa1d4f0b9 100644 --- a/src/Internal/Service/CheckoutEncryptionService.php +++ b/src/Internal/Service/CheckoutEncryptionService.php @@ -21,14 +21,14 @@ * Service for generating Level 3 data from orders. */ class CheckoutEncryptionService { - /** - * Encrypt client secret for the client. - * - * @param string $customer_id Customer id. - * @param string $client_secret Client secret. - * - * @return string - */ + /** + * Encrypt client secret for the client. + * + * @param string $customer_id Customer id. + * @param string $client_secret Client secret. + * + * @return string + */ public function encrypt_client_secret( string $customer_id, string $client_secret ): string { if ( \WC_Payments_Features::is_client_secret_encryption_enabled() ) { return openssl_encrypt( @@ -39,6 +39,7 @@ public function encrypt_client_secret( string $customer_id, string $client_secre str_repeat( 'WC', 8 ) ); } + return $client_secret; } } From ce20eaf4b3aa50f5d0a85af0d1e2d988cc09df10 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 12 Oct 2023 17:48:14 +0200 Subject: [PATCH 09/66] Code review fixes: Removed completed state variable in processing service --- src/Internal/Service/PaymentProcessingService.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Internal/Service/PaymentProcessingService.php b/src/Internal/Service/PaymentProcessingService.php index 20f4faa7bf2..690cd185c9b 100644 --- a/src/Internal/Service/PaymentProcessingService.php +++ b/src/Internal/Service/PaymentProcessingService.php @@ -64,11 +64,9 @@ public function process_payment( int $order_id, bool $manual_capture = false ) { // Start with a basis context. $context = $this->create_payment_context( $order_id, $manual_capture ); - $request = new PaymentRequest( $this->legacy_proxy ); - $initial_state = $this->state_factory->create_state( InitialState::class, $context ); - $completed_state = $initial_state->start_processing( $request ); - - return $completed_state; + $request = new PaymentRequest( $this->legacy_proxy ); + $initial_state = $this->state_factory->create_state( InitialState::class, $context ); + return $initial_state->start_processing( $request ); } /** From 7296e3be6acb0360e89a3e6d5be2f16868847c0b Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 12 Oct 2023 17:56:40 +0200 Subject: [PATCH 10/66] Code review fixes: Used legacy proxy sergice to call methods in CheckoutEncryptionService --- .../PaymentsServiceProvider.php | 4 ++- .../Service/CheckoutEncryptionService.php | 35 +++++++++++++------ .../Service/PaymentProcessingService.php | 13 ++++--- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php index 5436aa8e981..ad44617946b 100644 --- a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php +++ b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php @@ -70,7 +70,9 @@ public function register(): void { ->addArgument( LegacyProxy::class ); $container->addShared( PaymentRequestService::class ); - $container->addShared( CheckoutEncryptionService::class ); + + $container->addShared( CheckoutEncryptionService::class ) + ->addArgument( LegacyProxy::class ); $container->add( InitialState::class ) ->addArgument( StateFactory::class ) diff --git a/src/Internal/Service/CheckoutEncryptionService.php b/src/Internal/Service/CheckoutEncryptionService.php index a4aa1d4f0b9..b762ba751d2 100644 --- a/src/Internal/Service/CheckoutEncryptionService.php +++ b/src/Internal/Service/CheckoutEncryptionService.php @@ -7,20 +7,32 @@ namespace WCPay\Internal\Service; -use stdClass; -use WC_Order_Item; -use WC_Order_Item_Product; -use WC_Order_Item_Fee; -use WC_Payments_Account; -use WC_Payments_Utils; -use WCPay\Internal\Service\OrderService; -use WCPay\Exceptions\Order_Not_Found_Exception; +use WCPay\Internal\Payment\State\StateFactory; use WCPay\Internal\Proxy\LegacyProxy; /** - * Service for generating Level 3 data from orders. + * Service for checkout data encryption. */ class CheckoutEncryptionService { + + /** + * Legacy Proxy. + * + * @var LegacyProxy + */ + private $legacy_proxy; + + /** + * Service constructor. + * + * @param LegacyProxy $legacy_proxy Legacy proxy. + */ + public function __construct( + LegacyProxy $legacy_proxy + ) { + $this->legacy_proxy = $legacy_proxy; + } + /** * Encrypt client secret for the client. * @@ -30,8 +42,9 @@ class CheckoutEncryptionService { * @return string */ public function encrypt_client_secret( string $customer_id, string $client_secret ): string { - if ( \WC_Payments_Features::is_client_secret_encryption_enabled() ) { - return openssl_encrypt( + if ( $this->legacy_proxy->call_static( \WC_Payments_Features::class, 'is_client_secret_encryption_enabled' ) ) { + return $this->legacy_proxy->call_function( + 'openssl_encrypt', $client_secret, 'aes-128-cbc', substr( $customer_id, 5 ), diff --git a/src/Internal/Service/PaymentProcessingService.php b/src/Internal/Service/PaymentProcessingService.php index 690cd185c9b..1b4ef350491 100644 --- a/src/Internal/Service/PaymentProcessingService.php +++ b/src/Internal/Service/PaymentProcessingService.php @@ -7,7 +7,9 @@ namespace WCPay\Internal\Service; -use Exception; // Temporary exception! This service would have its own exception when more business logics are added. +use Exception; + +// Temporary exception! This service would have its own exception when more business logics are added. use WCPay\Vendor\League\Container\Exception\ContainerException; use WCPay\Internal\Payment\PaymentContext; use WCPay\Internal\Payment\State\InitialState; @@ -39,7 +41,7 @@ class PaymentProcessingService { * Service constructor. * * @param StateFactory $state_factory Factory for payment states. - * @param LegacyProxy $legacy_proxy Legacy proxy. + * @param LegacyProxy $legacy_proxy Legacy proxy. */ public function __construct( StateFactory $state_factory, @@ -52,7 +54,7 @@ public function __construct( /** * Process payment. * - * @param int $order_id Order ID provided by WooCommerce core. + * @param int $order_id Order ID provided by WooCommerce core. * @param bool $manual_capture Whether to only create an authorization instead of a charge (optional). * * @throws Exception @@ -66,19 +68,22 @@ public function process_payment( int $order_id, bool $manual_capture = false ) { $request = new PaymentRequest( $this->legacy_proxy ); $initial_state = $this->state_factory->create_state( InitialState::class, $context ); + return $initial_state->start_processing( $request ); } /** * Instantiates a new empty payment context. * - * @param int $order_id ID of the order that the context belongs to. + * @param int $order_id ID of the order that the context belongs to. * @param bool $manual_capture Whether manual capture is enabled. + * * @return PaymentContext */ protected function create_payment_context( int $order_id, bool $manual_capture = false ): PaymentContext { $context = new PaymentContext( $order_id ); $context->toggle_manual_capture( $manual_capture ); + return $context; } } From 585514936cd51dbe088123bba8615412d3a3272b Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 12 Oct 2023 18:04:46 +0200 Subject: [PATCH 11/66] Renamed VerifyState to VerifiedState --- .../ServiceProvider/PaymentsServiceProvider.php | 6 +++--- src/Internal/Payment/State/InitialState.php | 2 +- .../Payment/State/{VerifyState.php => VerifiedState.php} | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename src/Internal/Payment/State/{VerifyState.php => VerifiedState.php} (97%) diff --git a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php index ad44617946b..14f08cb90d5 100644 --- a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php +++ b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php @@ -21,7 +21,7 @@ use WCPay\Internal\Payment\State\ProcessedState; use WCPay\Internal\Payment\State\StateFactory; use WCPay\Internal\Payment\State\SystemErrorState; -use WCPay\Internal\Payment\State\VerifyState; +use WCPay\Internal\Payment\State\VerifiedState; use WCPay\Internal\Proxy\LegacyProxy; use WCPay\Internal\Service\CheckoutEncryptionService; use WCPay\Internal\Service\PaymentProcessingService; @@ -45,7 +45,7 @@ class PaymentsServiceProvider extends AbstractServiceProvider { Router::class, StateFactory::class, InitialState::class, - VerifyState::class, + VerifiedState::class, ProcessedState::class, CompletedState::class, SystemErrorState::class, @@ -80,7 +80,7 @@ public function register(): void { ->addArgument( WC_Payments_Customer_Service::class ) ->addArgument( Level3Service::class ); - $container->add( VerifyState::class ) + $container->add( VerifiedState::class ) ->addArgument( StateFactory::class ) ->addArgument( OrderService::class ) ->addArgument( PaymentRequestService::class ); diff --git a/src/Internal/Payment/State/InitialState.php b/src/Internal/Payment/State/InitialState.php index 4a4fc06b35e..27e762903d6 100644 --- a/src/Internal/Payment/State/InitialState.php +++ b/src/Internal/Payment/State/InitialState.php @@ -81,7 +81,7 @@ public function start_processing( PaymentRequest $request ) { // Populate further details from the order. $this->populate_context_from_order(); - $next_state = $this->create_state( VerifyState::class ); + $next_state = $this->create_state( VerifiedState::class ); return $next_state->process(); } diff --git a/src/Internal/Payment/State/VerifyState.php b/src/Internal/Payment/State/VerifiedState.php similarity index 97% rename from src/Internal/Payment/State/VerifyState.php rename to src/Internal/Payment/State/VerifiedState.php index b2f3afb0586..25ed78c4b6f 100644 --- a/src/Internal/Payment/State/VerifyState.php +++ b/src/Internal/Payment/State/VerifiedState.php @@ -1,6 +1,6 @@ Date: Thu, 12 Oct 2023 18:05:08 +0200 Subject: [PATCH 12/66] Removed intent check in VerifiedState --- src/Internal/Payment/State/VerifiedState.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Internal/Payment/State/VerifiedState.php b/src/Internal/Payment/State/VerifiedState.php index 25ed78c4b6f..261e10d5dc1 100644 --- a/src/Internal/Payment/State/VerifiedState.php +++ b/src/Internal/Payment/State/VerifiedState.php @@ -64,11 +64,6 @@ public function process() { // Payments are currently based on intents, request one from the API. try { $intent = $this->payment_request_service->create_intent( $context ); - - // Something went wrong since the intent is not an instance of payment intent class. Transition to error state. - if ( ! $intent instanceof WC_Payments_API_Abstract_Intention ) { - return $this->create_state( SystemErrorState::class ); - } $context->set_intent( $intent ); } catch ( Invalid_Request_Parameter_Exception | Extend_Request_Exception | Immutable_Parameter_Exception $e ) { return $this->create_state( SystemErrorState::class ); From 582352a1000f75a7d1f6c5b43c1655598557b4d1 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 12 Oct 2023 18:07:22 +0200 Subject: [PATCH 13/66] Code review fixes: Called complete() method on ProcessedState inside VerifiedState --- src/Internal/Payment/State/VerifiedState.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Internal/Payment/State/VerifiedState.php b/src/Internal/Payment/State/VerifiedState.php index 261e10d5dc1..a3a40ff47e6 100644 --- a/src/Internal/Payment/State/VerifiedState.php +++ b/src/Internal/Payment/State/VerifiedState.php @@ -57,6 +57,7 @@ public function __construct( * * @return AbstractPaymentState * @throws \WCPay\Internal\Payment\Exception\StateTransitionException + * @throws \WCPay\Vendor\League\Container\Exception\ContainerException */ public function process() { $context = $this->get_context(); @@ -75,6 +76,7 @@ public function process() { } // All good. Proceed to processed state. - return $this->create_state( ProcessedState::class ); + $next_state = $this->create_state( ProcessedState::class ); + return $next_state->complete(); } } From 427a6c36a2fbb2bf7f368a10daa4646f4120d3c4 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 12 Oct 2023 18:18:58 +0200 Subject: [PATCH 14/66] Added missing method --- src/Internal/Payment/State/AbstractPaymentState.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Internal/Payment/State/AbstractPaymentState.php b/src/Internal/Payment/State/AbstractPaymentState.php index 253d56ffe58..50dbabe6d56 100644 --- a/src/Internal/Payment/State/AbstractPaymentState.php +++ b/src/Internal/Payment/State/AbstractPaymentState.php @@ -105,6 +105,17 @@ public function start_processing( PaymentRequest $request ) { $this->throw_unavailable_method_exception( __METHOD__ ); } + /** + * Process all needed verifications. + * + * @return AbstractPaymentState + * @throws \WCPay\Exceptions\Order_Not_Found_Exception + * @throws \WCPay\Internal\Payment\Exception\StateTransitionException + */ + public function complete() { + $this->throw_unavailable_method_exception( __METHOD__ ); + } + /** * Throws an exception, indicating that a given method is not available. * From 044cdf8890f23a366b897fef839b1c28c29bf06a Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 12 Oct 2023 18:24:32 +0200 Subject: [PATCH 15/66] Refactored AuthenticationRequiredState to return redirect url only. --- .../State/AuthenticationRequiredState.php | 41 ++++++------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/src/Internal/Payment/State/AuthenticationRequiredState.php b/src/Internal/Payment/State/AuthenticationRequiredState.php index e0072528d28..312a23bc472 100644 --- a/src/Internal/Payment/State/AuthenticationRequiredState.php +++ b/src/Internal/Payment/State/AuthenticationRequiredState.php @@ -37,40 +37,25 @@ public function __construct( } /** - * Get response that will be sent to the client. + * Get redirect url. + * + * @return string */ - public function get_response(): array { + public function get_redirect_url() { $context = $this->get_context(); $intent = $context->get_intent(); $next_action = $intent->get_next_action(); + if ( isset( $next_action['type'] ) && 'redirect_to_url' === $next_action['type'] && ! empty( $next_action['redirect_to_url']['url'] ) ) { - $response = [ - 'result' => 'success', - 'redirect' => $next_action['redirect_to_url']['url'], - ]; - } else { - if ( ! $context->get_payment_method() ) { - $payment_method_id = $context->get_payment_method()->get_id(); - } else { - $payment_method_id = $intent->get_payment_method_id(); - } - $response = [ - 'result' => 'success', - // Include a new nonce for update_order_status to ensure the update order - // status call works when a guest user creates an account during checkout. - 'redirect' => sprintf( - '#wcpay-confirm-%s:%s:%s:%s', - $context->get_amount() > 0 ? 'pi' : 'si', - $context->get_order_id(), - $this->checkout_encryption_service->encrypt_client_secret( $intent->get_customer_id(), $intent->get_client_secret() ), - wp_create_nonce( 'wcpay_update_order_status_nonce' ) - ), - // Include the payment method ID so the Blocks integration can save cards. - 'payment_method' => $payment_method_id, - ]; + return $next_action['redirect_to_url']['url']; } - return $response; + return sprintf( + '#wcpay-confirm-%s:%s:%s:%s', + $context->get_amount() > 0 ? 'pi' : 'si', + $context->get_order_id(), + $this->checkout_encryption_service->encrypt_client_secret( $intent->get_customer_id(), $intent->get_client_secret() ), + wp_create_nonce( 'wcpay_update_order_status_nonce' ) + ); } - } From c0814d5f66ce50c743d2661bc9d3eca7419a933c Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 12 Oct 2023 18:29:39 +0200 Subject: [PATCH 16/66] Added legacy proxy call and fixed dependencies --- .../ServiceProvider/PaymentsServiceProvider.php | 3 ++- .../Payment/State/AbstractPaymentState.php | 10 ++++++++++ .../Payment/State/AuthenticationRequiredState.php | 14 +++++++++++++- src/Internal/Service/CheckoutEncryptionService.php | 1 - 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php index 14f08cb90d5..2f349773cca 100644 --- a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php +++ b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php @@ -91,7 +91,8 @@ public function register(): void { $container->add( AuthenticationRequiredState::class ) ->addArgument( StateFactory::class ) - ->addArgument( OrderService::class ); + ->addArgument( LegacyProxy::class ) + ->addArgument( CheckoutEncryptionService::class ); $container->add( CompletedState::class ) ->addArgument( StateFactory::class ); diff --git a/src/Internal/Payment/State/AbstractPaymentState.php b/src/Internal/Payment/State/AbstractPaymentState.php index 50dbabe6d56..19e78c549e3 100644 --- a/src/Internal/Payment/State/AbstractPaymentState.php +++ b/src/Internal/Payment/State/AbstractPaymentState.php @@ -116,6 +116,16 @@ public function complete() { $this->throw_unavailable_method_exception( __METHOD__ ); } + /** + * Get redirect url. + * + * @return string + * @throws \WCPay\Internal\Payment\Exception\StateTransitionException + */ + public function get_redirect_url() { + $this->throw_unavailable_method_exception( __METHOD__ ); + } + /** * Throws an exception, indicating that a given method is not available. * diff --git a/src/Internal/Payment/State/AuthenticationRequiredState.php b/src/Internal/Payment/State/AuthenticationRequiredState.php index 312a23bc472..766d4c10238 100644 --- a/src/Internal/Payment/State/AuthenticationRequiredState.php +++ b/src/Internal/Payment/State/AuthenticationRequiredState.php @@ -7,12 +7,21 @@ namespace WCPay\Internal\Payment\State; +use WCPay\Internal\Proxy\LegacyProxy; use WCPay\Internal\Service\CheckoutEncryptionService; /** * The state, which indicates that the payment processing has been completed. */ class AuthenticationRequiredState extends AbstractPaymentState { + + /** + * Legacy Proxy. + * + * @var LegacyProxy + */ + private $legacy_proxy; + /** * Checkout Encryption service * @@ -25,14 +34,17 @@ class AuthenticationRequiredState extends AbstractPaymentState { * Class constructor, only meant for storing dependencies. * * @param StateFactory $state_factory Factory for payment states. + * @param LegacyProxy $legacy_proxy Legacy proxy. * @param CheckoutEncryptionService $checkout_encryption_service Service for encrypting checkout data. */ public function __construct( StateFactory $state_factory, + LegacyProxy $legacy_proxy, CheckoutEncryptionService $checkout_encryption_service ) { parent::__construct( $state_factory ); + $this->legacy_proxy = $legacy_proxy; $this->checkout_encryption_service = $checkout_encryption_service; } @@ -55,7 +67,7 @@ public function get_redirect_url() { $context->get_amount() > 0 ? 'pi' : 'si', $context->get_order_id(), $this->checkout_encryption_service->encrypt_client_secret( $intent->get_customer_id(), $intent->get_client_secret() ), - wp_create_nonce( 'wcpay_update_order_status_nonce' ) + $this->legacy_proxy->call_function( 'wp_create_nonce', 'wcpay_update_order_status_nonce' ) ); } } diff --git a/src/Internal/Service/CheckoutEncryptionService.php b/src/Internal/Service/CheckoutEncryptionService.php index b762ba751d2..a4b1a5386c4 100644 --- a/src/Internal/Service/CheckoutEncryptionService.php +++ b/src/Internal/Service/CheckoutEncryptionService.php @@ -7,7 +7,6 @@ namespace WCPay\Internal\Service; -use WCPay\Internal\Payment\State\StateFactory; use WCPay\Internal\Proxy\LegacyProxy; /** From e2189419b383bb031404e3c4f906b26e169caa45 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Fri, 13 Oct 2023 13:01:01 +0200 Subject: [PATCH 17/66] Changed method name in AuthenticationRequiredState --- src/Internal/Payment/State/AuthenticationRequiredState.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Internal/Payment/State/AuthenticationRequiredState.php b/src/Internal/Payment/State/AuthenticationRequiredState.php index 766d4c10238..ab8d8dfb3f3 100644 --- a/src/Internal/Payment/State/AuthenticationRequiredState.php +++ b/src/Internal/Payment/State/AuthenticationRequiredState.php @@ -49,11 +49,11 @@ public function __construct( } /** - * Get redirect url. + * Get authentication redirect url. * * @return string */ - public function get_redirect_url() { + public function get_authentication_url() { $context = $this->get_context(); $intent = $context->get_intent(); $next_action = $intent->get_next_action(); From 8f1ebd8f2f87833dcccb8539e183ed9e164d3218 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Fri, 13 Oct 2023 13:05:53 +0200 Subject: [PATCH 18/66] Removed CheckoutEncryptionService service and moved methods to the state --- .../PaymentsServiceProvider.php | 8 +-- .../State/AuthenticationRequiredState.php | 46 +++++++++------ .../Service/CheckoutEncryptionService.php | 57 ------------------- 3 files changed, 29 insertions(+), 82 deletions(-) delete mode 100644 src/Internal/Service/CheckoutEncryptionService.php diff --git a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php index 2f349773cca..5cf4397aa30 100644 --- a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php +++ b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php @@ -23,7 +23,6 @@ use WCPay\Internal\Payment\State\SystemErrorState; use WCPay\Internal\Payment\State\VerifiedState; use WCPay\Internal\Proxy\LegacyProxy; -use WCPay\Internal\Service\CheckoutEncryptionService; use WCPay\Internal\Service\PaymentProcessingService; use WCPay\Internal\Service\ExampleService; use WCPay\Internal\Service\ExampleServiceWithDependencies; @@ -53,7 +52,6 @@ class PaymentsServiceProvider extends AbstractServiceProvider { ExampleService::class, ExampleServiceWithDependencies::class, PaymentRequestService::class, - CheckoutEncryptionService::class, ]; /** @@ -71,9 +69,6 @@ public function register(): void { $container->addShared( PaymentRequestService::class ); - $container->addShared( CheckoutEncryptionService::class ) - ->addArgument( LegacyProxy::class ); - $container->add( InitialState::class ) ->addArgument( StateFactory::class ) ->addArgument( OrderService::class ) @@ -91,8 +86,7 @@ public function register(): void { $container->add( AuthenticationRequiredState::class ) ->addArgument( StateFactory::class ) - ->addArgument( LegacyProxy::class ) - ->addArgument( CheckoutEncryptionService::class ); + ->addArgument( LegacyProxy::class ); $container->add( CompletedState::class ) ->addArgument( StateFactory::class ); diff --git a/src/Internal/Payment/State/AuthenticationRequiredState.php b/src/Internal/Payment/State/AuthenticationRequiredState.php index ab8d8dfb3f3..53a00058a9a 100644 --- a/src/Internal/Payment/State/AuthenticationRequiredState.php +++ b/src/Internal/Payment/State/AuthenticationRequiredState.php @@ -8,8 +8,6 @@ namespace WCPay\Internal\Payment\State; use WCPay\Internal\Proxy\LegacyProxy; -use WCPay\Internal\Service\CheckoutEncryptionService; - /** * The state, which indicates that the payment processing has been completed. */ @@ -22,30 +20,19 @@ class AuthenticationRequiredState extends AbstractPaymentState { */ private $legacy_proxy; - /** - * Checkout Encryption service - * - * @var CheckoutEncryptionService - */ - private $checkout_encryption_service; - - /** * Class constructor, only meant for storing dependencies. * - * @param StateFactory $state_factory Factory for payment states. - * @param LegacyProxy $legacy_proxy Legacy proxy. - * @param CheckoutEncryptionService $checkout_encryption_service Service for encrypting checkout data. + * @param StateFactory $state_factory Factory for payment states. + * @param LegacyProxy $legacy_proxy Legacy proxy. */ public function __construct( StateFactory $state_factory, - LegacyProxy $legacy_proxy, - CheckoutEncryptionService $checkout_encryption_service + LegacyProxy $legacy_proxy ) { parent::__construct( $state_factory ); - $this->legacy_proxy = $legacy_proxy; - $this->checkout_encryption_service = $checkout_encryption_service; + $this->legacy_proxy = $legacy_proxy; } /** @@ -66,8 +53,31 @@ public function get_authentication_url() { '#wcpay-confirm-%s:%s:%s:%s', $context->get_amount() > 0 ? 'pi' : 'si', $context->get_order_id(), - $this->checkout_encryption_service->encrypt_client_secret( $intent->get_customer_id(), $intent->get_client_secret() ), + $this->encrypt_client_secret( $intent->get_customer_id(), $intent->get_client_secret() ), $this->legacy_proxy->call_function( 'wp_create_nonce', 'wcpay_update_order_status_nonce' ) ); } + + /** + * Encrypt client secret for the client. + * + * @param string $customer_id Customer id. + * @param string $client_secret Client secret. + * + * @return string + */ + private function encrypt_client_secret( string $customer_id, string $client_secret ): string { + if ( $this->legacy_proxy->call_static( \WC_Payments_Features::class, 'is_client_secret_encryption_enabled' ) ) { + return $this->legacy_proxy->call_function( + 'openssl_encrypt', + $client_secret, + 'aes-128-cbc', + substr( $customer_id, 5 ), + 0, + str_repeat( 'WC', 8 ) + ); + } + + return $client_secret; + } } diff --git a/src/Internal/Service/CheckoutEncryptionService.php b/src/Internal/Service/CheckoutEncryptionService.php deleted file mode 100644 index a4b1a5386c4..00000000000 --- a/src/Internal/Service/CheckoutEncryptionService.php +++ /dev/null @@ -1,57 +0,0 @@ -legacy_proxy = $legacy_proxy; - } - - /** - * Encrypt client secret for the client. - * - * @param string $customer_id Customer id. - * @param string $client_secret Client secret. - * - * @return string - */ - public function encrypt_client_secret( string $customer_id, string $client_secret ): string { - if ( $this->legacy_proxy->call_static( \WC_Payments_Features::class, 'is_client_secret_encryption_enabled' ) ) { - return $this->legacy_proxy->call_function( - 'openssl_encrypt', - $client_secret, - 'aes-128-cbc', - substr( $customer_id, 5 ), - 0, - str_repeat( 'WC', 8 ) - ); - } - - return $client_secret; - } -} From d07b60c0669e77bcad0d5e16958ec03daa45d6f5 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Fri, 13 Oct 2023 13:44:52 +0200 Subject: [PATCH 19/66] PSALM fixes --- src/Internal/Payment/State/AbstractPaymentState.php | 8 ++++++-- src/Internal/Payment/State/InitialState.php | 5 +++++ src/Internal/Payment/State/ProcessedState.php | 1 + src/Internal/Payment/State/VerifiedState.php | 2 ++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Internal/Payment/State/AbstractPaymentState.php b/src/Internal/Payment/State/AbstractPaymentState.php index 19e78c549e3..093daaa8e43 100644 --- a/src/Internal/Payment/State/AbstractPaymentState.php +++ b/src/Internal/Payment/State/AbstractPaymentState.php @@ -91,7 +91,7 @@ protected function create_state( string $state_class ) { */ /** - * Initialtes the payment process. + * Initiates the payment process. * * @param PaymentRequest $request The incoming payment processing request. * @return AbstractPaymentState The next state. @@ -108,6 +108,8 @@ public function start_processing( PaymentRequest $request ) { /** * Process all needed verifications. * + * @psalm-suppress InvalidReturnType + * * @return AbstractPaymentState * @throws \WCPay\Exceptions\Order_Not_Found_Exception * @throws \WCPay\Internal\Payment\Exception\StateTransitionException @@ -119,10 +121,12 @@ public function complete() { /** * Get redirect url. * + * @psalm-suppress InvalidReturnType + * * @return string * @throws \WCPay\Internal\Payment\Exception\StateTransitionException */ - public function get_redirect_url() { + public function get_authentication_url() { $this->throw_unavailable_method_exception( __METHOD__ ); } diff --git a/src/Internal/Payment/State/InitialState.php b/src/Internal/Payment/State/InitialState.php index 27e762903d6..7d6fcd8d174 100644 --- a/src/Internal/Payment/State/InitialState.php +++ b/src/Internal/Payment/State/InitialState.php @@ -81,6 +81,11 @@ public function start_processing( PaymentRequest $request ) { // Populate further details from the order. $this->populate_context_from_order(); + /** + * Next state variable. + * + * @var VerifiedState $next_state + */ $next_state = $this->create_state( VerifiedState::class ); return $next_state->process(); diff --git a/src/Internal/Payment/State/ProcessedState.php b/src/Internal/Payment/State/ProcessedState.php index 3ab42d67418..1ff6746657b 100644 --- a/src/Internal/Payment/State/ProcessedState.php +++ b/src/Internal/Payment/State/ProcessedState.php @@ -48,6 +48,7 @@ public function __construct( * @return AbstractPaymentState * @throws \WCPay\Exceptions\Order_Not_Found_Exception * @throws \WCPay\Internal\Payment\Exception\StateTransitionException + * @throws \WCPay\Vendor\League\Container\Exception\ContainerException */ public function complete() { $context = $this->get_context(); diff --git a/src/Internal/Payment/State/VerifiedState.php b/src/Internal/Payment/State/VerifiedState.php index a3a40ff47e6..387b5faaade 100644 --- a/src/Internal/Payment/State/VerifiedState.php +++ b/src/Internal/Payment/State/VerifiedState.php @@ -56,6 +56,8 @@ public function __construct( * Process all needed verifications. * * @return AbstractPaymentState + * + * @throws \WCPay\Exceptions\Order_Not_Found_Exception * @throws \WCPay\Internal\Payment\Exception\StateTransitionException * @throws \WCPay\Vendor\League\Container\Exception\ContainerException */ From 4bf424a2290d9f69697cf4ff4c328bbd19abe342 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Fri, 13 Oct 2023 17:42:23 +0200 Subject: [PATCH 20/66] Added auth required response class --- includes/class-wc-payment-gateway-wcpay.php | 11 +++ .../AuthenticationRequiredResponse.php | 79 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 src/Internal/Payment/Response/AuthenticationRequiredResponse.php diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index cc33a732379..7986dd1b1a8 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -32,6 +32,8 @@ use WCPay\Duplicate_Payment_Prevention_Service; use WCPay\Fraud_Prevention\Fraud_Prevention_Service; use WCPay\Fraud_Prevention\Fraud_Risk_Tools; +use WCPay\Internal\Payment\Response\AuthenticationRequiredResponse; +use WCPay\Internal\Payment\State\AuthenticationRequiredState; use WCPay\Logger; use WCPay\Payment_Information; use WCPay\Payment_Methods\UPE_Payment_Gateway; @@ -826,6 +828,15 @@ public function new_process_payment( WC_Order $order ) { ]; } + if ( $state instanceof AuthenticationRequiredState ) { + $context = $state->get_context(); + // Maybe we can move this inside the response class and call function like generate_response() that will something like this where we have more control of status code, ... + return [ + 'result' => 'success', + 'redirect' => ( new AuthenticationRequiredResponse( $context->get_intent(), $context->get_order_id() ) )->get_url(), + ]; + } + throw new Exception( __( 'The payment process could not be completed.', 'woocommerce-payments' ) ); } diff --git a/src/Internal/Payment/Response/AuthenticationRequiredResponse.php b/src/Internal/Payment/Response/AuthenticationRequiredResponse.php new file mode 100644 index 00000000000..c0cbb8d6a9a --- /dev/null +++ b/src/Internal/Payment/Response/AuthenticationRequiredResponse.php @@ -0,0 +1,79 @@ +intent = $intent; + $this->order_id = $order_id; + } + + /** + * Get redirect url. + * + * @return string + */ + public function get_url() { + $legacy_proxy = wcpay_get_container()->get( LegacyProxy::class ); // Perhaps we can move it inside constructor or not use it at all. + $next_action = $this->intent->get_next_action(); + + if ( isset( $next_action['type'] ) && 'redirect_to_url' === $next_action['type'] && ! empty( $next_action['redirect_to_url']['url'] ) ) { + return $next_action['redirect_to_url']['url']; + } + + $client_secret = $this->intent->get_client_secret(); + + if ( $legacy_proxy->call_static( \WC_Payments_Features::class, 'is_client_secret_encryption_enabled' ) ) { + $client_secret = $legacy_proxy->call_function( + 'openssl_encrypt', + $client_secret, + 'aes-128-cbc', + substr( $this->intent->get_customer_id(), 5 ), + 0, + str_repeat( 'WC', 8 ) + ); + } + + return sprintf( + '#wcpay-confirm-%s:%s:%s:%s', + substr( $this->intent->get_id(), 0, 2 ), // intents starts with pi_ or si_ so we need only to first two letters. + $this->order_id, + $client_secret, + $legacy_proxy->call_function( 'wp_create_nonce', 'wcpay_update_order_status_nonce' ) + ); + } + + +} From 3e3b80b72078398459eac3da889b8232e0b38a3d Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Fri, 13 Oct 2023 18:34:02 +0200 Subject: [PATCH 21/66] Removed response class --- includes/class-wc-payment-gateway-wcpay.php | 2 +- .../PaymentsServiceProvider.php | 3 +- .../AuthenticationRequiredResponse.php | 79 ------------------- .../State/AuthenticationRequiredState.php | 68 ---------------- .../Service/PaymentProcessingService.php | 39 +++++++++ 5 files changed, 41 insertions(+), 150 deletions(-) delete mode 100644 src/Internal/Payment/Response/AuthenticationRequiredResponse.php diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 7986dd1b1a8..4eb8b308205 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -833,7 +833,7 @@ public function new_process_payment( WC_Order $order ) { // Maybe we can move this inside the response class and call function like generate_response() that will something like this where we have more control of status code, ... return [ 'result' => 'success', - 'redirect' => ( new AuthenticationRequiredResponse( $context->get_intent(), $context->get_order_id() ) )->get_url(), + 'redirect' => $service->get_authentication_redirect_url( $context->get_intent(), $context->get_order_id() ), ]; } diff --git a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php index 5cf4397aa30..d80b875f99e 100644 --- a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php +++ b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php @@ -85,8 +85,7 @@ public function register(): void { ->addArgument( OrderService::class ); $container->add( AuthenticationRequiredState::class ) - ->addArgument( StateFactory::class ) - ->addArgument( LegacyProxy::class ); + ->addArgument( StateFactory::class ); $container->add( CompletedState::class ) ->addArgument( StateFactory::class ); diff --git a/src/Internal/Payment/Response/AuthenticationRequiredResponse.php b/src/Internal/Payment/Response/AuthenticationRequiredResponse.php deleted file mode 100644 index c0cbb8d6a9a..00000000000 --- a/src/Internal/Payment/Response/AuthenticationRequiredResponse.php +++ /dev/null @@ -1,79 +0,0 @@ -intent = $intent; - $this->order_id = $order_id; - } - - /** - * Get redirect url. - * - * @return string - */ - public function get_url() { - $legacy_proxy = wcpay_get_container()->get( LegacyProxy::class ); // Perhaps we can move it inside constructor or not use it at all. - $next_action = $this->intent->get_next_action(); - - if ( isset( $next_action['type'] ) && 'redirect_to_url' === $next_action['type'] && ! empty( $next_action['redirect_to_url']['url'] ) ) { - return $next_action['redirect_to_url']['url']; - } - - $client_secret = $this->intent->get_client_secret(); - - if ( $legacy_proxy->call_static( \WC_Payments_Features::class, 'is_client_secret_encryption_enabled' ) ) { - $client_secret = $legacy_proxy->call_function( - 'openssl_encrypt', - $client_secret, - 'aes-128-cbc', - substr( $this->intent->get_customer_id(), 5 ), - 0, - str_repeat( 'WC', 8 ) - ); - } - - return sprintf( - '#wcpay-confirm-%s:%s:%s:%s', - substr( $this->intent->get_id(), 0, 2 ), // intents starts with pi_ or si_ so we need only to first two letters. - $this->order_id, - $client_secret, - $legacy_proxy->call_function( 'wp_create_nonce', 'wcpay_update_order_status_nonce' ) - ); - } - - -} diff --git a/src/Internal/Payment/State/AuthenticationRequiredState.php b/src/Internal/Payment/State/AuthenticationRequiredState.php index 53a00058a9a..c42b0c2fe58 100644 --- a/src/Internal/Payment/State/AuthenticationRequiredState.php +++ b/src/Internal/Payment/State/AuthenticationRequiredState.php @@ -12,72 +12,4 @@ * The state, which indicates that the payment processing has been completed. */ class AuthenticationRequiredState extends AbstractPaymentState { - - /** - * Legacy Proxy. - * - * @var LegacyProxy - */ - private $legacy_proxy; - - /** - * Class constructor, only meant for storing dependencies. - * - * @param StateFactory $state_factory Factory for payment states. - * @param LegacyProxy $legacy_proxy Legacy proxy. - */ - public function __construct( - StateFactory $state_factory, - LegacyProxy $legacy_proxy - ) { - parent::__construct( $state_factory ); - - $this->legacy_proxy = $legacy_proxy; - } - - /** - * Get authentication redirect url. - * - * @return string - */ - public function get_authentication_url() { - $context = $this->get_context(); - $intent = $context->get_intent(); - $next_action = $intent->get_next_action(); - - if ( isset( $next_action['type'] ) && 'redirect_to_url' === $next_action['type'] && ! empty( $next_action['redirect_to_url']['url'] ) ) { - return $next_action['redirect_to_url']['url']; - } - - return sprintf( - '#wcpay-confirm-%s:%s:%s:%s', - $context->get_amount() > 0 ? 'pi' : 'si', - $context->get_order_id(), - $this->encrypt_client_secret( $intent->get_customer_id(), $intent->get_client_secret() ), - $this->legacy_proxy->call_function( 'wp_create_nonce', 'wcpay_update_order_status_nonce' ) - ); - } - - /** - * Encrypt client secret for the client. - * - * @param string $customer_id Customer id. - * @param string $client_secret Client secret. - * - * @return string - */ - private function encrypt_client_secret( string $customer_id, string $client_secret ): string { - if ( $this->legacy_proxy->call_static( \WC_Payments_Features::class, 'is_client_secret_encryption_enabled' ) ) { - return $this->legacy_proxy->call_function( - 'openssl_encrypt', - $client_secret, - 'aes-128-cbc', - substr( $customer_id, 5 ), - 0, - str_repeat( 'WC', 8 ) - ); - } - - return $client_secret; - } } diff --git a/src/Internal/Service/PaymentProcessingService.php b/src/Internal/Service/PaymentProcessingService.php index 1b4ef350491..9cd61e85bf9 100644 --- a/src/Internal/Service/PaymentProcessingService.php +++ b/src/Internal/Service/PaymentProcessingService.php @@ -10,6 +10,7 @@ use Exception; // Temporary exception! This service would have its own exception when more business logics are added. +use WC_Payments_API_Abstract_Intention; use WCPay\Vendor\League\Container\Exception\ContainerException; use WCPay\Internal\Payment\PaymentContext; use WCPay\Internal\Payment\State\InitialState; @@ -86,4 +87,42 @@ protected function create_payment_context( int $order_id, bool $manual_capture = return $context; } + + /** + * Get redirect URL when authentication is required (3DS). + * + * @param WC_Payments_API_Abstract_Intention $intent Intent object. + * @param int $order_id Order id. + * + * @return string + */ + public function get_authentication_redirect_url( $intent, int $order_id ) { + $next_action = $intent->get_next_action(); + + if ( isset( $next_action['type'] ) && 'redirect_to_url' === $next_action['type'] && ! empty( $next_action['redirect_to_url']['url'] ) ) { + return $next_action['redirect_to_url']['url']; + } + + $client_secret = $intent->get_client_secret(); + + if ( $this->legacy_proxy->call_static( \WC_Payments_Features::class, 'is_client_secret_encryption_enabled' ) ) { + $client_secret = $this->legacy_proxy->call_function( + 'openssl_encrypt', + $client_secret, + 'aes-128-cbc', + substr( $intent->get_customer_id(), 5 ), + 0, + str_repeat( 'WC', 8 ) + ); + } + + return sprintf( + '#wcpay-confirm-%s:%s:%s:%s', + substr( $intent->get_id(), 0, 2 ), // intents starts with pi_ or si_ so we need only to first two letters. + $order_id, + $client_secret, + $this->legacy_proxy->call_function( 'wp_create_nonce', 'wcpay_update_order_status_nonce' ) + ); + } + } From 193728486e7b4d29a384568258941807cc996618 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 16 Oct 2023 15:52:49 +0200 Subject: [PATCH 22/66] Fixed broken tests --- .../Payment/State/AbstractPaymentState.php | 11 +- .../State/AbstractPaymentStateTest.php | 19 ++- .../Payment/State/InitialStateTest.php | 131 ++++++++---------- .../Service/PaymentProcessingServiceTest.php | 4 +- 4 files changed, 84 insertions(+), 81 deletions(-) diff --git a/src/Internal/Payment/State/AbstractPaymentState.php b/src/Internal/Payment/State/AbstractPaymentState.php index 093daaa8e43..feed2413e96 100644 --- a/src/Internal/Payment/State/AbstractPaymentState.php +++ b/src/Internal/Payment/State/AbstractPaymentState.php @@ -106,7 +106,7 @@ public function start_processing( PaymentRequest $request ) { } /** - * Process all needed verifications. + * Process verified state. * * @psalm-suppress InvalidReturnType * @@ -114,19 +114,20 @@ public function start_processing( PaymentRequest $request ) { * @throws \WCPay\Exceptions\Order_Not_Found_Exception * @throws \WCPay\Internal\Payment\Exception\StateTransitionException */ - public function complete() { + public function process() { $this->throw_unavailable_method_exception( __METHOD__ ); } /** - * Get redirect url. + * Process completed state. * * @psalm-suppress InvalidReturnType * - * @return string + * @return AbstractPaymentState + * @throws \WCPay\Exceptions\Order_Not_Found_Exception * @throws \WCPay\Internal\Payment\Exception\StateTransitionException */ - public function get_authentication_url() { + public function complete() { $this->throw_unavailable_method_exception( __METHOD__ ); } diff --git a/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php b/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php index 0635600fce3..b70a8aa7d1e 100644 --- a/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php +++ b/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php @@ -80,9 +80,22 @@ public function test_create_state_uses_the_factory() { $this->assertSame( $mock_completed_state, $result ); } - public function test_process_throws_exception() { + /** + * @dataProvider state_defined_methods_provider + */ + public function test_state_methods_will_throw_exceptions( $method ) { $this->expectException( StateTransitionException::class ); - $this->expectExceptionMessage( 'The WCPay\Internal\Payment\State\AbstractPaymentState::process method is not available in the current payment state (' . PureState::class . ').' ); - $this->sut->process( $this->createMock( PaymentRequest::class ) ); + $this->expectExceptionMessage( 'The WCPay\Internal\Payment\State\AbstractPaymentState::' . $method . ' method is not available in the current payment state (' . PureState::class . ').' ); + + // Dynamically call the method based on $method. + $this->sut->{$method}( $this->createMock( PaymentRequest::class ) ); + } + + public function state_defined_methods_provider() { + return [ + [ 'start_processing' ], + [ 'process' ], + [ 'complete' ], + ]; } } diff --git a/tests/unit/src/Internal/Payment/State/InitialStateTest.php b/tests/unit/src/Internal/Payment/State/InitialStateTest.php index 6e8a0b3f68c..b192d8e7103 100644 --- a/tests/unit/src/Internal/Payment/State/InitialStateTest.php +++ b/tests/unit/src/Internal/Payment/State/InitialStateTest.php @@ -8,6 +8,9 @@ namespace WCPay\Tests\Internal\Payment\State; use Exception; +use WC_Helper_Intention; +use WCPay\Internal\Payment\State\ProcessedState; +use WCPay\Internal\Payment\State\VerifiedState; use WCPAY_UnitTestCase; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit_Utils; @@ -59,11 +62,6 @@ class InitialStateTest extends WCPAY_UnitTestCase { */ private $mock_level3_service; - /** - * @var PaymentRequestService|MockObject - */ - private $mock_payment_request_service; - /** * @var PaymentContext|MockObject */ @@ -75,48 +73,23 @@ class InitialStateTest extends WCPAY_UnitTestCase { protected function setUp(): void { parent::setUp(); - $this->mock_state_factory = $this->createMock( StateFactory::class ); - $this->mock_order_service = $this->createMock( OrderService::class ); - $this->mock_context = $this->createMock( PaymentContext::class ); - $this->mock_customer_service = $this->createMock( WC_Payments_Customer_Service::class ); - $this->mock_level3_service = $this->createMock( Level3Service::class ); - $this->mock_payment_request_service = $this->createMock( PaymentRequestService::class ); + $this->mock_state_factory = $this->createMock( StateFactory::class ); + $this->mock_order_service = $this->createMock( OrderService::class ); + $this->mock_context = $this->createMock( PaymentContext::class ); + $this->mock_customer_service = $this->createMock( WC_Payments_Customer_Service::class ); + $this->mock_level3_service = $this->createMock( Level3Service::class ); $this->sut = new InitialState( $this->mock_state_factory, $this->mock_order_service, $this->mock_customer_service, - $this->mock_level3_service, - $this->mock_payment_request_service + $this->mock_level3_service ); $this->sut->set_context( $this->mock_context ); } - /** - * Different `process` scenarios. - * - * There will be a single parameter, representing an exception, if any. - * - * @return array - */ - public function provider_process() { - return [ - [ null ], - [ new Invalid_Request_Parameter_Exception( 'A parameter cannot be set.', 'invalid_parameter' ) ], - [ new Extend_Request_Exception( 'A parameter cannot be set.', 'cannot_extend' ) ], - ]; - } - - /** - * Ensures that the `process` method creates a new completed state. - * - * @param \Exception|null $exception Exception that would be thrown by intent creation. - * @dataProvider provider_process - */ - public function test_process( Exception $exception = null ) { - $order_id = 123; + public function test_start_processing() { $mock_request = $this->createMock( PaymentRequest::class ); - $mock_intent = $this->createMock( WC_Payments_API_Payment_Intention::class ); /** * This test works with the root `process` method, which calls a few @@ -132,52 +105,68 @@ public function test_process( Exception $exception = null ) { $this->mock_order_service, $this->mock_customer_service, $this->mock_level3_service, - $this->mock_payment_request_service, ] ) ->getMock(); $this->sut->set_context( $this->mock_context ); - // There's a single call to get the order ID. - $this->mock_context->expects( $this->once() ) + // Mock get order id calls. + $this->mock_context->expects( $this->exactly( 2 ) ) ->method( 'get_order_id' ) - ->willReturn( $order_id ); + ->willReturn( 1 ); // Verify that the context is populated. $this->sut->expects( $this->once() )->method( 'populate_context_from_request' )->with( $mock_request ); $this->sut->expects( $this->once() )->method( 'populate_context_from_order' ); - // Arrange intent creation. - if ( $exception ) { - $this->mock_payment_request_service->expects( $this->once() ) - ->method( 'create_intent' ) - ->with( $this->mock_context ) - ->willThrowException( $exception ); - } else { - $this->mock_payment_request_service->expects( $this->once() ) - ->method( 'create_intent' ) - ->with( $this->mock_context ) - ->willReturn( $mock_intent ); - - // Assert order update. - $this->mock_order_service->expects( $this->once() ) - ->method( 'update_order_from_successful_intent' ) - ->with( $order_id, $mock_intent, $this->mock_context ); - } - - // Arrange the final state. - $state_class = $exception ? SystemErrorState::class : CompletedState::class; - $mock_final_state = $this->createMock( $state_class ); - $this->mock_state_factory->expects( $this->once() ) + // Since the original create_state method is mocked, we have to manually set context. + $this->mock_state_factory->expects( $this->exactly( 3 ) ) ->method( 'create_state' ) - ->with( $state_class, $this->mock_context ) - ->willReturn( $mock_final_state ); - - // Act: Process. - $result = $this->sut->process( $mock_request ); - + ->withConsecutive( + [ VerifiedState::class, $this->mock_context ], + [ ProcessedState::class, $this->mock_context ], + [ CompletedState::class, $this->mock_context ] + ) + ->willReturnOnConsecutiveCalls( + ( function () { + $intent = WC_Helper_Intention::create_intention(); + + $mock_payment_request_service = $this->createMock( PaymentRequestService::class ); + $mock_payment_request_service->expects( $this->once() ) + ->method( 'create_intent' ) + ->with( $this->mock_context ) + ->willReturn( $intent ); + + $this->mock_context->expects( $this->exactly( 2 ) ) + ->method( 'get_intent' ) + ->willReturn( $intent ); + + $verified_state = new VerifiedState( $this->mock_state_factory, $this->mock_order_service, $mock_payment_request_service ); + $verified_state->set_context( $this->mock_context ); + + return $verified_state; + } )(), + ( function () { + $this->mock_order_service->expects( $this->once() ) + ->method( 'update_order_from_successful_intent' ) + ->with( $this->mock_context->get_order_id(), $this->mock_context->get_intent(), $this->mock_context ); + $processed_state = new ProcessedState( $this->mock_state_factory, $this->mock_order_service ); + $processed_state->set_context( $this->mock_context ); + + return $processed_state; + } )(), + ( function () { + $completed_state = new CompletedState( $this->mock_state_factory ); + $completed_state->set_context( $this->mock_context ); + + return $completed_state; + } )() + ); + + // Act: start processing. + $result = $this->sut->start_processing( $mock_request ); // Assert: Successful transition. - $this->assertSame( $mock_final_state, $result ); + $this->assertInstanceOf( CompletedState::class, $result ); } public function test_populate_context_from_request() { @@ -196,7 +185,7 @@ public function test_populate_context_from_request() { $this->mock_context->expects( $this->once() )->method( 'set_cvc_confirmation' )->with( $cvc_confirmation ); $this->mock_context->expects( $this->once() )->method( 'set_fingerprint' )->with( $fingerprint ); - // Use reflection to acces. + // Use reflection to access. PHPUnit_Utils::call_method( $this->sut, 'populate_context_from_request', [ $mock_request ] ); } diff --git a/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php b/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php index 9b993019506..f42d238ba76 100644 --- a/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php +++ b/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php @@ -80,7 +80,7 @@ public function test_process_payment_happy_path() { ->willReturn( $mock_initial_state ); $mock_initial_state->expects( $this->once() ) - ->method( 'process' ) + ->method( 'start_processing' ) ->with( $this->isInstanceOf( PaymentRequest::class ) ) ->willReturn( $mock_completed_state ); @@ -107,7 +107,7 @@ public function test_process_payment_happy_path_without_mock_builder() { ->willReturn( $mock_initial_state ); $mock_initial_state->expects( $this->once() ) - ->method( 'process' ) + ->method( 'start_processing' ) ->with( $this->isInstanceOf( PaymentRequest::class ) ) ->willReturn( $mock_completed_state ); From 8eaa6de4e54e4c74465c6ee781ab5c3b3d9c519a Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 16 Oct 2023 16:35:00 +0200 Subject: [PATCH 23/66] Added tests for VerifiedState --- .../Payment/State/VerifiedStateTest.php | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 tests/unit/src/Internal/Payment/State/VerifiedStateTest.php diff --git a/tests/unit/src/Internal/Payment/State/VerifiedStateTest.php b/tests/unit/src/Internal/Payment/State/VerifiedStateTest.php new file mode 100644 index 00000000000..4ddb33b6b44 --- /dev/null +++ b/tests/unit/src/Internal/Payment/State/VerifiedStateTest.php @@ -0,0 +1,139 @@ +mock_state_factory = $this->createMock( StateFactory::class ); + $this->mock_order_service = $this->createMock( OrderService::class ); + $this->mock_context = $this->createMock( PaymentContext::class ); + $this->mock_payment_request_service = $this->createMock( PaymentRequestService::class ); + + $this->sut = new VerifiedState( + $this->mock_state_factory, + $this->mock_order_service, + $this->mock_payment_request_service + ); + $this->sut->set_context( $this->mock_context ); + } + + public function test_process_will_transition_to_completed_state() { + $this->mock_payment_request_service->expects( $this->once() ) + ->method( 'create_intent' ) + ->with( $this->mock_context ) + ->willReturn( WC_Helper_Intention::create_intention() ); + + $mock_completed_state = $this->createMock( CompletedState::class ); + $mock_processed_state = $this->createMock( ProcessedState::class ); + + // We will mock only transition from verified to processed and mock ProcessedState execution code to avoid nested state change within the test. + $mock_processed_state->expects( $this->once() ) + ->method( 'complete' ) + ->willReturn( $mock_completed_state ); + + $this->mock_state_factory->expects( $this->once() ) + ->method( 'create_state' ) + ->with( ProcessedState::class, $this->mock_context ) + ->willReturn( $mock_processed_state ); + + $result = $this->sut->process(); + + $this->assertSame( $mock_completed_state, $result ); + } + + public function test_process_will_transition_to_error_state_when_api_exception_occurs() { + $this->mock_payment_request_service->expects( $this->once() ) + ->method( 'create_intent' ) + ->with( $this->mock_context ) + ->willThrowException( new Invalid_Request_Parameter_Exception( 'Invalid param', 'invalid_param' ) ); + + $mock_error_state = $this->createMock( SystemErrorState::class ); + $mock_processed_state = $this->createMock( ProcessedState::class ); + + $mock_processed_state->expects( $this->never() ) + ->method( 'complete' ); + + $this->mock_state_factory->expects( $this->once() ) + ->method( 'create_state' ) + ->with( SystemErrorState::class, $this->mock_context ) + ->willReturn( $mock_error_state ); + + $result = $this->sut->process(); + + $this->assertSame( $mock_error_state, $result ); + } + + public function test_process_will_transition_to_auth_required_state() { + + $this->mock_payment_request_service->expects( $this->once() ) + ->method( 'create_intent' ) + ->with( $this->mock_context ) + ->willReturn( WC_Helper_Intention::create_intention( [ 'status' => Intent_Status::REQUIRES_ACTION ] ) ); + + $mock_auth_state = $this->createMock( AuthenticationRequiredState::class ); + $mock_processed_state = $this->createMock( ProcessedState::class ); + + $mock_processed_state->expects( $this->never() ) + ->method( 'complete' ); + + $this->mock_state_factory->expects( $this->once() ) + ->method( 'create_state' ) + ->with( AuthenticationRequiredState::class, $this->mock_context ) + ->willReturn( $mock_auth_state ); + + $result = $this->sut->process(); + + $this->assertSame( $mock_auth_state, $result ); + } +} From 5a60491a7840265c8081ea572ea7a89ce3c43c95 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 16 Oct 2023 16:44:54 +0200 Subject: [PATCH 24/66] Added testes for ProcessedState --- .../Payment/State/ProcessedStateTest.php | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 tests/unit/src/Internal/Payment/State/ProcessedStateTest.php diff --git a/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php b/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php new file mode 100644 index 00000000000..c9bb0bed278 --- /dev/null +++ b/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php @@ -0,0 +1,85 @@ +mock_state_factory = $this->createMock( StateFactory::class ); + $this->mock_order_service = $this->createMock( OrderService::class ); + $this->mock_context = $this->createMock( PaymentContext::class ); + + $this->sut = new ProcessedState( + $this->mock_state_factory, + $this->mock_order_service + ); + $this->sut->set_context( $this->mock_context ); + } + + public function test_complete_will_transition_to_completed_state() { + + $intent = WC_Helper_Intention::create_intention(); + + $this->mock_context->expects( $this->once() ) + ->method( 'get_intent' ) + ->willReturn( $intent ); + + $this->mock_context->expects( $this->once() ) + ->method( 'get_order_id' ) + ->willReturn( 1 ); + + $this->mock_order_service->expects( $this->once() ) + ->method( 'update_order_from_successful_intent' ) + ->with( 1, $intent ); + $processed_state = new ProcessedState( $this->mock_state_factory, $this->mock_order_service ); + + $mock_completed_state = $this->createMock( CompletedState::class ); + + $this->mock_state_factory->expects( $this->once() ) + ->method( 'create_state' ) + ->with( CompletedState::class, $this->mock_context ) + ->willReturn( $mock_completed_state ); + + $result = $this->sut->complete(); + + $this->assertSame( $mock_completed_state, $result ); + } +} From eed546ccd4174c1f3a74822ebc17ddd7d9294ada Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 16 Oct 2023 16:45:38 +0200 Subject: [PATCH 25/66] Removed typo --- tests/unit/src/Internal/Payment/State/ProcessedStateTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php b/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php index c9bb0bed278..33db029e211 100644 --- a/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php +++ b/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php @@ -69,7 +69,6 @@ public function test_complete_will_transition_to_completed_state() { $this->mock_order_service->expects( $this->once() ) ->method( 'update_order_from_successful_intent' ) ->with( 1, $intent ); - $processed_state = new ProcessedState( $this->mock_state_factory, $this->mock_order_service ); $mock_completed_state = $this->createMock( CompletedState::class ); From 79a9bdbe06aaab7fa4ccb9cbd99f318f98526945 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 16 Oct 2023 16:50:08 +0200 Subject: [PATCH 26/66] Removed unused comment --- includes/class-wc-payment-gateway-wcpay.php | 1 - 1 file changed, 1 deletion(-) diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 4eb8b308205..6822eb82656 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -830,7 +830,6 @@ public function new_process_payment( WC_Order $order ) { if ( $state instanceof AuthenticationRequiredState ) { $context = $state->get_context(); - // Maybe we can move this inside the response class and call function like generate_response() that will something like this where we have more control of status code, ... return [ 'result' => 'success', 'redirect' => $service->get_authentication_redirect_url( $context->get_intent(), $context->get_order_id() ), From 6384a340fc72fe88804f4628c447878eb5d25e80 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 16 Oct 2023 16:51:00 +0200 Subject: [PATCH 27/66] Removed unused docblock --- src/Internal/Payment/State/InitialState.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Internal/Payment/State/InitialState.php b/src/Internal/Payment/State/InitialState.php index 7d6fcd8d174..27e762903d6 100644 --- a/src/Internal/Payment/State/InitialState.php +++ b/src/Internal/Payment/State/InitialState.php @@ -81,11 +81,6 @@ public function start_processing( PaymentRequest $request ) { // Populate further details from the order. $this->populate_context_from_order(); - /** - * Next state variable. - * - * @var VerifiedState $next_state - */ $next_state = $this->create_state( VerifiedState::class ); return $next_state->process(); From e32a73ccc66f6ab600621b91d3aa1551c3f2b718 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 16 Oct 2023 16:51:43 +0200 Subject: [PATCH 28/66] Removed unused imports --- src/Internal/Payment/State/InitialState.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Internal/Payment/State/InitialState.php b/src/Internal/Payment/State/InitialState.php index 27e762903d6..da67062f674 100644 --- a/src/Internal/Payment/State/InitialState.php +++ b/src/Internal/Payment/State/InitialState.php @@ -12,7 +12,6 @@ use WCPay\Internal\Payment\Exception\StateTransitionException; use WCPay\Internal\Service\OrderService; use WCPay\Internal\Service\Level3Service; -use WCPay\Internal\Service\PaymentRequestService; use WCPay\Exceptions\Order_Not_Found_Exception; use WCPay\Internal\Payment\PaymentRequest; use WCPay\Internal\Payment\PaymentRequestException; @@ -64,7 +63,7 @@ public function __construct( } /** - * Initialtes the payment process. + * Initiates the payment process. * * @param PaymentRequest $request The incoming payment processing request. * From 106b8c3f8b7a162d3c49ba689151d08edcdea7e4 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 16 Oct 2023 16:56:04 +0200 Subject: [PATCH 29/66] Removed order service dependecy from VerifiedState --- .../ServiceProvider/PaymentsServiceProvider.php | 1 - src/Internal/Payment/State/VerifiedState.php | 12 ------------ .../src/Internal/Payment/State/InitialStateTest.php | 2 +- .../src/Internal/Payment/State/VerifiedStateTest.php | 8 -------- 4 files changed, 1 insertion(+), 22 deletions(-) diff --git a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php index d80b875f99e..b378b4ba88c 100644 --- a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php +++ b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php @@ -77,7 +77,6 @@ public function register(): void { $container->add( VerifiedState::class ) ->addArgument( StateFactory::class ) - ->addArgument( OrderService::class ) ->addArgument( PaymentRequestService::class ); $container->add( ProcessedState::class ) diff --git a/src/Internal/Payment/State/VerifiedState.php b/src/Internal/Payment/State/VerifiedState.php index 387b5faaade..31e98d11669 100644 --- a/src/Internal/Payment/State/VerifiedState.php +++ b/src/Internal/Payment/State/VerifiedState.php @@ -7,12 +7,10 @@ namespace WCPay\Internal\Payment\State; -use WC_Payments_API_Abstract_Intention; use WCPay\Constants\Payment_Intent_Status; use WCPay\Core\Exceptions\Server\Request\Extend_Request_Exception; use WCPay\Core\Exceptions\Server\Request\Immutable_Parameter_Exception; use WCPay\Core\Exceptions\Server\Request\Invalid_Request_Parameter_Exception; -use WCPay\Internal\Service\OrderService; use WCPay\Internal\Service\PaymentRequestService; /** @@ -20,13 +18,6 @@ */ class VerifiedState extends AbstractPaymentState { - /** - * Order service. - * - * @var OrderService - */ - private $order_service; - /** * Payment request service. * @@ -38,17 +29,14 @@ class VerifiedState extends AbstractPaymentState { * Class constructor, only meant for storing dependencies. * * @param StateFactory $state_factory Factory for payment states. - * @param OrderService $order_service Service for order-related actions. * @param PaymentRequestService $payment_request_service Connection with the server. */ public function __construct( StateFactory $state_factory, - OrderService $order_service, PaymentRequestService $payment_request_service ) { parent::__construct( $state_factory ); - $this->order_service = $order_service; $this->payment_request_service = $payment_request_service; } diff --git a/tests/unit/src/Internal/Payment/State/InitialStateTest.php b/tests/unit/src/Internal/Payment/State/InitialStateTest.php index b192d8e7103..34ec6b69ba0 100644 --- a/tests/unit/src/Internal/Payment/State/InitialStateTest.php +++ b/tests/unit/src/Internal/Payment/State/InitialStateTest.php @@ -141,7 +141,7 @@ public function test_start_processing() { ->method( 'get_intent' ) ->willReturn( $intent ); - $verified_state = new VerifiedState( $this->mock_state_factory, $this->mock_order_service, $mock_payment_request_service ); + $verified_state = new VerifiedState( $this->mock_state_factory, $mock_payment_request_service ); $verified_state->set_context( $this->mock_context ); return $verified_state; diff --git a/tests/unit/src/Internal/Payment/State/VerifiedStateTest.php b/tests/unit/src/Internal/Payment/State/VerifiedStateTest.php index 4ddb33b6b44..81b18563320 100644 --- a/tests/unit/src/Internal/Payment/State/VerifiedStateTest.php +++ b/tests/unit/src/Internal/Payment/State/VerifiedStateTest.php @@ -18,7 +18,6 @@ use WCPay\Internal\Payment\State\StateFactory; use WCPay\Internal\Payment\State\SystemErrorState; use WCPay\Internal\Payment\State\VerifiedState; -use WCPay\Internal\Service\OrderService; use WCPay\Internal\Service\PaymentRequestService; use WCPAY_UnitTestCase; @@ -38,11 +37,6 @@ class VerifiedStateTest extends WCPAY_UnitTestCase { */ private $mock_state_factory; - /** - * @var OrderService|MockObject - */ - private $mock_order_service; - /** * @var PaymentRequestService|MockObject */ @@ -56,13 +50,11 @@ protected function setUp(): void { parent::setUp(); $this->mock_state_factory = $this->createMock( StateFactory::class ); - $this->mock_order_service = $this->createMock( OrderService::class ); $this->mock_context = $this->createMock( PaymentContext::class ); $this->mock_payment_request_service = $this->createMock( PaymentRequestService::class ); $this->sut = new VerifiedState( $this->mock_state_factory, - $this->mock_order_service, $this->mock_payment_request_service ); $this->sut->set_context( $this->mock_context ); From b8e438b659a12b0129986884fd9e59cb8c9016f3 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 16 Oct 2023 16:57:56 +0200 Subject: [PATCH 30/66] Added empty line --- src/Internal/Payment/State/ProcessedState.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Internal/Payment/State/ProcessedState.php b/src/Internal/Payment/State/ProcessedState.php index 1ff6746657b..b8fdeaa597a 100644 --- a/src/Internal/Payment/State/ProcessedState.php +++ b/src/Internal/Payment/State/ProcessedState.php @@ -52,6 +52,7 @@ public function __construct( */ public function complete() { $context = $this->get_context(); + // Complete processing. $this->order_service->update_order_from_successful_intent( $context->get_order_id(), $context->get_intent(), $context ); From d9b28ba0a8ec8bca5a2092bdceca924907b785de Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 16 Oct 2023 16:59:15 +0200 Subject: [PATCH 31/66] Updated comment on state --- src/Internal/Payment/State/AuthenticationRequiredState.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Internal/Payment/State/AuthenticationRequiredState.php b/src/Internal/Payment/State/AuthenticationRequiredState.php index c42b0c2fe58..b32b71a8e1e 100644 --- a/src/Internal/Payment/State/AuthenticationRequiredState.php +++ b/src/Internal/Payment/State/AuthenticationRequiredState.php @@ -7,9 +7,8 @@ namespace WCPay\Internal\Payment\State; -use WCPay\Internal\Proxy\LegacyProxy; /** - * The state, which indicates that the payment processing has been completed. + * The state, which indicates that the payment requires authentication (3DS). */ class AuthenticationRequiredState extends AbstractPaymentState { } From 4ac98dc77fd09dfbe0a6491c765209e0a7c18e1d Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 16 Oct 2023 17:06:34 +0200 Subject: [PATCH 32/66] Added class check to determinate redirect url prefix. --- src/Internal/Service/PaymentProcessingService.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Internal/Service/PaymentProcessingService.php b/src/Internal/Service/PaymentProcessingService.php index 9cd61e85bf9..3da349201c6 100644 --- a/src/Internal/Service/PaymentProcessingService.php +++ b/src/Internal/Service/PaymentProcessingService.php @@ -7,10 +7,9 @@ namespace WCPay\Internal\Service; -use Exception; - // Temporary exception! This service would have its own exception when more business logics are added. use WC_Payments_API_Abstract_Intention; +use WC_Payments_API_Setup_Intention; use WCPay\Vendor\League\Container\Exception\ContainerException; use WCPay\Internal\Payment\PaymentContext; use WCPay\Internal\Payment\State\InitialState; @@ -58,7 +57,6 @@ public function __construct( * @param int $order_id Order ID provided by WooCommerce core. * @param bool $manual_capture Whether to only create an authorization instead of a charge (optional). * - * @throws Exception * @throws StateTransitionException In case a state cannot be initialized. * @throws PaymentRequestException When the request is malformed. This should be converted to a failure state. * @throws ContainerException When the dependency container cannot instantiate the state. @@ -118,7 +116,7 @@ public function get_authentication_redirect_url( $intent, int $order_id ) { return sprintf( '#wcpay-confirm-%s:%s:%s:%s', - substr( $intent->get_id(), 0, 2 ), // intents starts with pi_ or si_ so we need only to first two letters. + $intent instanceof WC_Payments_API_Setup_Intention ? 'si' : 'pi', $order_id, $client_secret, $this->legacy_proxy->call_function( 'wp_create_nonce', 'wcpay_update_order_status_nonce' ) From f3f8e5e6d37470e71bf606af1bee06d4dc532913 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 16 Oct 2023 17:25:34 +0200 Subject: [PATCH 33/66] Fixed PSALM errors --- src/Internal/Service/PaymentProcessingService.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Internal/Service/PaymentProcessingService.php b/src/Internal/Service/PaymentProcessingService.php index 3da349201c6..1504c567191 100644 --- a/src/Internal/Service/PaymentProcessingService.php +++ b/src/Internal/Service/PaymentProcessingService.php @@ -10,6 +10,7 @@ // Temporary exception! This service would have its own exception when more business logics are added. use WC_Payments_API_Abstract_Intention; use WC_Payments_API_Setup_Intention; +use WCPay\Exceptions\Order_Not_Found_Exception; use WCPay\Vendor\League\Container\Exception\ContainerException; use WCPay\Internal\Payment\PaymentContext; use WCPay\Internal\Payment\State\InitialState; @@ -57,9 +58,10 @@ public function __construct( * @param int $order_id Order ID provided by WooCommerce core. * @param bool $manual_capture Whether to only create an authorization instead of a charge (optional). * - * @throws StateTransitionException In case a state cannot be initialized. - * @throws PaymentRequestException When the request is malformed. This should be converted to a failure state. - * @throws ContainerException When the dependency container cannot instantiate the state. + * @throws StateTransitionException In case a state cannot be initialized. + * @throws PaymentRequestException When the request is malformed. This should be converted to a failure state. + * @throws Order_Not_Found_Exception When order is not found. + * @throws ContainerException When the dependency container cannot instantiate the state. */ public function process_payment( int $order_id, bool $manual_capture = false ) { // Start with a basis context. From 48faef115909a536ede39ad8bee130d687e234a0 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 16 Oct 2023 17:28:14 +0200 Subject: [PATCH 34/66] Replaced dockblock excpetions with use statments --- src/Internal/Payment/State/VerifiedState.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Internal/Payment/State/VerifiedState.php b/src/Internal/Payment/State/VerifiedState.php index 31e98d11669..bd5386e7778 100644 --- a/src/Internal/Payment/State/VerifiedState.php +++ b/src/Internal/Payment/State/VerifiedState.php @@ -11,7 +11,10 @@ use WCPay\Core\Exceptions\Server\Request\Extend_Request_Exception; use WCPay\Core\Exceptions\Server\Request\Immutable_Parameter_Exception; use WCPay\Core\Exceptions\Server\Request\Invalid_Request_Parameter_Exception; +use WCPay\Exceptions\Order_Not_Found_Exception; +use WCPay\Internal\Payment\Exception\StateTransitionException; use WCPay\Internal\Service\PaymentRequestService; +use WCPay\Vendor\League\Container\Exception\ContainerException; /** * This state is used to create payment intent and verify next actions based on the intent state. @@ -45,9 +48,9 @@ public function __construct( * * @return AbstractPaymentState * - * @throws \WCPay\Exceptions\Order_Not_Found_Exception - * @throws \WCPay\Internal\Payment\Exception\StateTransitionException - * @throws \WCPay\Vendor\League\Container\Exception\ContainerException + * @throws Order_Not_Found_Exception + * @throws StateTransitionException + * @throws ContainerException */ public function process() { $context = $this->get_context(); From 818a8f45dc2ce50316e4da9c6eb93e1716340ad9 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 16 Oct 2023 17:33:02 +0200 Subject: [PATCH 35/66] Used better intent status constant --- src/Internal/Payment/State/VerifiedState.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Internal/Payment/State/VerifiedState.php b/src/Internal/Payment/State/VerifiedState.php index bd5386e7778..de3af60980b 100644 --- a/src/Internal/Payment/State/VerifiedState.php +++ b/src/Internal/Payment/State/VerifiedState.php @@ -7,7 +7,7 @@ namespace WCPay\Internal\Payment\State; -use WCPay\Constants\Payment_Intent_Status; +use WCPay\Constants\Intent_Status; use WCPay\Core\Exceptions\Server\Request\Extend_Request_Exception; use WCPay\Core\Exceptions\Server\Request\Immutable_Parameter_Exception; use WCPay\Core\Exceptions\Server\Request\Invalid_Request_Parameter_Exception; @@ -64,7 +64,7 @@ public function process() { } // Intent requires authorization (3DS check). - if ( Payment_Intent_Status::REQUIRES_ACTION === $intent->get_status() ) { + if ( Intent_Status::REQUIRES_ACTION === $intent->get_status() ) { return $this->create_state( AuthenticationRequiredState::class ); } From af8b3c80eeeebc8ab3a310b3e9a1cfd4a30ca747 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 16 Oct 2023 17:34:31 +0200 Subject: [PATCH 36/66] Cleaned ProcessedState class --- src/Internal/Payment/State/ProcessedState.php | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Internal/Payment/State/ProcessedState.php b/src/Internal/Payment/State/ProcessedState.php index b8fdeaa597a..b08e4da72f9 100644 --- a/src/Internal/Payment/State/ProcessedState.php +++ b/src/Internal/Payment/State/ProcessedState.php @@ -7,18 +7,15 @@ namespace WCPay\Internal\Payment\State; -use WC_Payments_API_Payment_Intention; -use WCPay\Core\Exceptions\Server\Request\Extend_Request_Exception; -use WCPay\Core\Exceptions\Server\Request\Immutable_Parameter_Exception; -use WCPay\Core\Exceptions\Server\Request\Invalid_Request_Parameter_Exception; +use WCPay\Exceptions\Order_Not_Found_Exception; +use WCPay\Internal\Payment\Exception\StateTransitionException; use WCPay\Internal\Service\OrderService; -use WCPay\Internal\Service\PaymentRequestService; +use WCPay\Vendor\League\Container\Exception\ContainerException; /** * This state is used when payment is completed on the server, and we need to update date on the plugin side. */ class ProcessedState extends AbstractPaymentState { - /** * Order service. * @@ -26,7 +23,6 @@ class ProcessedState extends AbstractPaymentState { */ private $order_service; - /** * Class constructor, only meant for storing dependencies. * @@ -46,9 +42,9 @@ public function __construct( * Process all needed verifications. * * @return AbstractPaymentState - * @throws \WCPay\Exceptions\Order_Not_Found_Exception - * @throws \WCPay\Internal\Payment\Exception\StateTransitionException - * @throws \WCPay\Vendor\League\Container\Exception\ContainerException + * @throws Order_Not_Found_Exception + * @throws StateTransitionException + * @throws ContainerException */ public function complete() { $context = $this->get_context(); From 6f370c18f713924970eff8e885339b9cbd01086f Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 16 Oct 2023 17:34:58 +0200 Subject: [PATCH 37/66] Removed unused comment --- src/Internal/Service/PaymentProcessingService.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Internal/Service/PaymentProcessingService.php b/src/Internal/Service/PaymentProcessingService.php index 1504c567191..7d0e7466747 100644 --- a/src/Internal/Service/PaymentProcessingService.php +++ b/src/Internal/Service/PaymentProcessingService.php @@ -7,7 +7,6 @@ namespace WCPay\Internal\Service; -// Temporary exception! This service would have its own exception when more business logics are added. use WC_Payments_API_Abstract_Intention; use WC_Payments_API_Setup_Intention; use WCPay\Exceptions\Order_Not_Found_Exception; From 93e856ca0614cbaf4302fe8aaa0cd9efa5df7b18 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 16 Oct 2023 18:34:27 +0200 Subject: [PATCH 38/66] Added missing tests --- .../Internal/Payment/PaymentContextTest.php | 8 ++ .../Service/PaymentProcessingServiceTest.php | 81 +++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/tests/unit/src/Internal/Payment/PaymentContextTest.php b/tests/unit/src/Internal/Payment/PaymentContextTest.php index 71ed252cfcf..7ba3247f4c7 100644 --- a/tests/unit/src/Internal/Payment/PaymentContextTest.php +++ b/tests/unit/src/Internal/Payment/PaymentContextTest.php @@ -7,6 +7,7 @@ namespace WCPay\Tests\Internal\Payment; +use WC_Helper_Intention; use WCPAY_UnitTestCase; use WCPay\Internal\Payment\PaymentContext; use WCPay\Internal\Payment\PaymentMethod\NewPaymentMethod; @@ -117,4 +118,11 @@ public function test_customer_id() { $this->sut->set_customer_id( $customer_id ); $this->assertSame( $customer_id, $this->sut->get_customer_id() ); } + + public function test_intent() { + $intent = WC_Helper_Intention::create_intention(); + + $this->sut->set_intent( $intent ); + $this->assertSame( $intent, $this->sut->get_intent() ); + } } diff --git a/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php b/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php index f42d238ba76..f67a44dd588 100644 --- a/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php +++ b/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php @@ -8,7 +8,9 @@ namespace WCPay\Tests\Internal\Service; use PHPUnit\Framework\MockObject\MockObject; +use WC_Helper_Intention; use WC_Payment_Gateway_WCPay; +use WCPay\Constants\Intent_Status; use WCPay\Internal\Payment\PaymentContext; use WCPay\Internal\Payment\PaymentRequest; use WCPay\Internal\Payment\State\CompletedState; @@ -114,4 +116,83 @@ public function test_process_payment_happy_path_without_mock_builder() { $result = $sut->process_payment( 1 ); $this->assertSame( $mock_completed_state, $result ); } + + public function test_get_authentication_redirect_url_will_return_url_from_payment_intent() { + $sut = new PaymentProcessingService( $this->mock_state_factory, $this->mock_legacy_proxy ); + + $url = 'localhost'; + $intent_data = [ + 'next_action' => [ + 'type' => 'redirect_to_url', + 'redirect_to_url' => [ + 'url' => $url, + ], + ], + 'status' => Intent_Status::REQUIRES_CAPTURE, + ]; + $intent = WC_Helper_Intention::create_intention( $intent_data ); + + $this->mock_legacy_proxy->expects( $this->never() ) + ->method( 'call_static' ); + + $this->mock_legacy_proxy->expects( $this->never() ) + ->method( 'call_function' ); + + $result = $sut->get_authentication_redirect_url( $intent, 1 ); + $this->assertSame( $url, $result ); + + } + + public function test_get_authentication_redirect_url_will_return_url_with_encrypted_secret_key() { + $sut = new PaymentProcessingService( $this->mock_state_factory, $this->mock_legacy_proxy ); + + $intent = WC_Helper_Intention::create_intention(); + $secret = 'encrypted_secret'; // Dummy text to avoid calling crypt function. + $nonce = 'nonce'; // Return of nonce function when called from legacy proxy. + $order = 1; // Order id. + + $this->mock_legacy_proxy->expects( $this->once() ) + ->method( 'call_static' ) + ->with( \WC_Payments_Features::class, 'is_client_secret_encryption_enabled' ) + ->willReturn( true ); + + $this->mock_legacy_proxy->expects( $this->exactly( 2 ) ) + ->method( 'call_function' ) + ->withConsecutive( + [ + 'openssl_encrypt', + $intent->get_client_secret(), + 'aes-128-cbc', + substr( $intent->get_customer_id(), 5 ), + 0, + str_repeat( 'WC', 8 ), + ], + [ 'wp_create_nonce', 'wcpay_update_order_status_nonce' ] + ) + ->willReturnOnConsecutiveCalls( $secret, $nonce ); + $result = $sut->get_authentication_redirect_url( $intent, $order ); + $this->assertSame( "#wcpay-confirm-pi:$order:$secret:$nonce", $result ); + } + + public function test_get_authentication_redirect_url_will_return_url_with_secret_and_si_prefix() { + $sut = new PaymentProcessingService( $this->mock_state_factory, $this->mock_legacy_proxy ); + + $intent = WC_Helper_Intention::create_setup_intention(); + $nonce = 'nonce'; // Return of nonce function when called from legacy proxy. + $order = 1; // Order id. + + $this->mock_legacy_proxy->expects( $this->once() ) + ->method( 'call_static' ) + ->with( \WC_Payments_Features::class, 'is_client_secret_encryption_enabled' ) + ->willReturn( false ); // Just to test when encryption is disabled. + + $this->mock_legacy_proxy->expects( $this->once() ) + ->method( 'call_function' ) + ->with( 'wp_create_nonce', 'wcpay_update_order_status_nonce' ) + ->willReturn( $nonce ); + $result = $sut->get_authentication_redirect_url( $intent, $order ); + $this->assertSame( "#wcpay-confirm-si:$order:" . $intent->get_client_secret() . ":$nonce", $result ); + } + + } From 7765bee4c4e6a445879d0019d080de8be2660c52 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Tue, 17 Oct 2023 12:46:15 +0200 Subject: [PATCH 39/66] Removed VerifiedState --- .../PaymentsServiceProvider.php | 6 +- src/Internal/Payment/State/InitialState.php | 43 +++++- src/Internal/Payment/State/VerifiedState.php | 75 ---------- .../Payment/State/InitialStateTest.php | 128 ++++++++++++----- .../Payment/State/VerifiedStateTest.php | 131 ------------------ 5 files changed, 128 insertions(+), 255 deletions(-) delete mode 100644 src/Internal/Payment/State/VerifiedState.php delete mode 100644 tests/unit/src/Internal/Payment/State/VerifiedStateTest.php diff --git a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php index b378b4ba88c..751e2a94959 100644 --- a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php +++ b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php @@ -44,7 +44,6 @@ class PaymentsServiceProvider extends AbstractServiceProvider { Router::class, StateFactory::class, InitialState::class, - VerifiedState::class, ProcessedState::class, CompletedState::class, SystemErrorState::class, @@ -73,10 +72,7 @@ public function register(): void { ->addArgument( StateFactory::class ) ->addArgument( OrderService::class ) ->addArgument( WC_Payments_Customer_Service::class ) - ->addArgument( Level3Service::class ); - - $container->add( VerifiedState::class ) - ->addArgument( StateFactory::class ) + ->addArgument( Level3Service::class ) ->addArgument( PaymentRequestService::class ); $container->add( ProcessedState::class ) diff --git a/src/Internal/Payment/State/InitialState.php b/src/Internal/Payment/State/InitialState.php index da67062f674..5b2b60de668 100644 --- a/src/Internal/Payment/State/InitialState.php +++ b/src/Internal/Payment/State/InitialState.php @@ -8,6 +8,11 @@ namespace WCPay\Internal\Payment\State; use WC_Payments_Customer_Service; +use WCPay\Constants\Intent_Status; +use WCPay\Core\Exceptions\Server\Request\Extend_Request_Exception; +use WCPay\Core\Exceptions\Server\Request\Immutable_Parameter_Exception; +use WCPay\Core\Exceptions\Server\Request\Invalid_Request_Parameter_Exception; +use WCPay\Internal\Service\PaymentRequestService; use WCPay\Vendor\League\Container\Exception\ContainerException; use WCPay\Internal\Payment\Exception\StateTransitionException; use WCPay\Internal\Service\OrderService; @@ -41,6 +46,13 @@ class InitialState extends AbstractPaymentState { */ private $level3_service; + /** + * Payment request service. + * + * @var PaymentRequestService + */ + private $payment_request_service; + /** * Class constructor, only meant for storing dependencies. * @@ -48,18 +60,21 @@ class InitialState extends AbstractPaymentState { * @param OrderService $order_service Service for order-related actions. * @param WC_Payments_Customer_Service $customer_service Service for managing remote customers. * @param Level3Service $level3_service Service for Level3 Data. + * @param PaymentRequestService $payment_request_service Connection with the server. */ public function __construct( StateFactory $state_factory, OrderService $order_service, WC_Payments_Customer_Service $customer_service, - Level3Service $level3_service + Level3Service $level3_service, + PaymentRequestService $payment_request_service ) { parent::__construct( $state_factory ); - $this->order_service = $order_service; - $this->customer_service = $customer_service; - $this->level3_service = $level3_service; + $this->order_service = $order_service; + $this->customer_service = $customer_service; + $this->level3_service = $level3_service; + $this->payment_request_service = $payment_request_service; } /** @@ -80,9 +95,25 @@ public function start_processing( PaymentRequest $request ) { // Populate further details from the order. $this->populate_context_from_order(); - $next_state = $this->create_state( VerifiedState::class ); + $context = $this->get_context(); + + // Payments are currently based on intents, request one from the API. + try { + $intent = $this->payment_request_service->create_intent( $context ); + $context->set_intent( $intent ); + } catch ( Invalid_Request_Parameter_Exception | Extend_Request_Exception | Immutable_Parameter_Exception $e ) { + return $this->create_state( SystemErrorState::class ); + } + + // Intent requires authorization (3DS check). + if ( Intent_Status::REQUIRES_ACTION === $intent->get_status() ) { + return $this->create_state( AuthenticationRequiredState::class ); + } + + // All good. Proceed to processed state. + $next_state = $this->create_state( ProcessedState::class ); - return $next_state->process(); + return $next_state->complete(); } /** diff --git a/src/Internal/Payment/State/VerifiedState.php b/src/Internal/Payment/State/VerifiedState.php deleted file mode 100644 index de3af60980b..00000000000 --- a/src/Internal/Payment/State/VerifiedState.php +++ /dev/null @@ -1,75 +0,0 @@ -payment_request_service = $payment_request_service; - } - - /** - * Process all needed verifications. - * - * @return AbstractPaymentState - * - * @throws Order_Not_Found_Exception - * @throws StateTransitionException - * @throws ContainerException - */ - public function process() { - $context = $this->get_context(); - - // Payments are currently based on intents, request one from the API. - try { - $intent = $this->payment_request_service->create_intent( $context ); - $context->set_intent( $intent ); - } catch ( Invalid_Request_Parameter_Exception | Extend_Request_Exception | Immutable_Parameter_Exception $e ) { - return $this->create_state( SystemErrorState::class ); - } - - // Intent requires authorization (3DS check). - if ( Intent_Status::REQUIRES_ACTION === $intent->get_status() ) { - return $this->create_state( AuthenticationRequiredState::class ); - } - - // All good. Proceed to processed state. - $next_state = $this->create_state( ProcessedState::class ); - return $next_state->complete(); - } -} diff --git a/tests/unit/src/Internal/Payment/State/InitialStateTest.php b/tests/unit/src/Internal/Payment/State/InitialStateTest.php index 34ec6b69ba0..f21700c7a99 100644 --- a/tests/unit/src/Internal/Payment/State/InitialStateTest.php +++ b/tests/unit/src/Internal/Payment/State/InitialStateTest.php @@ -9,6 +9,8 @@ use Exception; use WC_Helper_Intention; +use WCPay\Constants\Intent_Status; +use WCPay\Internal\Payment\State\AuthenticationRequiredState; use WCPay\Internal\Payment\State\ProcessedState; use WCPay\Internal\Payment\State\VerifiedState; use WCPAY_UnitTestCase; @@ -60,6 +62,11 @@ class InitialStateTest extends WCPAY_UnitTestCase { /** * @var Level3Service|MockObject */ + private $mock_payment_request_service; + + /** + * @var PaymentRequestService|MockObject + */ private $mock_level3_service; /** @@ -73,17 +80,19 @@ class InitialStateTest extends WCPAY_UnitTestCase { protected function setUp(): void { parent::setUp(); - $this->mock_state_factory = $this->createMock( StateFactory::class ); - $this->mock_order_service = $this->createMock( OrderService::class ); - $this->mock_context = $this->createMock( PaymentContext::class ); - $this->mock_customer_service = $this->createMock( WC_Payments_Customer_Service::class ); - $this->mock_level3_service = $this->createMock( Level3Service::class ); + $this->mock_state_factory = $this->createMock( StateFactory::class ); + $this->mock_order_service = $this->createMock( OrderService::class ); + $this->mock_context = $this->createMock( PaymentContext::class ); + $this->mock_customer_service = $this->createMock( WC_Payments_Customer_Service::class ); + $this->mock_level3_service = $this->createMock( Level3Service::class ); + $this->mock_payment_request_service = $this->createMock( PaymentRequestService::class ); $this->sut = new InitialState( $this->mock_state_factory, $this->mock_order_service, $this->mock_customer_service, - $this->mock_level3_service + $this->mock_level3_service, + $this->mock_payment_request_service ); $this->sut->set_context( $this->mock_context ); } @@ -97,18 +106,7 @@ public function test_start_processing() { * * @var MockObject|InitialState */ - $this->sut = $this->getMockBuilder( InitialState::class ) - ->onlyMethods( [ 'populate_context_from_request', 'populate_context_from_order' ] ) - ->setConstructorArgs( - [ - $this->mock_state_factory, - $this->mock_order_service, - $this->mock_customer_service, - $this->mock_level3_service, - ] - ) - ->getMock(); - $this->sut->set_context( $this->mock_context ); + $this->mock_initial_state(); // Mock get order id calls. $this->mock_context->expects( $this->exactly( 2 ) ) @@ -119,33 +117,26 @@ public function test_start_processing() { $this->sut->expects( $this->once() )->method( 'populate_context_from_request' )->with( $mock_request ); $this->sut->expects( $this->once() )->method( 'populate_context_from_order' ); + $intent = WC_Helper_Intention::create_intention(); + + $this->mock_payment_request_service + ->expects( $this->once() ) + ->method( 'create_intent' ) + ->with( $this->mock_context ) + ->willReturn( $intent ); + + $this->mock_context->expects( $this->exactly( 2 ) ) + ->method( 'get_intent' ) + ->willReturn( $intent ); + // Since the original create_state method is mocked, we have to manually set context. - $this->mock_state_factory->expects( $this->exactly( 3 ) ) + $this->mock_state_factory->expects( $this->exactly( 2 ) ) ->method( 'create_state' ) ->withConsecutive( - [ VerifiedState::class, $this->mock_context ], [ ProcessedState::class, $this->mock_context ], [ CompletedState::class, $this->mock_context ] ) ->willReturnOnConsecutiveCalls( - ( function () { - $intent = WC_Helper_Intention::create_intention(); - - $mock_payment_request_service = $this->createMock( PaymentRequestService::class ); - $mock_payment_request_service->expects( $this->once() ) - ->method( 'create_intent' ) - ->with( $this->mock_context ) - ->willReturn( $intent ); - - $this->mock_context->expects( $this->exactly( 2 ) ) - ->method( 'get_intent' ) - ->willReturn( $intent ); - - $verified_state = new VerifiedState( $this->mock_state_factory, $mock_payment_request_service ); - $verified_state->set_context( $this->mock_context ); - - return $verified_state; - } )(), ( function () { $this->mock_order_service->expects( $this->once() ) ->method( 'update_order_from_successful_intent' ) @@ -169,6 +160,51 @@ public function test_start_processing() { $this->assertInstanceOf( CompletedState::class, $result ); } + public function test_start_processing_will_transition_to_error_state_when_api_exception_occurs() { + $mock_request = $this->createMock( PaymentRequest::class ); + $mock_error_state = $this->createMock( SystemErrorState::class ); + $this->mock_initial_state(); + + $this->mock_payment_request_service + ->expects( $this->once() ) + ->method( 'create_intent' ) + ->with( $this->mock_context ) + ->willThrowException( new Invalid_Request_Parameter_Exception( 'Invalid param', 'invalid_param' ) ); + + // Let's mock these services in order to prevent real execution of them. + $this->sut->expects( $this->once() )->method( 'populate_context_from_request' )->with( $mock_request ); + $this->sut->expects( $this->once() )->method( 'populate_context_from_order' ); + + $this->mock_state_factory->expects( $this->once() ) + ->method( 'create_state' ) + ->with( SystemErrorState::class, $this->mock_context ) + ->willReturn( $mock_error_state ); + $result = $this->sut->start_processing( $mock_request ); + $this->assertSame( $mock_error_state, $result ); + } + + public function test_processing_will_transition_to_auth_required_state() { + $mock_request = $this->createMock( PaymentRequest::class ); + $mock_auth_state = $this->createMock( AuthenticationRequiredState::class ); + $this->mock_initial_state(); + + $this->mock_payment_request_service->expects( $this->once() ) + ->method( 'create_intent' ) + ->with( $this->mock_context ) + ->willReturn( WC_Helper_Intention::create_intention( [ 'status' => Intent_Status::REQUIRES_ACTION ] ) ); + + // Let's mock these services in order to prevent real execution of them. + $this->sut->expects( $this->once() )->method( 'populate_context_from_request' )->with( $mock_request ); + $this->sut->expects( $this->once() )->method( 'populate_context_from_order' ); + + $this->mock_state_factory->expects( $this->once() ) + ->method( 'create_state' ) + ->with( AuthenticationRequiredState::class, $this->mock_context ) + ->willReturn( $mock_auth_state ); + $result = $this->sut->start_processing( $mock_request ); + $this->assertSame( $mock_auth_state, $result ); + } + public function test_populate_context_from_request() { $payment_method = new NewPaymentMethod( 'pm_123' ); $fingerprint = 'fingerprint'; @@ -243,4 +279,20 @@ public function test_populate_context_from_order() { PHPUnit_Utils::call_method( $this->sut, 'populate_context_from_order', [] ); } + + private function mock_initial_state() { + $this->sut = $this->getMockBuilder( InitialState::class ) + ->onlyMethods( [ 'populate_context_from_request', 'populate_context_from_order' ] ) + ->setConstructorArgs( + [ + $this->mock_state_factory, + $this->mock_order_service, + $this->mock_customer_service, + $this->mock_level3_service, + $this->mock_payment_request_service, + ] + ) + ->getMock(); + $this->sut->set_context( $this->mock_context ); + } } diff --git a/tests/unit/src/Internal/Payment/State/VerifiedStateTest.php b/tests/unit/src/Internal/Payment/State/VerifiedStateTest.php deleted file mode 100644 index 81b18563320..00000000000 --- a/tests/unit/src/Internal/Payment/State/VerifiedStateTest.php +++ /dev/null @@ -1,131 +0,0 @@ -mock_state_factory = $this->createMock( StateFactory::class ); - $this->mock_context = $this->createMock( PaymentContext::class ); - $this->mock_payment_request_service = $this->createMock( PaymentRequestService::class ); - - $this->sut = new VerifiedState( - $this->mock_state_factory, - $this->mock_payment_request_service - ); - $this->sut->set_context( $this->mock_context ); - } - - public function test_process_will_transition_to_completed_state() { - $this->mock_payment_request_service->expects( $this->once() ) - ->method( 'create_intent' ) - ->with( $this->mock_context ) - ->willReturn( WC_Helper_Intention::create_intention() ); - - $mock_completed_state = $this->createMock( CompletedState::class ); - $mock_processed_state = $this->createMock( ProcessedState::class ); - - // We will mock only transition from verified to processed and mock ProcessedState execution code to avoid nested state change within the test. - $mock_processed_state->expects( $this->once() ) - ->method( 'complete' ) - ->willReturn( $mock_completed_state ); - - $this->mock_state_factory->expects( $this->once() ) - ->method( 'create_state' ) - ->with( ProcessedState::class, $this->mock_context ) - ->willReturn( $mock_processed_state ); - - $result = $this->sut->process(); - - $this->assertSame( $mock_completed_state, $result ); - } - - public function test_process_will_transition_to_error_state_when_api_exception_occurs() { - $this->mock_payment_request_service->expects( $this->once() ) - ->method( 'create_intent' ) - ->with( $this->mock_context ) - ->willThrowException( new Invalid_Request_Parameter_Exception( 'Invalid param', 'invalid_param' ) ); - - $mock_error_state = $this->createMock( SystemErrorState::class ); - $mock_processed_state = $this->createMock( ProcessedState::class ); - - $mock_processed_state->expects( $this->never() ) - ->method( 'complete' ); - - $this->mock_state_factory->expects( $this->once() ) - ->method( 'create_state' ) - ->with( SystemErrorState::class, $this->mock_context ) - ->willReturn( $mock_error_state ); - - $result = $this->sut->process(); - - $this->assertSame( $mock_error_state, $result ); - } - - public function test_process_will_transition_to_auth_required_state() { - - $this->mock_payment_request_service->expects( $this->once() ) - ->method( 'create_intent' ) - ->with( $this->mock_context ) - ->willReturn( WC_Helper_Intention::create_intention( [ 'status' => Intent_Status::REQUIRES_ACTION ] ) ); - - $mock_auth_state = $this->createMock( AuthenticationRequiredState::class ); - $mock_processed_state = $this->createMock( ProcessedState::class ); - - $mock_processed_state->expects( $this->never() ) - ->method( 'complete' ); - - $this->mock_state_factory->expects( $this->once() ) - ->method( 'create_state' ) - ->with( AuthenticationRequiredState::class, $this->mock_context ) - ->willReturn( $mock_auth_state ); - - $result = $this->sut->process(); - - $this->assertSame( $mock_auth_state, $result ); - } -} From 776c96fa45b2f0da36dd2483b2d60a645bf04abf Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Tue, 17 Oct 2023 12:59:23 +0200 Subject: [PATCH 40/66] Simplifed test for InitialState --- .../Payment/State/InitialStateTest.php | 44 ++++--------------- 1 file changed, 9 insertions(+), 35 deletions(-) diff --git a/tests/unit/src/Internal/Payment/State/InitialStateTest.php b/tests/unit/src/Internal/Payment/State/InitialStateTest.php index f21700c7a99..a9597fbe6e2 100644 --- a/tests/unit/src/Internal/Payment/State/InitialStateTest.php +++ b/tests/unit/src/Internal/Payment/State/InitialStateTest.php @@ -7,7 +7,6 @@ namespace WCPay\Tests\Internal\Payment\State; -use Exception; use WC_Helper_Intention; use WCPay\Constants\Intent_Status; use WCPay\Internal\Payment\State\AuthenticationRequiredState; @@ -16,11 +15,8 @@ use WCPAY_UnitTestCase; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit_Utils; -use ReflectionClass; use WC_Order; -use WC_Payments_API_Payment_Intention; use WC_Payments_Customer_Service; -use WCPay\Core\Exceptions\Server\Request\Extend_Request_Exception; use WCPay\Core\Exceptions\Server\Request\Invalid_Request_Parameter_Exception; use WCPay\Internal\Payment\State\InitialState; use WCPay\Internal\Payment\State\StateFactory; @@ -107,11 +103,12 @@ public function test_start_processing() { * @var MockObject|InitialState */ $this->mock_initial_state(); + $mock_processed_state = $this->createMock( ProcessedState::class ); + $mock_completed_state = $this->createMock( CompletedState::class ); - // Mock get order id calls. - $this->mock_context->expects( $this->exactly( 2 ) ) - ->method( 'get_order_id' ) - ->willReturn( 1 ); + $mock_processed_state->expects( $this->once() ) + ->method( 'complete' ) + ->willReturn( $mock_completed_state ); // Verify that the context is populated. $this->sut->expects( $this->once() )->method( 'populate_context_from_request' )->with( $mock_request ); @@ -125,39 +122,16 @@ public function test_start_processing() { ->with( $this->mock_context ) ->willReturn( $intent ); - $this->mock_context->expects( $this->exactly( 2 ) ) - ->method( 'get_intent' ) - ->willReturn( $intent ); - // Since the original create_state method is mocked, we have to manually set context. - $this->mock_state_factory->expects( $this->exactly( 2 ) ) + $this->mock_state_factory->expects( $this->once() ) ->method( 'create_state' ) - ->withConsecutive( - [ ProcessedState::class, $this->mock_context ], - [ CompletedState::class, $this->mock_context ] - ) - ->willReturnOnConsecutiveCalls( - ( function () { - $this->mock_order_service->expects( $this->once() ) - ->method( 'update_order_from_successful_intent' ) - ->with( $this->mock_context->get_order_id(), $this->mock_context->get_intent(), $this->mock_context ); - $processed_state = new ProcessedState( $this->mock_state_factory, $this->mock_order_service ); - $processed_state->set_context( $this->mock_context ); - - return $processed_state; - } )(), - ( function () { - $completed_state = new CompletedState( $this->mock_state_factory ); - $completed_state->set_context( $this->mock_context ); - - return $completed_state; - } )() - ); + ->with( ProcessedState::class, $this->mock_context ) + ->willReturn( $mock_processed_state ); // Act: start processing. $result = $this->sut->start_processing( $mock_request ); // Assert: Successful transition. - $this->assertInstanceOf( CompletedState::class, $result ); + $this->assertSame( $mock_completed_state, $result ); } public function test_start_processing_will_transition_to_error_state_when_api_exception_occurs() { From 7799a23692a763c71959e0407ef296365dd3e845 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Tue, 17 Oct 2023 14:23:39 +0200 Subject: [PATCH 41/66] Code review fixes --- src/Internal/Payment/State/AbstractPaymentState.php | 4 ++-- src/Internal/Payment/State/InitialState.php | 7 +++---- src/Internal/Payment/State/ProcessedState.php | 4 ++-- .../Internal/Payment/State/AbstractPaymentStateTest.php | 2 +- tests/unit/src/Internal/Payment/State/InitialStateTest.php | 2 +- .../unit/src/Internal/Payment/State/ProcessedStateTest.php | 4 ++-- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Internal/Payment/State/AbstractPaymentState.php b/src/Internal/Payment/State/AbstractPaymentState.php index feed2413e96..2a9f707b8ec 100644 --- a/src/Internal/Payment/State/AbstractPaymentState.php +++ b/src/Internal/Payment/State/AbstractPaymentState.php @@ -119,7 +119,7 @@ public function process() { } /** - * Process completed state. + * Complete processing. * * @psalm-suppress InvalidReturnType * @@ -127,7 +127,7 @@ public function process() { * @throws \WCPay\Exceptions\Order_Not_Found_Exception * @throws \WCPay\Internal\Payment\Exception\StateTransitionException */ - public function complete() { + public function complete_processing() { $this->throw_unavailable_method_exception( __METHOD__ ); } diff --git a/src/Internal/Payment/State/InitialState.php b/src/Internal/Payment/State/InitialState.php index 5b2b60de668..f7cad27f251 100644 --- a/src/Internal/Payment/State/InitialState.php +++ b/src/Internal/Payment/State/InitialState.php @@ -95,11 +95,10 @@ public function start_processing( PaymentRequest $request ) { // Populate further details from the order. $this->populate_context_from_order(); - $context = $this->get_context(); - // Payments are currently based on intents, request one from the API. try { - $intent = $this->payment_request_service->create_intent( $context ); + $context = $this->get_context(); + $intent = $this->payment_request_service->create_intent( $context ); $context->set_intent( $intent ); } catch ( Invalid_Request_Parameter_Exception | Extend_Request_Exception | Immutable_Parameter_Exception $e ) { return $this->create_state( SystemErrorState::class ); @@ -113,7 +112,7 @@ public function start_processing( PaymentRequest $request ) { // All good. Proceed to processed state. $next_state = $this->create_state( ProcessedState::class ); - return $next_state->complete(); + return $next_state->complete_processing(); } /** diff --git a/src/Internal/Payment/State/ProcessedState.php b/src/Internal/Payment/State/ProcessedState.php index b08e4da72f9..379161d1397 100644 --- a/src/Internal/Payment/State/ProcessedState.php +++ b/src/Internal/Payment/State/ProcessedState.php @@ -39,14 +39,14 @@ public function __construct( } /** - * Process all needed verifications. + * Complete processing. * * @return AbstractPaymentState * @throws Order_Not_Found_Exception * @throws StateTransitionException * @throws ContainerException */ - public function complete() { + public function complete_processing() { $context = $this->get_context(); // Complete processing. diff --git a/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php b/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php index b70a8aa7d1e..33043516e75 100644 --- a/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php +++ b/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php @@ -95,7 +95,7 @@ public function state_defined_methods_provider() { return [ [ 'start_processing' ], [ 'process' ], - [ 'complete' ], + [ 'complete_processing' ], ]; } } diff --git a/tests/unit/src/Internal/Payment/State/InitialStateTest.php b/tests/unit/src/Internal/Payment/State/InitialStateTest.php index a9597fbe6e2..7513c1cd51f 100644 --- a/tests/unit/src/Internal/Payment/State/InitialStateTest.php +++ b/tests/unit/src/Internal/Payment/State/InitialStateTest.php @@ -107,7 +107,7 @@ public function test_start_processing() { $mock_completed_state = $this->createMock( CompletedState::class ); $mock_processed_state->expects( $this->once() ) - ->method( 'complete' ) + ->method( 'complete_processing' ) ->willReturn( $mock_completed_state ); // Verify that the context is populated. diff --git a/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php b/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php index 33db029e211..7e25ca604cf 100644 --- a/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php +++ b/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php @@ -54,7 +54,7 @@ protected function setUp(): void { $this->sut->set_context( $this->mock_context ); } - public function test_complete_will_transition_to_completed_state() { + public function test_complete_processing_will_transition_to_completed_state() { $intent = WC_Helper_Intention::create_intention(); @@ -77,7 +77,7 @@ public function test_complete_will_transition_to_completed_state() { ->with( CompletedState::class, $this->mock_context ) ->willReturn( $mock_completed_state ); - $result = $this->sut->complete(); + $result = $this->sut->complete_processing(); $this->assertSame( $mock_completed_state, $result ); } From 5b0cce85d50502c32a5e46898915254e1614bfae Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Wed, 18 Oct 2023 10:19:31 +0200 Subject: [PATCH 42/66] Lint fixes --- .../Payment/State/AbstractPaymentState.php | 52 +++++++++---------- .../Service/PaymentProcessingService.php | 30 +++++------ 2 files changed, 39 insertions(+), 43 deletions(-) diff --git a/src/Internal/Payment/State/AbstractPaymentState.php b/src/Internal/Payment/State/AbstractPaymentState.php index 2a9f707b8ec..78e769bb0c8 100644 --- a/src/Internal/Payment/State/AbstractPaymentState.php +++ b/src/Internal/Payment/State/AbstractPaymentState.php @@ -64,42 +64,17 @@ public function get_context(): PaymentContext { return $this->context; } - /** - * Creates a new instance of a given payment state class. - * - * States control the payment flow, and allow transitions to the next state. - * This method should only be called whenever the process is ready to transition - * to the next state, as each new state will be considered the payment's latest one. - * - * @param string $state_class The class of the state to crate. - * @return AbstractPaymentState - * @throws StateTransitionException In case the new state could not be created. - * @throws ContainerException When the dependency container cannot instantiate the state. - */ - protected function create_state( string $state_class ) { - $state = $this->state_factory->create_state( $state_class, $this->context ); - - // This is where logging will be added. - - return $state; - } - - /** - * State-specific methods might declare a return type, but - * their hollow definitions here would only throw an exception. - * phpcs:disable Squiz.Commenting.FunctionComment.InvalidNoReturn - */ - + // phpcs:disable Squiz.Commenting.FunctionComment.InvalidNoReturn /** * Initiates the payment process. * * @param PaymentRequest $request The incoming payment processing request. * @return AbstractPaymentState The next state. + * * @throws StateTransitionException In case the completed state could not be initialized. * @throws ContainerException When the dependency container cannot instantiate the state. * @throws Order_Not_Found_Exception Order could not be found. * @throws PaymentRequestException When data is not available or invalid. - * @psalm-suppress InvalidReturnType If this method does not throw, it will return a new state. */ public function start_processing( PaymentRequest $request ) { $this->throw_unavailable_method_exception( __METHOD__ ); @@ -121,7 +96,7 @@ public function process() { /** * Complete processing. * - * @psalm-suppress InvalidReturnType + * @psalm-suppress * * @return AbstractPaymentState * @throws \WCPay\Exceptions\Order_Not_Found_Exception @@ -130,6 +105,27 @@ public function process() { public function complete_processing() { $this->throw_unavailable_method_exception( __METHOD__ ); } + // phpcs:enable Squiz.Commenting.FunctionComment.InvalidNoReturn + + /** + * Creates a new instance of a given payment state class. + * + * States control the payment flow, and allow transitions to the next state. + * This method should only be called whenever the process is ready to transition + * to the next state, as each new state will be considered the payment's latest one. + * + * @param string $state_class The class of the state to crate. + * @return AbstractPaymentState + * @throws StateTransitionException In case the new state could not be created. + * @throws ContainerException When the dependency container cannot instantiate the state. + */ + protected function create_state( string $state_class ) { + $state = $this->state_factory->create_state( $state_class, $this->context ); + + // This is where logging will be added. + + return $state; + } /** * Throws an exception, indicating that a given method is not available. diff --git a/src/Internal/Service/PaymentProcessingService.php b/src/Internal/Service/PaymentProcessingService.php index 7d0e7466747..30f373aac12 100644 --- a/src/Internal/Service/PaymentProcessingService.php +++ b/src/Internal/Service/PaymentProcessingService.php @@ -72,21 +72,6 @@ public function process_payment( int $order_id, bool $manual_capture = false ) { return $initial_state->start_processing( $request ); } - /** - * Instantiates a new empty payment context. - * - * @param int $order_id ID of the order that the context belongs to. - * @param bool $manual_capture Whether manual capture is enabled. - * - * @return PaymentContext - */ - protected function create_payment_context( int $order_id, bool $manual_capture = false ): PaymentContext { - $context = new PaymentContext( $order_id ); - $context->toggle_manual_capture( $manual_capture ); - - return $context; - } - /** * Get redirect URL when authentication is required (3DS). * @@ -124,4 +109,19 @@ public function get_authentication_redirect_url( $intent, int $order_id ) { ); } + /** + * Instantiates a new empty payment context. + * + * @param int $order_id ID of the order that the context belongs to. + * @param bool $manual_capture Whether manual capture is enabled. + * + * @return PaymentContext + */ + protected function create_payment_context( int $order_id, bool $manual_capture = false ): PaymentContext { + $context = new PaymentContext( $order_id ); + $context->toggle_manual_capture( $manual_capture ); + + return $context; + } + } From 88531a9dbc93bd3eb5ab03a2ba081aef54ea818c Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Wed, 18 Oct 2023 10:21:27 +0200 Subject: [PATCH 43/66] Added changelog --- changelog/rpp-7414-authentication-required-state | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelog/rpp-7414-authentication-required-state diff --git a/changelog/rpp-7414-authentication-required-state b/changelog/rpp-7414-authentication-required-state new file mode 100644 index 00000000000..e13364d79c2 --- /dev/null +++ b/changelog/rpp-7414-authentication-required-state @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Added authentication required state From 9fa462b2b1e91bab360aab689e2ef047270855f9 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Wed, 18 Oct 2023 10:27:59 +0200 Subject: [PATCH 44/66] Fixed spacing in initial state --- src/Internal/Payment/State/InitialState.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Internal/Payment/State/InitialState.php b/src/Internal/Payment/State/InitialState.php index f7cad27f251..d8a6594c5c9 100644 --- a/src/Internal/Payment/State/InitialState.php +++ b/src/Internal/Payment/State/InitialState.php @@ -56,10 +56,10 @@ class InitialState extends AbstractPaymentState { /** * Class constructor, only meant for storing dependencies. * - * @param StateFactory $state_factory Factory for payment states. - * @param OrderService $order_service Service for order-related actions. - * @param WC_Payments_Customer_Service $customer_service Service for managing remote customers. - * @param Level3Service $level3_service Service for Level3 Data. + * @param StateFactory $state_factory Factory for payment states. + * @param OrderService $order_service Service for order-related actions. + * @param WC_Payments_Customer_Service $customer_service Service for managing remote customers. + * @param Level3Service $level3_service Service for Level3 Data. * @param PaymentRequestService $payment_request_service Connection with the server. */ public function __construct( From 48da0fc92461c2e53787cf45c2e79dcf0fa5bf8d Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Wed, 18 Oct 2023 17:35:00 +0200 Subject: [PATCH 45/66] Code review fixes --- includes/class-wc-payment-gateway-wcpay.php | 1 - .../PaymentsServiceProvider.php | 2 +- src/Internal/Payment/PaymentContext.php | 2 +- .../Payment/State/AbstractPaymentState.php | 17 +---- .../State/AbstractPaymentStateTest.php | 1 - .../Payment/State/InitialStateTest.php | 67 +++++++++---------- .../Payment/State/ProcessedStateTest.php | 5 +- 7 files changed, 36 insertions(+), 59 deletions(-) diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index d5817bd4bf5..93e93e8a4e4 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -32,7 +32,6 @@ use WCPay\Duplicate_Payment_Prevention_Service; use WCPay\Fraud_Prevention\Fraud_Prevention_Service; use WCPay\Fraud_Prevention\Fraud_Risk_Tools; -use WCPay\Internal\Payment\Response\AuthenticationRequiredResponse; use WCPay\Internal\Payment\State\AuthenticationRequiredState; use WCPay\Logger; use WCPay\Payment_Information; diff --git a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php index 751e2a94959..c3a4b2ebeea 100644 --- a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php +++ b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php @@ -21,7 +21,6 @@ use WCPay\Internal\Payment\State\ProcessedState; use WCPay\Internal\Payment\State\StateFactory; use WCPay\Internal\Payment\State\SystemErrorState; -use WCPay\Internal\Payment\State\VerifiedState; use WCPay\Internal\Proxy\LegacyProxy; use WCPay\Internal\Service\PaymentProcessingService; use WCPay\Internal\Service\ExampleService; @@ -44,6 +43,7 @@ class PaymentsServiceProvider extends AbstractServiceProvider { Router::class, StateFactory::class, InitialState::class, + AuthenticationRequiredState::class, ProcessedState::class, CompletedState::class, SystemErrorState::class, diff --git a/src/Internal/Payment/PaymentContext.php b/src/Internal/Payment/PaymentContext.php index 2a802a4c124..bbb1696cf04 100644 --- a/src/Internal/Payment/PaymentContext.php +++ b/src/Internal/Payment/PaymentContext.php @@ -227,7 +227,7 @@ public function get_customer_id(): ?string { } /** - * Stores the payment intent. + * Stores the payment intent object. * * @param WC_Payments_API_Abstract_Intention $intent Instance of intent. */ diff --git a/src/Internal/Payment/State/AbstractPaymentState.php b/src/Internal/Payment/State/AbstractPaymentState.php index 78e769bb0c8..28cb93d4bed 100644 --- a/src/Internal/Payment/State/AbstractPaymentState.php +++ b/src/Internal/Payment/State/AbstractPaymentState.php @@ -80,27 +80,14 @@ public function start_processing( PaymentRequest $request ) { $this->throw_unavailable_method_exception( __METHOD__ ); } - /** - * Process verified state. - * - * @psalm-suppress InvalidReturnType - * - * @return AbstractPaymentState - * @throws \WCPay\Exceptions\Order_Not_Found_Exception - * @throws \WCPay\Internal\Payment\Exception\StateTransitionException - */ - public function process() { - $this->throw_unavailable_method_exception( __METHOD__ ); - } - /** * Complete processing. * * @psalm-suppress * * @return AbstractPaymentState - * @throws \WCPay\Exceptions\Order_Not_Found_Exception - * @throws \WCPay\Internal\Payment\Exception\StateTransitionException + * @throws Order_Not_Found_Exception + * @throws StateTransitionException */ public function complete_processing() { $this->throw_unavailable_method_exception( __METHOD__ ); diff --git a/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php b/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php index 33043516e75..e45740804c3 100644 --- a/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php +++ b/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php @@ -94,7 +94,6 @@ public function test_state_methods_will_throw_exceptions( $method ) { public function state_defined_methods_provider() { return [ [ 'start_processing' ], - [ 'process' ], [ 'complete_processing' ], ]; } diff --git a/tests/unit/src/Internal/Payment/State/InitialStateTest.php b/tests/unit/src/Internal/Payment/State/InitialStateTest.php index 7513c1cd51f..283a7b12f5b 100644 --- a/tests/unit/src/Internal/Payment/State/InitialStateTest.php +++ b/tests/unit/src/Internal/Payment/State/InitialStateTest.php @@ -11,7 +11,6 @@ use WCPay\Constants\Intent_Status; use WCPay\Internal\Payment\State\AuthenticationRequiredState; use WCPay\Internal\Payment\State\ProcessedState; -use WCPay\Internal\Payment\State\VerifiedState; use WCPAY_UnitTestCase; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit_Utils; @@ -39,6 +38,12 @@ class InitialStateTest extends WCPAY_UnitTestCase { * @var InitialState */ private $sut; + /** + * Service under test. + * + * @var StateFactory|InitialState + */ + private $mocked_sut; /** * @var StateFactory|MockObject @@ -91,18 +96,24 @@ protected function setUp(): void { $this->mock_payment_request_service ); $this->sut->set_context( $this->mock_context ); + + $this->mocked_sut = $this->getMockBuilder( InitialState::class ) + ->onlyMethods( [ 'populate_context_from_request', 'populate_context_from_order' ] ) + ->setConstructorArgs( + [ + $this->mock_state_factory, + $this->mock_order_service, + $this->mock_customer_service, + $this->mock_level3_service, + $this->mock_payment_request_service, + ] + ) + ->getMock(); + $this->mocked_sut->set_context( $this->mock_context ); } public function test_start_processing() { - $mock_request = $this->createMock( PaymentRequest::class ); - - /** - * This test works with the root `process` method, which calls a few - * internal methods. We want to mock them for the purpose of this test. - * - * @var MockObject|InitialState - */ - $this->mock_initial_state(); + $mock_request = $this->createMock( PaymentRequest::class ); $mock_processed_state = $this->createMock( ProcessedState::class ); $mock_completed_state = $this->createMock( CompletedState::class ); @@ -111,8 +122,8 @@ public function test_start_processing() { ->willReturn( $mock_completed_state ); // Verify that the context is populated. - $this->sut->expects( $this->once() )->method( 'populate_context_from_request' )->with( $mock_request ); - $this->sut->expects( $this->once() )->method( 'populate_context_from_order' ); + $this->mocked_sut->expects( $this->once() )->method( 'populate_context_from_request' )->with( $mock_request ); + $this->mocked_sut->expects( $this->once() )->method( 'populate_context_from_order' ); $intent = WC_Helper_Intention::create_intention(); @@ -129,7 +140,7 @@ public function test_start_processing() { ->willReturn( $mock_processed_state ); // Act: start processing. - $result = $this->sut->start_processing( $mock_request ); + $result = $this->mocked_sut->start_processing( $mock_request ); // Assert: Successful transition. $this->assertSame( $mock_completed_state, $result ); } @@ -137,7 +148,6 @@ public function test_start_processing() { public function test_start_processing_will_transition_to_error_state_when_api_exception_occurs() { $mock_request = $this->createMock( PaymentRequest::class ); $mock_error_state = $this->createMock( SystemErrorState::class ); - $this->mock_initial_state(); $this->mock_payment_request_service ->expects( $this->once() ) @@ -146,21 +156,20 @@ public function test_start_processing_will_transition_to_error_state_when_api_ex ->willThrowException( new Invalid_Request_Parameter_Exception( 'Invalid param', 'invalid_param' ) ); // Let's mock these services in order to prevent real execution of them. - $this->sut->expects( $this->once() )->method( 'populate_context_from_request' )->with( $mock_request ); - $this->sut->expects( $this->once() )->method( 'populate_context_from_order' ); + $this->mocked_sut->expects( $this->once() )->method( 'populate_context_from_request' )->with( $mock_request ); + $this->mocked_sut->expects( $this->once() )->method( 'populate_context_from_order' ); $this->mock_state_factory->expects( $this->once() ) ->method( 'create_state' ) ->with( SystemErrorState::class, $this->mock_context ) ->willReturn( $mock_error_state ); - $result = $this->sut->start_processing( $mock_request ); + $result = $this->mocked_sut->start_processing( $mock_request ); $this->assertSame( $mock_error_state, $result ); } public function test_processing_will_transition_to_auth_required_state() { $mock_request = $this->createMock( PaymentRequest::class ); $mock_auth_state = $this->createMock( AuthenticationRequiredState::class ); - $this->mock_initial_state(); $this->mock_payment_request_service->expects( $this->once() ) ->method( 'create_intent' ) @@ -168,14 +177,14 @@ public function test_processing_will_transition_to_auth_required_state() { ->willReturn( WC_Helper_Intention::create_intention( [ 'status' => Intent_Status::REQUIRES_ACTION ] ) ); // Let's mock these services in order to prevent real execution of them. - $this->sut->expects( $this->once() )->method( 'populate_context_from_request' )->with( $mock_request ); - $this->sut->expects( $this->once() )->method( 'populate_context_from_order' ); + $this->mocked_sut->expects( $this->once() )->method( 'populate_context_from_request' )->with( $mock_request ); + $this->mocked_sut->expects( $this->once() )->method( 'populate_context_from_order' ); $this->mock_state_factory->expects( $this->once() ) ->method( 'create_state' ) ->with( AuthenticationRequiredState::class, $this->mock_context ) ->willReturn( $mock_auth_state ); - $result = $this->sut->start_processing( $mock_request ); + $result = $this->mocked_sut->start_processing( $mock_request ); $this->assertSame( $mock_auth_state, $result ); } @@ -253,20 +262,4 @@ public function test_populate_context_from_order() { PHPUnit_Utils::call_method( $this->sut, 'populate_context_from_order', [] ); } - - private function mock_initial_state() { - $this->sut = $this->getMockBuilder( InitialState::class ) - ->onlyMethods( [ 'populate_context_from_request', 'populate_context_from_order' ] ) - ->setConstructorArgs( - [ - $this->mock_state_factory, - $this->mock_order_service, - $this->mock_customer_service, - $this->mock_level3_service, - $this->mock_payment_request_service, - ] - ) - ->getMock(); - $this->sut->set_context( $this->mock_context ); - } } diff --git a/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php b/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php index 7e25ca604cf..f5b79ea32d5 100644 --- a/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php +++ b/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php @@ -1,6 +1,6 @@ mock_context->expects( $this->once() ) From f42350428196cee409ff98623a6086c7101c4bf0 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Wed, 18 Oct 2023 17:48:16 +0200 Subject: [PATCH 46/66] Fixed psalm errors --- src/Internal/Payment/State/AbstractPaymentState.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Internal/Payment/State/AbstractPaymentState.php b/src/Internal/Payment/State/AbstractPaymentState.php index 28cb93d4bed..8fc83105018 100644 --- a/src/Internal/Payment/State/AbstractPaymentState.php +++ b/src/Internal/Payment/State/AbstractPaymentState.php @@ -68,6 +68,8 @@ public function get_context(): PaymentContext { /** * Initiates the payment process. * + * @psalm-suppress InvalidReturnType + * * @param PaymentRequest $request The incoming payment processing request. * @return AbstractPaymentState The next state. * @@ -83,7 +85,7 @@ public function start_processing( PaymentRequest $request ) { /** * Complete processing. * - * @psalm-suppress + * @psalm-suppress InvalidReturnType * * @return AbstractPaymentState * @throws Order_Not_Found_Exception From 3ea2e5781db3002835bb7d4dc43d118e9608a164 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Wed, 18 Oct 2023 23:48:39 +0300 Subject: [PATCH 47/66] Adding the necessary files for a local check --- bin/check-src-test-coverage.sh | 19 +++++++++++++++++++ package.json | 1 + phpunit-src.xml.dist | 28 ++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100755 bin/check-src-test-coverage.sh create mode 100644 phpunit-src.xml.dist diff --git a/bin/check-src-test-coverage.sh b/bin/check-src-test-coverage.sh new file mode 100755 index 00000000000..4c4cc86e6a9 --- /dev/null +++ b/bin/check-src-test-coverage.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -e + +echo "Installing the test environment..." + +docker-compose exec -u www-data wordpress \ + /var/www/html/wp-content/plugins/woocommerce-payments/bin/install-wp-tests.sh + +echo "Checking coverage..." + +docker-compose exec -u www-data wordpress \ + /var/www/html/wp-content/plugins/woocommerce-payments/vendor/bin/phpunit \ + --configuration /var/www/html/wp-content/plugins/woocommerce-payments/phpunit-src.xml.dist \ + --coverage-html /var/www/html/php-test-coverage \ + --coverage-clover /var/www/html/clover.xml + $* + +./vendor/bin/coverage-check docker/wordpress/clover.xml 100 diff --git a/package.json b/package.json index a49df63fa09..d5b2b153dfc 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "test:update-snapshots": "npm run test:js -- --updateSnapshot", "test:php": "./bin/run-tests.sh", "test:php-coverage": "./bin/check-test-coverage.sh", + "test:php-coverage-src": "./bin/check-src-test-coverage.sh", "test:php-watch": "npm run test:php -- -w", "test:qit": "npm run build:release && ./tests/qit/security.sh", "watch": "webpack --watch", diff --git a/phpunit-src.xml.dist b/phpunit-src.xml.dist new file mode 100644 index 00000000000..44e91de38ae --- /dev/null +++ b/phpunit-src.xml.dist @@ -0,0 +1,28 @@ + + + + + ./tests/unit/helpers + ./tests/unit/src + + + + + + + src + + + src/Internal/DependencyManagement/ServiceProvider + + + + + From 97345dbc66b1cc740585d7850147f228ccdad2eb Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 19 Oct 2023 17:32:43 +0200 Subject: [PATCH 48/66] Code review fixes --- src/Internal/Payment/State/InitialState.php | 1 - .../Payment/State/InitialStateTest.php | 7 ++-- .../Payment/State/ProcessedStateTest.php | 5 +++ .../Service/PaymentProcessingServiceTest.php | 36 ++++++++++++++----- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/Internal/Payment/State/InitialState.php b/src/Internal/Payment/State/InitialState.php index d8a6594c5c9..7e3311d6819 100644 --- a/src/Internal/Payment/State/InitialState.php +++ b/src/Internal/Payment/State/InitialState.php @@ -124,7 +124,6 @@ public function start_processing( PaymentRequest $request ) { * on all needed parameters being in place. * * @param PaymentRequest $request The request to use. - * * @throws PaymentRequestException When data is not available or invalid. */ protected function populate_context_from_request( PaymentRequest $request ) { diff --git a/tests/unit/src/Internal/Payment/State/InitialStateTest.php b/tests/unit/src/Internal/Payment/State/InitialStateTest.php index 283a7b12f5b..1d68af19c97 100644 --- a/tests/unit/src/Internal/Payment/State/InitialStateTest.php +++ b/tests/unit/src/Internal/Payment/State/InitialStateTest.php @@ -38,10 +38,11 @@ class InitialStateTest extends WCPAY_UnitTestCase { * @var InitialState */ private $sut; + /** * Service under test. * - * @var StateFactory|InitialState + * @var InitialState|MockObject */ private $mocked_sut; @@ -61,12 +62,12 @@ class InitialStateTest extends WCPAY_UnitTestCase { private $mock_customer_service; /** - * @var Level3Service|MockObject + * @var PaymentRequestService|MockObject */ private $mock_payment_request_service; /** - * @var PaymentRequestService|MockObject + * @var Level3Service|MockObject */ private $mock_level3_service; diff --git a/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php b/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php index f5b79ea32d5..f0a89422e9e 100644 --- a/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php +++ b/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php @@ -37,6 +37,11 @@ class ProcessedStateTest extends WCPAY_UnitTestCase { */ private $mock_order_service; + /** + * @var PaymentContext|MockObject + */ + private $mock_context; + /** * Set up the test. */ diff --git a/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php b/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php index f67a44dd588..66d448edbe1 100644 --- a/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php +++ b/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php @@ -10,6 +10,8 @@ use PHPUnit\Framework\MockObject\MockObject; use WC_Helper_Intention; use WC_Payment_Gateway_WCPay; +use WC_Payments_API_Abstract_Intention; +use WC_Payments_API_Setup_Intention; use WCPay\Constants\Intent_Status; use WCPay\Internal\Payment\PaymentContext; use WCPay\Internal\Payment\PaymentRequest; @@ -143,13 +145,21 @@ public function test_get_authentication_redirect_url_will_return_url_from_paymen } - public function test_get_authentication_redirect_url_will_return_url_with_encrypted_secret_key() { + /** + * Test URL will be returned with encrypted secret key + * + * @param WC_Payments_API_Abstract_Intention $intent + * + * @dataProvider intent_provider_for_encrypted_key_urls + * @return void + */ + public function test_redirect_url_returns_url_with_encrypted_secret_key( $intent ) { $sut = new PaymentProcessingService( $this->mock_state_factory, $this->mock_legacy_proxy ); - $intent = WC_Helper_Intention::create_intention(); $secret = 'encrypted_secret'; // Dummy text to avoid calling crypt function. $nonce = 'nonce'; // Return of nonce function when called from legacy proxy. $order = 1; // Order id. + $prefix = $intent instanceof WC_Payments_API_Setup_Intention ? 'si' : 'pi'; $this->mock_legacy_proxy->expects( $this->once() ) ->method( 'call_static' ) @@ -170,13 +180,12 @@ public function test_get_authentication_redirect_url_will_return_url_with_encryp [ 'wp_create_nonce', 'wcpay_update_order_status_nonce' ] ) ->willReturnOnConsecutiveCalls( $secret, $nonce ); + $result = $sut->get_authentication_redirect_url( $intent, $order ); - $this->assertSame( "#wcpay-confirm-pi:$order:$secret:$nonce", $result ); + $this->assertSame( "#wcpay-confirm-$prefix:$order:$secret:$nonce", $result ); } - public function test_get_authentication_redirect_url_will_return_url_with_secret_and_si_prefix() { - $sut = new PaymentProcessingService( $this->mock_state_factory, $this->mock_legacy_proxy ); - + public function test_redirect_url_returns_url_with_non_encrypted_client_secret_when_encryption_disabled() { $intent = WC_Helper_Intention::create_setup_intention(); $nonce = 'nonce'; // Return of nonce function when called from legacy proxy. $order = 1; // Order id. @@ -190,9 +199,20 @@ public function test_get_authentication_redirect_url_will_return_url_with_secret ->method( 'call_function' ) ->with( 'wp_create_nonce', 'wcpay_update_order_status_nonce' ) ->willReturn( $nonce ); - $result = $sut->get_authentication_redirect_url( $intent, $order ); + + $result = $this->sut->get_authentication_redirect_url( $intent, $order ); $this->assertSame( "#wcpay-confirm-si:$order:" . $intent->get_client_secret() . ":$nonce", $result ); } - + /** + * Data provider for encrypted key url test. + * + * @return array[] + */ + public function intent_provider_for_encrypted_key_urls() { + return [ + [ WC_Helper_Intention::create_intention() ], + [ WC_Helper_Intention::create_setup_intention() ], + ]; + } } From b35ba641977dfd7e93c02b85d336adae06f2dcf7 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 19 Oct 2023 17:53:49 +0200 Subject: [PATCH 49/66] Used mocked version of state for tests --- .../src/Internal/Service/PaymentProcessingServiceTest.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php b/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php index 66d448edbe1..507a3bc8071 100644 --- a/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php +++ b/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php @@ -96,8 +96,6 @@ public function test_process_payment_happy_path() { * Test the basic happy path of processing a payment. */ public function test_process_payment_happy_path_without_mock_builder() { - $sut = new PaymentProcessingService( $this->mock_state_factory, $this->mock_legacy_proxy ); - $mock_initial_state = $this->createMock( InitialState::class ); $mock_completed_state = $this->createMock( CompletedState::class ); @@ -115,7 +113,7 @@ public function test_process_payment_happy_path_without_mock_builder() { ->with( $this->isInstanceOf( PaymentRequest::class ) ) ->willReturn( $mock_completed_state ); - $result = $sut->process_payment( 1 ); + $result = $this->sut->process_payment( 1 ); $this->assertSame( $mock_completed_state, $result ); } @@ -154,8 +152,6 @@ public function test_get_authentication_redirect_url_will_return_url_from_paymen * @return void */ public function test_redirect_url_returns_url_with_encrypted_secret_key( $intent ) { - $sut = new PaymentProcessingService( $this->mock_state_factory, $this->mock_legacy_proxy ); - $secret = 'encrypted_secret'; // Dummy text to avoid calling crypt function. $nonce = 'nonce'; // Return of nonce function when called from legacy proxy. $order = 1; // Order id. @@ -181,7 +177,7 @@ public function test_redirect_url_returns_url_with_encrypted_secret_key( $intent ) ->willReturnOnConsecutiveCalls( $secret, $nonce ); - $result = $sut->get_authentication_redirect_url( $intent, $order ); + $result = $this->sut->get_authentication_redirect_url( $intent, $order ); $this->assertSame( "#wcpay-confirm-$prefix:$order:$secret:$nonce", $result ); } From adb8aeaa42110eb728893ef8982c929123c1afae Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Mon, 23 Oct 2023 12:41:08 +0300 Subject: [PATCH 50/66] Workflow for src tests coverage --- .github/workflows/src-coverage.yml | 39 ++++++++++++++++++++++++ bin/phpunit.sh | 7 +++-- bin/run-ci-tests-check-src-coverage.bash | 16 ++++++++++ 3 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/src-coverage.yml mode change 100644 => 100755 bin/phpunit.sh create mode 100644 bin/run-ci-tests-check-src-coverage.bash diff --git a/.github/workflows/src-coverage.yml b/.github/workflows/src-coverage.yml new file mode 100644 index 00000000000..4acb64983d1 --- /dev/null +++ b/.github/workflows/src-coverage.yml @@ -0,0 +1,39 @@ +name: Code coverage (`src` directory) + +on: + pull_request + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + woocommerce-coverage: + name: Src Coverage + runs-on: ubuntu-latest + strategy: + fail-fast: false + max-parallel: 10 + matrix: + woocommerce: [ 'latest' ] + wordpress: [ 'latest' ] + php: [ '7.4' ] + env: + WP_VERSION: ${{ matrix.wordpress }} + WC_VERSION: ${{ matrix.woocommerce }} + steps: + # clone the repository + - uses: actions/checkout@v3 + # enable dependencies caching + - uses: actions/cache@v3 + with: + path: ~/.cache/composer/ + key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }} + # setup PHP, but without debug extensions for reasonable performance + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: composer + coverage: xdebug2 + # run CI checks + - run: bash bin/run-ci-tests-check-src-coverage.bash diff --git a/bin/phpunit.sh b/bin/phpunit.sh old mode 100644 new mode 100755 index f7d7c10ad38..2bbcc5de46d --- a/bin/phpunit.sh +++ b/bin/phpunit.sh @@ -6,6 +6,7 @@ fi CURRENT_PHP_MAJOR_VERSION=$(php -r 'echo PHP_MAJOR_VERSION;') CURRENT_PHP_MINOR_VERSION=$(php -r 'echo PHP_MINOR_VERSION;') +CONFIG_FILE=${1-phpunit.xml.dist} # The PHPUnit version inside composer.json is not compatible with PHP versions bellow 7.3 # Update this constant if you wish to bump supported PHP major version @@ -13,13 +14,13 @@ SUPPORTED_PHP_MAJOR_VERSION_FOR_PHPUNIT_INSTALLED_VIA_COMPOSER_JSON=7 SUPPORTED_PHP_MINOR_VERSION_FOR_PHPUNIT_INSTALLED_VIA_COMPOSER_JSON=3 if [ $CURRENT_PHP_MAJOR_VERSION -gt $SUPPORTED_PHP_MAJOR_VERSION_FOR_PHPUNIT_INSTALLED_VIA_COMPOSER_JSON ]; then - ./vendor/bin/phpunit -c phpunit.xml.dist "$@"; + ./vendor/bin/phpunit -c $CONFIG_FILE "$@"; else if [ $CURRENT_PHP_MINOR_VERSION -ge $SUPPORTED_PHP_MINOR_VERSION_FOR_PHPUNIT_INSTALLED_VIA_COMPOSER_JSON ]; then - ./vendor/bin/phpunit -c phpunit.xml.dist "$@"; + ./vendor/bin/phpunit -c $CONFIG_FILE "$@"; else chmod +x ./bin/phpunit6 - ./bin/phpunit6 -c phpunit.xml.dist "$@"; + ./bin/phpunit6 -c $CONFIG_FILE "$@"; fi fi diff --git a/bin/run-ci-tests-check-src-coverage.bash b/bin/run-ci-tests-check-src-coverage.bash new file mode 100644 index 00000000000..c6f5f353cb3 --- /dev/null +++ b/bin/run-ci-tests-check-src-coverage.bash @@ -0,0 +1,16 @@ +#!/bin/bash + +# set strict mode for bash +set -euo pipefail +IFS=$'\n\t' + +# set environment variables +WCPAY_DIR="$GITHUB_WORKSPACE" + +composer self-update && composer install --no-progress +sudo systemctl start mysql.service +bash bin/install-wp-tests.sh woocommerce_test root root localhost $WP_VERSION $WC_VERSION false +echo 'Running the tests...' +bash bin/phpunit.sh phpunit-src.xml.dist --coverage-clover /tmp/clover.xml +vendor/bin/coverage-check /tmp/clover.xml 100 + From 6836debacc6173efa81f9a9fe6f370bd9399f801 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Mon, 23 Oct 2023 12:49:05 +0300 Subject: [PATCH 51/66] Changing config --- .github/workflows/src-coverage.yml | 4 ++-- bin/phpunit.sh | 7 +++---- bin/run-ci-tests-check-coverage.bash | 2 +- bin/run-ci-tests-check-src-coverage.bash | 2 +- bin/run-ci-tests.bash | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/src-coverage.yml b/.github/workflows/src-coverage.yml index 4acb64983d1..3192cd639ad 100644 --- a/.github/workflows/src-coverage.yml +++ b/.github/workflows/src-coverage.yml @@ -1,4 +1,4 @@ -name: Code coverage (`src` directory) +name: Code coverage on: pull_request @@ -9,7 +9,7 @@ concurrency: jobs: woocommerce-coverage: - name: Src Coverage + name: Code coverage (`src` directory) runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/bin/phpunit.sh b/bin/phpunit.sh index 2bbcc5de46d..c25459aacc6 100755 --- a/bin/phpunit.sh +++ b/bin/phpunit.sh @@ -6,7 +6,6 @@ fi CURRENT_PHP_MAJOR_VERSION=$(php -r 'echo PHP_MAJOR_VERSION;') CURRENT_PHP_MINOR_VERSION=$(php -r 'echo PHP_MINOR_VERSION;') -CONFIG_FILE=${1-phpunit.xml.dist} # The PHPUnit version inside composer.json is not compatible with PHP versions bellow 7.3 # Update this constant if you wish to bump supported PHP major version @@ -14,13 +13,13 @@ SUPPORTED_PHP_MAJOR_VERSION_FOR_PHPUNIT_INSTALLED_VIA_COMPOSER_JSON=7 SUPPORTED_PHP_MINOR_VERSION_FOR_PHPUNIT_INSTALLED_VIA_COMPOSER_JSON=3 if [ $CURRENT_PHP_MAJOR_VERSION -gt $SUPPORTED_PHP_MAJOR_VERSION_FOR_PHPUNIT_INSTALLED_VIA_COMPOSER_JSON ]; then - ./vendor/bin/phpunit -c $CONFIG_FILE "$@"; + ./vendor/bin/phpunit "$@"; else if [ $CURRENT_PHP_MINOR_VERSION -ge $SUPPORTED_PHP_MINOR_VERSION_FOR_PHPUNIT_INSTALLED_VIA_COMPOSER_JSON ]; then - ./vendor/bin/phpunit -c $CONFIG_FILE "$@"; + ./vendor/bin/phpunit "$@"; else chmod +x ./bin/phpunit6 - ./bin/phpunit6 -c $CONFIG_FILE "$@"; + ./bin/phpunit6 "$@"; fi fi diff --git a/bin/run-ci-tests-check-coverage.bash b/bin/run-ci-tests-check-coverage.bash index 7c2d5d33c24..cddb5183620 100644 --- a/bin/run-ci-tests-check-coverage.bash +++ b/bin/run-ci-tests-check-coverage.bash @@ -11,6 +11,6 @@ composer self-update && composer install --no-progress sudo systemctl start mysql.service bash bin/install-wp-tests.sh woocommerce_test root root localhost $WP_VERSION $WC_VERSION false echo 'Running the tests...' -bash bin/phpunit.sh --coverage-clover /tmp/clover.xml +bash bin/phpunit.sh -c phpunit.xml.dist --coverage-clover /tmp/clover.xml vendor/bin/coverage-check /tmp/clover.xml 60 diff --git a/bin/run-ci-tests-check-src-coverage.bash b/bin/run-ci-tests-check-src-coverage.bash index c6f5f353cb3..967be3bb25f 100644 --- a/bin/run-ci-tests-check-src-coverage.bash +++ b/bin/run-ci-tests-check-src-coverage.bash @@ -11,6 +11,6 @@ composer self-update && composer install --no-progress sudo systemctl start mysql.service bash bin/install-wp-tests.sh woocommerce_test root root localhost $WP_VERSION $WC_VERSION false echo 'Running the tests...' -bash bin/phpunit.sh phpunit-src.xml.dist --coverage-clover /tmp/clover.xml +bash bin/phpunit.sh -c phpunit-src.xml.dist --coverage-clover /tmp/clover.xml vendor/bin/coverage-check /tmp/clover.xml 100 diff --git a/bin/run-ci-tests.bash b/bin/run-ci-tests.bash index e7a9a03e340..8102c8202ad 100644 --- a/bin/run-ci-tests.bash +++ b/bin/run-ci-tests.bash @@ -24,4 +24,4 @@ echo 'Setting up test environment...' bash bin/install-wp-tests.sh woocommerce_test root root localhost $WP_VERSION $WC_VERSION false $GUTENBERG_VERSION echo 'Running the tests...' -bash bin/phpunit.sh +bash bin/phpunit.sh -c phpunit.xml.dist From aa6b575a9eeb2d92081aafc56d8e49e363391ddd Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Mon, 23 Oct 2023 12:50:27 +0300 Subject: [PATCH 52/66] Renaming the new workflow --- .github/workflows/src-coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/src-coverage.yml b/.github/workflows/src-coverage.yml index 3192cd639ad..5f2f41da5df 100644 --- a/.github/workflows/src-coverage.yml +++ b/.github/workflows/src-coverage.yml @@ -1,4 +1,4 @@ -name: Code coverage +name: `src` coverage on: pull_request From e828d71c7fbc8f3b30de492c102c71231ad9860d Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Mon, 23 Oct 2023 12:51:36 +0300 Subject: [PATCH 53/66] Fixing the name --- .github/workflows/src-coverage.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/src-coverage.yml b/.github/workflows/src-coverage.yml index 5f2f41da5df..a64fbf5d9b2 100644 --- a/.github/workflows/src-coverage.yml +++ b/.github/workflows/src-coverage.yml @@ -1,4 +1,4 @@ -name: `src` coverage +name: src coverage on: pull_request @@ -9,7 +9,7 @@ concurrency: jobs: woocommerce-coverage: - name: Code coverage (`src` directory) + name: Code coverage (src directory) runs-on: ubuntu-latest strategy: fail-fast: false From 687e1bdb146999d4696f5dadbf791ccb9e8ca98e Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 23 Oct 2023 11:55:34 +0200 Subject: [PATCH 54/66] Added docblock for mocked sut variable Co-authored-by: Radoslav Georgiev --- tests/unit/src/Internal/Payment/State/InitialStateTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/unit/src/Internal/Payment/State/InitialStateTest.php b/tests/unit/src/Internal/Payment/State/InitialStateTest.php index 1d68af19c97..74687a167e6 100644 --- a/tests/unit/src/Internal/Payment/State/InitialStateTest.php +++ b/tests/unit/src/Internal/Payment/State/InitialStateTest.php @@ -98,6 +98,12 @@ protected function setUp(): void { ); $this->sut->set_context( $this->mock_context ); + /** + * This test works with the root `process` method, which calls a few + * internal methods. We want to mock them for the purpose of this test. + * + * @var MockObject|InitialState + */ $this->mocked_sut = $this->getMockBuilder( InitialState::class ) ->onlyMethods( [ 'populate_context_from_request', 'populate_context_from_order' ] ) ->setConstructorArgs( From 1df06bb9994adb41ddc92646fd833f5987f3840e Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Mon, 23 Oct 2023 12:55:43 +0300 Subject: [PATCH 55/66] One more rename --- .github/workflows/src-coverage.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/src-coverage.yml b/.github/workflows/src-coverage.yml index a64fbf5d9b2..0a72ba29ab1 100644 --- a/.github/workflows/src-coverage.yml +++ b/.github/workflows/src-coverage.yml @@ -1,4 +1,4 @@ -name: src coverage +name: Code coverage (src) on: pull_request @@ -9,7 +9,7 @@ concurrency: jobs: woocommerce-coverage: - name: Code coverage (src directory) + name: Code coverage runs-on: ubuntu-latest strategy: fail-fast: false From 1a734bc90a9b3b35990f8233d58b2781c6816867 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Mon, 23 Oct 2023 13:27:45 +0300 Subject: [PATCH 56/66] Changelog entry --- changelog/patch | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelog/patch diff --git a/changelog/patch b/changelog/patch new file mode 100644 index 00000000000..1be87335e20 --- /dev/null +++ b/changelog/patch @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: Adding 100% src coverage helpers. + + From 2c165178f28b4e6a39827c60fd9e55320d218bff Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 23 Oct 2023 12:47:36 +0200 Subject: [PATCH 57/66] Updated tests --- .../State/AbstractPaymentStateTest.php | 19 +++++++------------ .../Service/PaymentProcessingServiceTest.php | 12 +++++------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php b/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php index e45740804c3..d57b1e8a2e9 100644 --- a/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php +++ b/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php @@ -80,21 +80,16 @@ public function test_create_state_uses_the_factory() { $this->assertSame( $mock_completed_state, $result ); } - /** - * @dataProvider state_defined_methods_provider - */ - public function test_state_methods_will_throw_exceptions( $method ) { + public function test_calling_start_processing_will_throw_exceptions() { $this->expectException( StateTransitionException::class ); - $this->expectExceptionMessage( 'The WCPay\Internal\Payment\State\AbstractPaymentState::' . $method . ' method is not available in the current payment state (' . PureState::class . ').' ); + $this->expectExceptionMessage( 'The WCPay\Internal\Payment\State\AbstractPaymentState::start_processing method is not available in the current payment state (' . PureState::class . ').' ); - // Dynamically call the method based on $method. - $this->sut->{$method}( $this->createMock( PaymentRequest::class ) ); + $this->sut->start_processing( $this->createMock( PaymentRequest::class ) ); } + public function test_calling_complete_processing_will_throw_exceptions() { + $this->expectException( StateTransitionException::class ); + $this->expectExceptionMessage( 'The WCPay\Internal\Payment\State\AbstractPaymentState::complete_processing method is not available in the current payment state (' . PureState::class . ').' ); - public function state_defined_methods_provider() { - return [ - [ 'start_processing' ], - [ 'complete_processing' ], - ]; + $this->sut->complete_processing(); } } diff --git a/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php b/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php index 507a3bc8071..9232ce76a5a 100644 --- a/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php +++ b/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php @@ -72,11 +72,7 @@ public function test_process_payment_happy_path() { $mock_initial_state = $this->createMock( InitialState::class ); $mock_completed_state = $this->createMock( CompletedState::class ); - // Set up the mocks to be returned. - $this->sut->expects( $this->once() ) - ->method( 'create_payment_context' ) - ->with( 1 ) - ->willReturn( $mock_context ); + $sut = new PaymentProcessingService( $this->mock_state_factory, $this->mock_legacy_proxy ); $this->mock_state_factory->expects( $this->once() ) ->method( 'create_state' ) @@ -88,7 +84,7 @@ public function test_process_payment_happy_path() { ->with( $this->isInstanceOf( PaymentRequest::class ) ) ->willReturn( $mock_completed_state ); - $result = $this->sut->process_payment( 1 ); + $result = $sut->process_payment( 1 ); $this->assertSame( $mock_completed_state, $result ); } @@ -96,6 +92,8 @@ public function test_process_payment_happy_path() { * Test the basic happy path of processing a payment. */ public function test_process_payment_happy_path_without_mock_builder() { + $sut = new PaymentProcessingService( $this->mock_state_factory, $this->mock_legacy_proxy ); + $mock_initial_state = $this->createMock( InitialState::class ); $mock_completed_state = $this->createMock( CompletedState::class ); @@ -113,7 +111,7 @@ public function test_process_payment_happy_path_without_mock_builder() { ->with( $this->isInstanceOf( PaymentRequest::class ) ) ->willReturn( $mock_completed_state ); - $result = $this->sut->process_payment( 1 ); + $result = $sut->process_payment( 1 ); $this->assertSame( $mock_completed_state, $result ); } From f6abcee907c010cf8d2c5a5aafab289f1b864943 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 23 Oct 2023 13:13:55 +0200 Subject: [PATCH 58/66] Changed state names --- includes/class-wc-payment-gateway-wcpay.php | 4 ++-- .../ServiceProvider/PaymentsServiceProvider.php | 12 ++++++------ .../State/{ProcessedState.php => CapturedState.php} | 4 ++-- src/Internal/Payment/State/InitialState.php | 4 ++-- ...uiredState.php => PendingAuthenticationState.php} | 4 ++-- ...{ProcessedStateTest.php => CapturedStateTest.php} | 12 ++++++------ .../src/Internal/Payment/State/InitialStateTest.php | 12 ++++++------ 7 files changed, 26 insertions(+), 26 deletions(-) rename src/Internal/Payment/State/{ProcessedState.php => CapturedState.php} (94%) rename src/Internal/Payment/State/{AuthenticationRequiredState.php => PendingAuthenticationState.php} (63%) rename tests/unit/src/Internal/Payment/State/{ProcessedStateTest.php => CapturedStateTest.php} (89%) diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index b81435ea58c..dc8295dff69 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -32,7 +32,7 @@ use WCPay\Duplicate_Payment_Prevention_Service; use WCPay\Fraud_Prevention\Fraud_Prevention_Service; use WCPay\Fraud_Prevention\Fraud_Risk_Tools; -use WCPay\Internal\Payment\State\AuthenticationRequiredState; +use WCPay\Internal\Payment\State\PendingAuthenticationState; use WCPay\Logger; use WCPay\Payment_Information; use WCPay\Payment_Methods\UPE_Payment_Gateway; @@ -857,7 +857,7 @@ public function new_process_payment( WC_Order $order ) { ]; } - if ( $state instanceof AuthenticationRequiredState ) { + if ( $state instanceof PendingAuthenticationState ) { $context = $state->get_context(); return [ 'result' => 'success', diff --git a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php index c3a4b2ebeea..2c0276a1001 100644 --- a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php +++ b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php @@ -14,11 +14,11 @@ use WCPay\Database_Cache; use WCPay\Internal\DependencyManagement\AbstractServiceProvider; use WCPay\Internal\Payment\Router; -use WCPay\Internal\Payment\State\AuthenticationRequiredState; +use WCPay\Internal\Payment\State\PendingAuthenticationState; use WCPay\Internal\Payment\State\CompletedState; use WCPay\Internal\Payment\State\InitialState; use WCPay\Internal\Payment\State\PaymentErrorState; -use WCPay\Internal\Payment\State\ProcessedState; +use WCPay\Internal\Payment\State\CapturedState; use WCPay\Internal\Payment\State\StateFactory; use WCPay\Internal\Payment\State\SystemErrorState; use WCPay\Internal\Proxy\LegacyProxy; @@ -43,8 +43,8 @@ class PaymentsServiceProvider extends AbstractServiceProvider { Router::class, StateFactory::class, InitialState::class, - AuthenticationRequiredState::class, - ProcessedState::class, + PendingAuthenticationState::class, + CapturedState::class, CompletedState::class, SystemErrorState::class, PaymentErrorState::class, @@ -75,11 +75,11 @@ public function register(): void { ->addArgument( Level3Service::class ) ->addArgument( PaymentRequestService::class ); - $container->add( ProcessedState::class ) + $container->add( CapturedState::class ) ->addArgument( StateFactory::class ) ->addArgument( OrderService::class ); - $container->add( AuthenticationRequiredState::class ) + $container->add( PendingAuthenticationState::class ) ->addArgument( StateFactory::class ); $container->add( CompletedState::class ) diff --git a/src/Internal/Payment/State/ProcessedState.php b/src/Internal/Payment/State/CapturedState.php similarity index 94% rename from src/Internal/Payment/State/ProcessedState.php rename to src/Internal/Payment/State/CapturedState.php index 379161d1397..ef4aa9bfc3f 100644 --- a/src/Internal/Payment/State/ProcessedState.php +++ b/src/Internal/Payment/State/CapturedState.php @@ -1,6 +1,6 @@ get_status() ) { - return $this->create_state( AuthenticationRequiredState::class ); + return $this->create_state( PendingAuthenticationState::class ); } // All good. Proceed to processed state. - $next_state = $this->create_state( ProcessedState::class ); + $next_state = $this->create_state( CapturedState::class ); return $next_state->complete_processing(); } diff --git a/src/Internal/Payment/State/AuthenticationRequiredState.php b/src/Internal/Payment/State/PendingAuthenticationState.php similarity index 63% rename from src/Internal/Payment/State/AuthenticationRequiredState.php rename to src/Internal/Payment/State/PendingAuthenticationState.php index b32b71a8e1e..4ffcf0ba4d8 100644 --- a/src/Internal/Payment/State/AuthenticationRequiredState.php +++ b/src/Internal/Payment/State/PendingAuthenticationState.php @@ -1,6 +1,6 @@ mock_order_service = $this->createMock( OrderService::class ); $this->mock_context = $this->createMock( PaymentContext::class ); - $this->sut = new ProcessedState( + $this->sut = new CapturedState( $this->mock_state_factory, $this->mock_order_service ); diff --git a/tests/unit/src/Internal/Payment/State/InitialStateTest.php b/tests/unit/src/Internal/Payment/State/InitialStateTest.php index 74687a167e6..897ae5b38fc 100644 --- a/tests/unit/src/Internal/Payment/State/InitialStateTest.php +++ b/tests/unit/src/Internal/Payment/State/InitialStateTest.php @@ -9,8 +9,8 @@ use WC_Helper_Intention; use WCPay\Constants\Intent_Status; -use WCPay\Internal\Payment\State\AuthenticationRequiredState; -use WCPay\Internal\Payment\State\ProcessedState; +use WCPay\Internal\Payment\State\PendingAuthenticationState; +use WCPay\Internal\Payment\State\CapturedState; use WCPAY_UnitTestCase; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit_Utils; @@ -121,7 +121,7 @@ protected function setUp(): void { public function test_start_processing() { $mock_request = $this->createMock( PaymentRequest::class ); - $mock_processed_state = $this->createMock( ProcessedState::class ); + $mock_processed_state = $this->createMock( CapturedState::class ); $mock_completed_state = $this->createMock( CompletedState::class ); $mock_processed_state->expects( $this->once() ) @@ -143,7 +143,7 @@ public function test_start_processing() { // Since the original create_state method is mocked, we have to manually set context. $this->mock_state_factory->expects( $this->once() ) ->method( 'create_state' ) - ->with( ProcessedState::class, $this->mock_context ) + ->with( CapturedState::class, $this->mock_context ) ->willReturn( $mock_processed_state ); // Act: start processing. @@ -176,7 +176,7 @@ public function test_start_processing_will_transition_to_error_state_when_api_ex public function test_processing_will_transition_to_auth_required_state() { $mock_request = $this->createMock( PaymentRequest::class ); - $mock_auth_state = $this->createMock( AuthenticationRequiredState::class ); + $mock_auth_state = $this->createMock( PendingAuthenticationState::class ); $this->mock_payment_request_service->expects( $this->once() ) ->method( 'create_intent' ) @@ -189,7 +189,7 @@ public function test_processing_will_transition_to_auth_required_state() { $this->mock_state_factory->expects( $this->once() ) ->method( 'create_state' ) - ->with( AuthenticationRequiredState::class, $this->mock_context ) + ->with( PendingAuthenticationState::class, $this->mock_context ) ->willReturn( $mock_auth_state ); $result = $this->mocked_sut->start_processing( $mock_request ); $this->assertSame( $mock_auth_state, $result ); From e90a707e365bce7e6427a962cda2e1c55dd3167a Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 23 Oct 2023 14:28:18 +0200 Subject: [PATCH 59/66] Revert "Changed state names" This reverts commit f6abcee907c010cf8d2c5a5aafab289f1b864943. --- includes/class-wc-payment-gateway-wcpay.php | 4 ++-- .../ServiceProvider/PaymentsServiceProvider.php | 12 ++++++------ ...tionState.php => AuthenticationRequiredState.php} | 4 ++-- src/Internal/Payment/State/InitialState.php | 4 ++-- .../State/{CapturedState.php => ProcessedState.php} | 4 ++-- .../src/Internal/Payment/State/InitialStateTest.php | 12 ++++++------ ...{CapturedStateTest.php => ProcessedStateTest.php} | 12 ++++++------ 7 files changed, 26 insertions(+), 26 deletions(-) rename src/Internal/Payment/State/{PendingAuthenticationState.php => AuthenticationRequiredState.php} (63%) rename src/Internal/Payment/State/{CapturedState.php => ProcessedState.php} (94%) rename tests/unit/src/Internal/Payment/State/{CapturedStateTest.php => ProcessedStateTest.php} (89%) diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 781882529d2..fca52f46724 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -32,7 +32,7 @@ use WCPay\Duplicate_Payment_Prevention_Service; use WCPay\Fraud_Prevention\Fraud_Prevention_Service; use WCPay\Fraud_Prevention\Fraud_Risk_Tools; -use WCPay\Internal\Payment\State\PendingAuthenticationState; +use WCPay\Internal\Payment\State\AuthenticationRequiredState; use WCPay\Logger; use WCPay\Payment_Information; use WCPay\Payment_Methods\UPE_Payment_Gateway; @@ -857,7 +857,7 @@ public function new_process_payment( WC_Order $order ) { ]; } - if ( $state instanceof PendingAuthenticationState ) { + if ( $state instanceof AuthenticationRequiredState ) { $context = $state->get_context(); return [ 'result' => 'success', diff --git a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php index 2c0276a1001..c3a4b2ebeea 100644 --- a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php +++ b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php @@ -14,11 +14,11 @@ use WCPay\Database_Cache; use WCPay\Internal\DependencyManagement\AbstractServiceProvider; use WCPay\Internal\Payment\Router; -use WCPay\Internal\Payment\State\PendingAuthenticationState; +use WCPay\Internal\Payment\State\AuthenticationRequiredState; use WCPay\Internal\Payment\State\CompletedState; use WCPay\Internal\Payment\State\InitialState; use WCPay\Internal\Payment\State\PaymentErrorState; -use WCPay\Internal\Payment\State\CapturedState; +use WCPay\Internal\Payment\State\ProcessedState; use WCPay\Internal\Payment\State\StateFactory; use WCPay\Internal\Payment\State\SystemErrorState; use WCPay\Internal\Proxy\LegacyProxy; @@ -43,8 +43,8 @@ class PaymentsServiceProvider extends AbstractServiceProvider { Router::class, StateFactory::class, InitialState::class, - PendingAuthenticationState::class, - CapturedState::class, + AuthenticationRequiredState::class, + ProcessedState::class, CompletedState::class, SystemErrorState::class, PaymentErrorState::class, @@ -75,11 +75,11 @@ public function register(): void { ->addArgument( Level3Service::class ) ->addArgument( PaymentRequestService::class ); - $container->add( CapturedState::class ) + $container->add( ProcessedState::class ) ->addArgument( StateFactory::class ) ->addArgument( OrderService::class ); - $container->add( PendingAuthenticationState::class ) + $container->add( AuthenticationRequiredState::class ) ->addArgument( StateFactory::class ); $container->add( CompletedState::class ) diff --git a/src/Internal/Payment/State/PendingAuthenticationState.php b/src/Internal/Payment/State/AuthenticationRequiredState.php similarity index 63% rename from src/Internal/Payment/State/PendingAuthenticationState.php rename to src/Internal/Payment/State/AuthenticationRequiredState.php index 4ffcf0ba4d8..b32b71a8e1e 100644 --- a/src/Internal/Payment/State/PendingAuthenticationState.php +++ b/src/Internal/Payment/State/AuthenticationRequiredState.php @@ -1,6 +1,6 @@ get_status() ) { - return $this->create_state( PendingAuthenticationState::class ); + return $this->create_state( AuthenticationRequiredState::class ); } // All good. Proceed to processed state. - $next_state = $this->create_state( CapturedState::class ); + $next_state = $this->create_state( ProcessedState::class ); return $next_state->complete_processing(); } diff --git a/src/Internal/Payment/State/CapturedState.php b/src/Internal/Payment/State/ProcessedState.php similarity index 94% rename from src/Internal/Payment/State/CapturedState.php rename to src/Internal/Payment/State/ProcessedState.php index ef4aa9bfc3f..379161d1397 100644 --- a/src/Internal/Payment/State/CapturedState.php +++ b/src/Internal/Payment/State/ProcessedState.php @@ -1,6 +1,6 @@ createMock( PaymentRequest::class ); - $mock_processed_state = $this->createMock( CapturedState::class ); + $mock_processed_state = $this->createMock( ProcessedState::class ); $mock_completed_state = $this->createMock( CompletedState::class ); $mock_processed_state->expects( $this->once() ) @@ -143,7 +143,7 @@ public function test_start_processing() { // Since the original create_state method is mocked, we have to manually set context. $this->mock_state_factory->expects( $this->once() ) ->method( 'create_state' ) - ->with( CapturedState::class, $this->mock_context ) + ->with( ProcessedState::class, $this->mock_context ) ->willReturn( $mock_processed_state ); // Act: start processing. @@ -176,7 +176,7 @@ public function test_start_processing_will_transition_to_error_state_when_api_ex public function test_processing_will_transition_to_auth_required_state() { $mock_request = $this->createMock( PaymentRequest::class ); - $mock_auth_state = $this->createMock( PendingAuthenticationState::class ); + $mock_auth_state = $this->createMock( AuthenticationRequiredState::class ); $this->mock_payment_request_service->expects( $this->once() ) ->method( 'create_intent' ) @@ -189,7 +189,7 @@ public function test_processing_will_transition_to_auth_required_state() { $this->mock_state_factory->expects( $this->once() ) ->method( 'create_state' ) - ->with( PendingAuthenticationState::class, $this->mock_context ) + ->with( AuthenticationRequiredState::class, $this->mock_context ) ->willReturn( $mock_auth_state ); $result = $this->mocked_sut->start_processing( $mock_request ); $this->assertSame( $mock_auth_state, $result ); diff --git a/tests/unit/src/Internal/Payment/State/CapturedStateTest.php b/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php similarity index 89% rename from tests/unit/src/Internal/Payment/State/CapturedStateTest.php rename to tests/unit/src/Internal/Payment/State/ProcessedStateTest.php index 71856023f13..f0a89422e9e 100644 --- a/tests/unit/src/Internal/Payment/State/CapturedStateTest.php +++ b/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php @@ -1,6 +1,6 @@ mock_order_service = $this->createMock( OrderService::class ); $this->mock_context = $this->createMock( PaymentContext::class ); - $this->sut = new CapturedState( + $this->sut = new ProcessedState( $this->mock_state_factory, $this->mock_order_service ); From 518dff16e722f23d30bb7cc60995d93e75c24f96 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Mon, 23 Oct 2023 14:31:33 +0200 Subject: [PATCH 60/66] Removed unusued variable from test --- tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php b/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php index 9232ce76a5a..d9fd41360df 100644 --- a/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php +++ b/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php @@ -68,7 +68,6 @@ protected function setUp(): void { */ public function test_process_payment_happy_path() { // Prepare all required mocks. - $mock_context = $this->createMock( PaymentContext::class ); $mock_initial_state = $this->createMock( InitialState::class ); $mock_completed_state = $this->createMock( CompletedState::class ); From e3657724d7285e15837fbd4a297d10e917217682 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Tue, 24 Oct 2023 10:07:37 +0300 Subject: [PATCH 61/66] Ignoring the workaround in Container.php --- src/Container.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Container.php b/src/Container.php index ff49bc510ca..3465d9f4d8c 100644 --- a/src/Container.php +++ b/src/Container.php @@ -16,6 +16,8 @@ use WCPay\Internal\DependencyManagement\ServiceProvider\GenericServiceProvider; use WCPay\Internal\DependencyManagement\ServiceProvider\ProxiesServiceProvider; +// @codeCoverageIgnoreStart + /** * Hides errors during update from 6.6.0 or 6.6.1 to 6.6.2. * @@ -36,6 +38,8 @@ wp_die(); } +// @codeCoverageIgnoreEnd + /** * WCPay Dependency Injection Container. * From b2c0f8bd51cfc96ff643328143cfa648bca839ac Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Thu, 26 Oct 2023 09:09:49 +0300 Subject: [PATCH 62/66] Using a single local bash file --- bin/check-src-test-coverage.sh | 1 + bin/check-test-coverage.sh | 16 +++++++++++++--- package.json | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/bin/check-src-test-coverage.sh b/bin/check-src-test-coverage.sh index 4c4cc86e6a9..5e002c3f5ba 100755 --- a/bin/check-src-test-coverage.sh +++ b/bin/check-src-test-coverage.sh @@ -10,6 +10,7 @@ docker-compose exec -u www-data wordpress \ echo "Checking coverage..." docker-compose exec -u www-data wordpress \ + php -d xdebug.remote_autostart=on \ /var/www/html/wp-content/plugins/woocommerce-payments/vendor/bin/phpunit \ --configuration /var/www/html/wp-content/plugins/woocommerce-payments/phpunit-src.xml.dist \ --coverage-html /var/www/html/php-test-coverage \ diff --git a/bin/check-test-coverage.sh b/bin/check-test-coverage.sh index e4bf6651134..ba8e6ef8b17 100755 --- a/bin/check-test-coverage.sh +++ b/bin/check-test-coverage.sh @@ -2,6 +2,14 @@ set -e +if [ "$1" == "src" ]; then + CONFIGURATION_FILE=phpunit-src.xml.dist + COVERAGE=100 +else + CONFIGURATION_FILE=phpunit.xml.dist + COVERAGE=60 +fi + echo "Installing the test environment..." docker-compose exec -u www-data wordpress \ @@ -12,6 +20,8 @@ echo "Checking coverage..." docker-compose exec -u www-data wordpress \ php -d xdebug.remote_autostart=on \ /var/www/html/wp-content/plugins/woocommerce-payments/vendor/bin/phpunit \ - --configuration /var/www/html/wp-content/plugins/woocommerce-payments/phpunit.xml.dist \ - --coverage-html /var/www/html/php-test-coverage - $* + --configuration "/var/www/html/wp-content/plugins/woocommerce-payments/$CONFIGURATION_FILE" \ + --coverage-html /var/www/html/php-test-coverage \ + --coverage-clover /var/www/html/clover.xml + +./vendor/bin/coverage-check docker/wordpress/clover.xml $COVERAGE diff --git a/package.json b/package.json index c1587abd83d..2f90d762764 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "test:update-snapshots": "npm run test:js -- --updateSnapshot", "test:php": "./bin/run-tests.sh", "test:php-coverage": "./bin/check-test-coverage.sh", - "test:php-coverage-src": "./bin/check-src-test-coverage.sh", + "test:php-coverage-src": "./bin/check-test-coverage.sh src", "test:php-watch": "npm run test:php -- -w", "test:qit": "npm run build:release && ./tests/qit/security.sh", "watch": "webpack --watch", From d35db3461b8358f3cb4625e077c1499a4eec5f4d Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Thu, 26 Oct 2023 09:14:52 +0300 Subject: [PATCH 63/66] Using a matrix for GH workflow --- .github/workflows/coverage.yml | 6 ++++-- bin/run-ci-tests-check-coverage.bash | 13 +++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index b1401136de9..e3f3dac0043 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -18,9 +18,11 @@ jobs: woocommerce: [ 'latest' ] wordpress: [ 'latest' ] php: [ '7.4' ] + directory: [ 'includes', 'src' ] env: - WP_VERSION: ${{ matrix.wordpress }} - WC_VERSION: ${{ matrix.woocommerce }} + WP_VERSION: ${{ matrix.wordpress }} + WC_VERSION: ${{ matrix.woocommerce }} + COVERAGE_DIR: ${{ matrix.directory }} steps: # clone the repository - uses: actions/checkout@v3 diff --git a/bin/run-ci-tests-check-coverage.bash b/bin/run-ci-tests-check-coverage.bash index cddb5183620..52cf248489d 100644 --- a/bin/run-ci-tests-check-coverage.bash +++ b/bin/run-ci-tests-check-coverage.bash @@ -7,10 +7,19 @@ IFS=$'\n\t' # set environment variables WCPAY_DIR="$GITHUB_WORKSPACE" +# determine whether to test everything, or just src, and what coverage to require +if [ "$COVERAGE_DIR" == "src" ]; then + CONFIGURATION_FILE=phpunit-src.xml.dist + COVERAGE=100 +else + CONFIGURATION_FILE=phpunit.xml.dist + COVERAGE=60 +fi + composer self-update && composer install --no-progress sudo systemctl start mysql.service bash bin/install-wp-tests.sh woocommerce_test root root localhost $WP_VERSION $WC_VERSION false echo 'Running the tests...' -bash bin/phpunit.sh -c phpunit.xml.dist --coverage-clover /tmp/clover.xml -vendor/bin/coverage-check /tmp/clover.xml 60 +bash bin/phpunit.sh -c $CONFIGURATION_FILE --coverage-clover /tmp/clover.xml +vendor/bin/coverage-check /tmp/clover.xml $COVERAGE From 50072ab08019bd9162cc69b66109cb148196ad8b Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Thu, 26 Oct 2023 09:17:36 +0300 Subject: [PATCH 64/66] Removing redundant files --- .github/workflows/src-coverage.yml | 39 ------------------------------ bin/check-src-test-coverage.sh | 20 --------------- 2 files changed, 59 deletions(-) delete mode 100644 .github/workflows/src-coverage.yml delete mode 100755 bin/check-src-test-coverage.sh diff --git a/.github/workflows/src-coverage.yml b/.github/workflows/src-coverage.yml deleted file mode 100644 index 0a72ba29ab1..00000000000 --- a/.github/workflows/src-coverage.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Code coverage (src) - -on: - pull_request - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - woocommerce-coverage: - name: Code coverage - runs-on: ubuntu-latest - strategy: - fail-fast: false - max-parallel: 10 - matrix: - woocommerce: [ 'latest' ] - wordpress: [ 'latest' ] - php: [ '7.4' ] - env: - WP_VERSION: ${{ matrix.wordpress }} - WC_VERSION: ${{ matrix.woocommerce }} - steps: - # clone the repository - - uses: actions/checkout@v3 - # enable dependencies caching - - uses: actions/cache@v3 - with: - path: ~/.cache/composer/ - key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }} - # setup PHP, but without debug extensions for reasonable performance - - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - tools: composer - coverage: xdebug2 - # run CI checks - - run: bash bin/run-ci-tests-check-src-coverage.bash diff --git a/bin/check-src-test-coverage.sh b/bin/check-src-test-coverage.sh deleted file mode 100755 index 5e002c3f5ba..00000000000 --- a/bin/check-src-test-coverage.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash - -set -e - -echo "Installing the test environment..." - -docker-compose exec -u www-data wordpress \ - /var/www/html/wp-content/plugins/woocommerce-payments/bin/install-wp-tests.sh - -echo "Checking coverage..." - -docker-compose exec -u www-data wordpress \ - php -d xdebug.remote_autostart=on \ - /var/www/html/wp-content/plugins/woocommerce-payments/vendor/bin/phpunit \ - --configuration /var/www/html/wp-content/plugins/woocommerce-payments/phpunit-src.xml.dist \ - --coverage-html /var/www/html/php-test-coverage \ - --coverage-clover /var/www/html/clover.xml - $* - -./vendor/bin/coverage-check docker/wordpress/clover.xml 100 From e5dc5a913730827dfdf40671d0e2788cd528ba02 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Thu, 26 Oct 2023 09:21:18 +0300 Subject: [PATCH 65/66] Removing another redundant file --- bin/run-ci-tests-check-src-coverage.bash | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 bin/run-ci-tests-check-src-coverage.bash diff --git a/bin/run-ci-tests-check-src-coverage.bash b/bin/run-ci-tests-check-src-coverage.bash deleted file mode 100644 index 967be3bb25f..00000000000 --- a/bin/run-ci-tests-check-src-coverage.bash +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -# set strict mode for bash -set -euo pipefail -IFS=$'\n\t' - -# set environment variables -WCPAY_DIR="$GITHUB_WORKSPACE" - -composer self-update && composer install --no-progress -sudo systemctl start mysql.service -bash bin/install-wp-tests.sh woocommerce_test root root localhost $WP_VERSION $WC_VERSION false -echo 'Running the tests...' -bash bin/phpunit.sh -c phpunit-src.xml.dist --coverage-clover /tmp/clover.xml -vendor/bin/coverage-check /tmp/clover.xml 100 - From 1b5aeea2f781f27db1d5748a0981115c63ab5376 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Thu, 26 Oct 2023 15:44:04 +0300 Subject: [PATCH 66/66] Adding a separate PHPUnit config for includes --- bin/check-test-coverage.sh | 2 +- bin/run-ci-tests-check-coverage.bash | 2 +- phpunit-includes.xml.dist | 30 ++++++++++++++++++++++++++++ phpunit-src.xml.dist | 4 ++-- phpunit.xml.dist | 14 ++----------- 5 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 phpunit-includes.xml.dist diff --git a/bin/check-test-coverage.sh b/bin/check-test-coverage.sh index ba8e6ef8b17..58a370c98b9 100755 --- a/bin/check-test-coverage.sh +++ b/bin/check-test-coverage.sh @@ -6,7 +6,7 @@ if [ "$1" == "src" ]; then CONFIGURATION_FILE=phpunit-src.xml.dist COVERAGE=100 else - CONFIGURATION_FILE=phpunit.xml.dist + CONFIGURATION_FILE=phpunit-includes.xml.dist COVERAGE=60 fi diff --git a/bin/run-ci-tests-check-coverage.bash b/bin/run-ci-tests-check-coverage.bash index 52cf248489d..c14ce1aa913 100644 --- a/bin/run-ci-tests-check-coverage.bash +++ b/bin/run-ci-tests-check-coverage.bash @@ -12,7 +12,7 @@ if [ "$COVERAGE_DIR" == "src" ]; then CONFIGURATION_FILE=phpunit-src.xml.dist COVERAGE=100 else - CONFIGURATION_FILE=phpunit.xml.dist + CONFIGURATION_FILE=phpunit-includes.xml.dist COVERAGE=60 fi diff --git a/phpunit-includes.xml.dist b/phpunit-includes.xml.dist new file mode 100644 index 00000000000..c77770a39e4 --- /dev/null +++ b/phpunit-includes.xml.dist @@ -0,0 +1,30 @@ + + + + + + ./tests/unit + ./tests/unit/multi-currency + + ./tests/unit/src + + + ./tests/unit/helpers + ./tests/unit/multi-currency + + + + + + + includes + + + diff --git a/phpunit-src.xml.dist b/phpunit-src.xml.dist index 44e91de38ae..5ca1a0c5c1c 100644 --- a/phpunit-src.xml.dist +++ b/phpunit-src.xml.dist @@ -1,4 +1,5 @@ + - + ./tests/unit/helpers ./tests/unit/src @@ -24,5 +25,4 @@ - diff --git a/phpunit.xml.dist b/phpunit.xml.dist index cf5eb5bb5de..00e9a2abd3b 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,4 +1,5 @@ + - - - - includes - src - - - src/Internal/DependencyManagement/ServiceProvider - - - - +