diff --git a/modules/ppcp-vaulting/services.php b/modules/ppcp-vaulting/services.php index f2c60328e..a0ad2d814 100644 --- a/modules/ppcp-vaulting/services.php +++ b/modules/ppcp-vaulting/services.php @@ -57,4 +57,17 @@ return array( $container->get( 'woocommerce.logger.woocommerce' ) ); }, + 'vaulting.credit-card-handler' => function(ContainerInterface $container): VaultedCreditCardHandler { + return new VaultedCreditCardHandler( + $container->get('subscription.helper'), + $container->get('vaulting.repository.payment-token'), + $container->get( 'api.factory.purchase-unit' ), + $container->get( 'api.factory.payer' ), + $container->get( 'api.factory.shipping-preference' ), + $container->get( 'api.endpoint.order' ), + $container->get( 'onboarding.environment' ), + $container->get( 'wcgateway.processor.authorized-payments' ), + $container->get( 'wcgateway.settings' ) + ); + }, ); diff --git a/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php new file mode 100644 index 000000000..1a90d57ea --- /dev/null +++ b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php @@ -0,0 +1,202 @@ +subscription_helper = $subscription_helper; + $this->payment_token_repository = $payment_token_repository; + $this->purchase_unit_factory = $purchase_unit_factory; + $this->payer_factory = $payer_factory; + $this->shipping_preference_factory = $shipping_preference_factory; + $this->order_endpoint = $order_endpoint; + $this->environment = $environment; + $this->authorized_payments_processor = $authorized_payments_processor; + $this->config = $config; + } + + public function handle_payment( + string $saved_credit_card, + WC_Order $wc_order + ): WC_Order { + + $change_payment = filter_input( INPUT_POST, 'woocommerce_change_payment', FILTER_SANITIZE_STRING ); + if($change_payment) { + if ( $this->subscription_helper->has_subscription( $wc_order->get_id() ) && $this->subscription_helper->is_subscription_change_payment() ) { + if ( $saved_credit_card ) { + update_post_meta( $wc_order->get_id(), 'payment_token_id', $saved_credit_card ); + + return $wc_order; + } + } + } + + $user_id = (int) $wc_order->get_customer_id(); + $customer = new WC_Customer( $user_id ); + $tokens = $this->payment_token_repository->all_for_user_id( (int) $customer->get_id() ); + $selected_token = null; + foreach ( $tokens as $token ) { + if ( $token->id() === $saved_credit_card ) { + $selected_token = $token; + break; + } + } + if ( ! $selected_token ) { + throw new RuntimeException('Saved card token not found.'); + } + + $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); + $payer = $this->payer_factory->from_customer( $customer ); + $shipping_preference = $this->shipping_preference_factory->from_state( + $purchase_unit, + '' + ); + + try { + $order = $this->order_endpoint->create( + array( $purchase_unit ), + $shipping_preference, + $payer, + $selected_token + ); + + $this->add_paypal_meta( $wc_order, $order, $this->environment ); + + if ( ! $order->status()->is( OrderStatus::COMPLETED ) ) { + throw new RuntimeException("Unexpected status for order {$order->id()} using a saved card: {$order->status()->name()}."); + } + + if ( ! in_array( + $order->intent(), + array( 'CAPTURE', 'AUTHORIZE' ), + true + ) ) { + throw new RuntimeException("Could neither capture nor authorize order {$order->id()} using a saved card. Status: {$order->status()->name()}. Intent: {$order->intent()}."); + } + + if ( $order->intent() === 'AUTHORIZE' ) { + $order = $this->order_endpoint->authorize( $order ); + $wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'false' ); + } + + $transaction_id = $this->get_paypal_order_transaction_id( $order ); + if ( $transaction_id ) { + $this->update_transaction_id( $transaction_id, $wc_order ); + } + + $this->handle_new_order_status( $order, $wc_order ); + + if ( $this->is_free_trial_order( $wc_order ) ) { + $this->authorized_payments_processor->void_authorizations( $order ); + $wc_order->payment_complete(); + } elseif ( $this->config->has( 'intent' ) && strtoupper( (string) $this->config->get( 'intent' ) ) === 'CAPTURE' ) { + $this->authorized_payments_processor->capture_authorized_payment( $wc_order ); + } + + return $wc_order; + } catch ( RuntimeException $error ) { + throw new RuntimeException($error->getMessage()); + } + } +} diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 63948c96f..de88b4bb3 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -95,40 +95,29 @@ return array( 'wcgateway.credit-card-gateway' => static function ( ContainerInterface $container ): CreditCardGateway { $order_processor = $container->get( 'wcgateway.order-processor' ); $settings_renderer = $container->get( 'wcgateway.settings.render' ); - $authorized_payments = $container->get( 'wcgateway.processor.authorized-payments' ); $settings = $container->get( 'wcgateway.settings' ); $module_url = $container->get( 'wcgateway.url' ); $session_handler = $container->get( 'session.handler' ); $refund_processor = $container->get( 'wcgateway.processor.refunds' ); $state = $container->get( 'onboarding.state' ); $transaction_url_provider = $container->get( 'wcgateway.transaction-url-provider' ); - $payment_token_repository = $container->get( 'vaulting.repository.payment-token' ); - $purchase_unit_factory = $container->get( 'api.factory.purchase-unit' ); - $payer_factory = $container->get( 'api.factory.payer' ); - $order_endpoint = $container->get( 'api.endpoint.order' ); $subscription_helper = $container->get( 'subscription.helper' ); $payments_endpoint = $container->get( 'api.endpoint.payments' ); $logger = $container->get( 'woocommerce.logger.woocommerce' ); - $environment = $container->get( 'onboarding.environment' ); + $vaulted_credit_card_handler = $container->get('vaulting.credit-card-handler'); return new CreditCardGateway( $settings_renderer, $order_processor, - $authorized_payments, $settings, $module_url, $session_handler, $refund_processor, $state, $transaction_url_provider, - $payment_token_repository, - $purchase_unit_factory, - $container->get( 'api.factory.shipping-preference' ), - $payer_factory, - $order_endpoint, $subscription_helper, $logger, - $environment, - $payments_endpoint + $payments_endpoint, + $vaulted_credit_card_handler ); }, 'wcgateway.card-button-gateway' => static function ( ContainerInterface $container ): CardButtonGateway { diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index 4b97120bc..67bcb014f 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -12,27 +12,17 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Gateway; use Exception; use Psr\Log\LoggerInterface; use WC_Order; -use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint; -use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; -use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; -use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; -use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; -use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Session\SessionHandler; -use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; +use WooCommerce\PayPalCommerce\Vaulting\VaultedCreditCardHandler; use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException; -use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; -use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor; -use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor; -use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait; use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer; use Psr\Container\ContainerInterface; @@ -41,8 +31,7 @@ use Psr\Container\ContainerInterface; */ class CreditCardGateway extends \WC_Payment_Gateway_CC { - use ProcessPaymentTrait, OrderMetaTrait, TransactionIdHandlingTrait, PaymentsStatusHandlingTrait, FreeTrialHandlerTrait, - GatewaySettingsRendererTrait; + use ProcessPaymentTrait, GatewaySettingsRendererTrait; const ID = 'ppcp-credit-card-gateway'; @@ -60,13 +49,6 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { */ protected $order_processor; - /** - * The processor for authorized payments. - * - * @var AuthorizedPaymentsProcessor - */ - protected $authorized_payments_processor; - /** * The settings. * @@ -74,6 +56,11 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { */ protected $config; + /** + * @var VaultedCreditCardHandler + */ + protected $vaulted_credit_card_handler; + /** * The URL to the module. * @@ -116,34 +103,6 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { */ private $payment_token_repository; - /** - * The purchase unit factory. - * - * @var PurchaseUnitFactory - */ - private $purchase_unit_factory; - - /** - * The shipping_preference factory. - * - * @var ShippingPreferenceFactory - */ - private $shipping_preference_factory; - - /** - * The payer factory. - * - * @var PayerFactory - */ - private $payer_factory; - - /** - * The order endpoint. - * - * @var OrderEndpoint - */ - private $order_endpoint; - /** * The subscription helper. * @@ -158,13 +117,6 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { */ protected $logger; - /** - * The environment. - * - * @var Environment - */ - protected $environment; - /** * The payments endpoint * @@ -177,62 +129,44 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { * * @param SettingsRenderer $settings_renderer The Settings Renderer. * @param OrderProcessor $order_processor The Order processor. - * @param AuthorizedPaymentsProcessor $authorized_payments_processor The Authorized Payments processor. * @param ContainerInterface $config The settings. * @param string $module_url The URL to the module. * @param SessionHandler $session_handler The Session Handler. * @param RefundProcessor $refund_processor The refund processor. * @param State $state The state. * @param TransactionUrlProvider $transaction_url_provider Service able to provide view transaction url base. - * @param PaymentTokenRepository $payment_token_repository The payment token repository. - * @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory. - * @param ShippingPreferenceFactory $shipping_preference_factory The shipping_preference factory. - * @param PayerFactory $payer_factory The payer factory. - * @param OrderEndpoint $order_endpoint The order endpoint. * @param SubscriptionHelper $subscription_helper The subscription helper. * @param LoggerInterface $logger The logger. - * @param Environment $environment The environment. * @param PaymentsEndpoint $payments_endpoint The payments endpoint. + * @param VaultedCreditCardHandler $vaulted_credit_card_handler The vaulted credit card handler. */ public function __construct( SettingsRenderer $settings_renderer, OrderProcessor $order_processor, - AuthorizedPaymentsProcessor $authorized_payments_processor, ContainerInterface $config, string $module_url, SessionHandler $session_handler, RefundProcessor $refund_processor, State $state, TransactionUrlProvider $transaction_url_provider, - PaymentTokenRepository $payment_token_repository, - PurchaseUnitFactory $purchase_unit_factory, - ShippingPreferenceFactory $shipping_preference_factory, - PayerFactory $payer_factory, - OrderEndpoint $order_endpoint, SubscriptionHelper $subscription_helper, LoggerInterface $logger, - Environment $environment, - PaymentsEndpoint $payments_endpoint + PaymentsEndpoint $payments_endpoint, + VaultedCreditCardHandler $vaulted_credit_card_handler ) { $this->id = self::ID; $this->settings_renderer = $settings_renderer; $this->order_processor = $order_processor; - $this->authorized_payments_processor = $authorized_payments_processor; $this->config = $config; $this->module_url = $module_url; $this->session_handler = $session_handler; $this->refund_processor = $refund_processor; $this->state = $state; $this->transaction_url_provider = $transaction_url_provider; - $this->payment_token_repository = $payment_token_repository; - $this->purchase_unit_factory = $purchase_unit_factory; - $this->shipping_preference_factory = $shipping_preference_factory; - $this->payer_factory = $payer_factory; - $this->order_endpoint = $order_endpoint; $this->subscription_helper = $subscription_helper; $this->logger = $logger; - $this->environment = $environment; $this->payments_endpoint = $payments_endpoint; + $this->vaulted_credit_card_handler = $vaulted_credit_card_handler; if ( $state->current_state() === State::STATE_ONBOARDED ) { $this->supports = array( 'refunds' ); @@ -424,101 +358,20 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { * If customer has chosen a saved credit card payment. */ $saved_credit_card = filter_input( INPUT_POST, 'saved_credit_card', FILTER_SANITIZE_STRING ); - $change_payment = filter_input( INPUT_POST, 'woocommerce_change_payment', FILTER_SANITIZE_STRING ); - if ( $saved_credit_card && ! isset( $change_payment ) ) { - - $user_id = (int) $wc_order->get_customer_id(); - $customer = new \WC_Customer( $user_id ); - $tokens = $this->payment_token_repository->all_for_user_id( (int) $customer->get_id() ); - - $selected_token = null; - foreach ( $tokens as $token ) { - if ( $token->id() === $saved_credit_card ) { - $selected_token = $token; - break; - } - } - - if ( ! $selected_token ) { - return $this->handle_payment_failure( - $wc_order, - new GatewayGenericException( new Exception( 'Saved card token not found.' ) ) - ); - } - - $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); - $payer = $this->payer_factory->from_customer( $customer ); - - $shipping_preference = $this->shipping_preference_factory->from_state( - $purchase_unit, - '' - ); - + if($saved_credit_card) { try { - $order = $this->order_endpoint->create( - array( $purchase_unit ), - $shipping_preference, - $payer, - $selected_token + $wc_order = $this->vaulted_credit_card_handler->handle_payment( + $saved_credit_card, + $wc_order ); - $this->add_paypal_meta( $wc_order, $order, $this->environment ); - - if ( ! $order->status()->is( OrderStatus::COMPLETED ) ) { - return $this->handle_payment_failure( - $wc_order, - new GatewayGenericException( new Exception( "Unexpected status for order {$order->id()} using a saved card: {$order->status()->name()}." ) ) - ); - } - - if ( ! in_array( - $order->intent(), - array( 'CAPTURE', 'AUTHORIZE' ), - true - ) ) { - return $this->handle_payment_failure( - $wc_order, - new GatewayGenericException( new Exception( "Could neither capture nor authorize order {$order->id()} using a saved card. Status: {$order->status()->name()}. Intent: {$order->intent()}." ) ) - ); - } - - if ( $order->intent() === 'AUTHORIZE' ) { - $order = $this->order_endpoint->authorize( $order ); - - $wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'false' ); - } - - $transaction_id = $this->get_paypal_order_transaction_id( $order ); - if ( $transaction_id ) { - $this->update_transaction_id( $transaction_id, $wc_order ); - } - - $this->handle_new_order_status( $order, $wc_order ); - - if ( $this->is_free_trial_order( $wc_order ) ) { - $this->authorized_payments_processor->void_authorizations( $order ); - $wc_order->payment_complete(); - } elseif ( $this->config->has( 'intent' ) && strtoupper( (string) $this->config->get( 'intent' ) ) === 'CAPTURE' ) { - $this->authorized_payments_processor->capture_authorized_payment( $wc_order ); - } - return $this->handle_payment_success( $wc_order ); - } catch ( RuntimeException $error ) { + + } catch(RuntimeException $error) { return $this->handle_payment_failure( $wc_order, $error ); } } - /** - * If customer has chosen change Subscription payment. - */ - if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) { - if ( $saved_credit_card ) { - update_post_meta( $order_id, 'payment_token_id', $saved_credit_card ); - - return $this->handle_payment_success( $wc_order ); - } - } - /** * If the WC_Order is paid through the approved webhook. */ diff --git a/patchwork.json b/patchwork.json index dd62edf7f..0236daa4b 100644 --- a/patchwork.json +++ b/patchwork.json @@ -1,5 +1,6 @@ { - "redefinable-internals": [ - "json_decode" - ] + "redefinable-internals": [ + "json_decode", + "filter_input" + ] } diff --git a/tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php new file mode 100644 index 000000000..4f751a924 --- /dev/null +++ b/tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php @@ -0,0 +1,108 @@ +settingsRenderer = Mockery::mock(SettingsRenderer::class); + $this->orderProcessor = Mockery::mock(OrderProcessor::class); + $this->config = Mockery::mock(ContainerInterface::class); + $this->moduleUrl = ''; + $this->sessionHandler = Mockery::mock(SessionHandler::class); + $this->refundProcessor = Mockery::mock(RefundProcessor::class); + $this->state = Mockery::mock(State::class); + $this->transactionUrlProvider = Mockery::mock(TransactionUrlProvider::class); + $this->subscriptionHelper = Mockery::mock(SubscriptionHelper::class); + $this->logger = Mockery::mock(LoggerInterface::class); + $this->paymentsEndpoint = Mockery::mock(PaymentsEndpoint::class); + $this->vaultedCreditCardHandler = Mockery::mock(VaultedCreditCardHandler::class); + + $this->state->shouldReceive('current_state')->andReturn(State::STATE_ONBOARDED); + $this->config->shouldReceive('has')->andReturn(true); + $this->config->shouldReceive('get')->andReturn(''); + + $this->testee = new CreditCardGateway( + $this->settingsRenderer, + $this->orderProcessor, + $this->config, + $this->moduleUrl, + $this->sessionHandler, + $this->refundProcessor, + $this->state, + $this->transactionUrlProvider, + $this->subscriptionHelper, + $this->logger, + $this->paymentsEndpoint, + $this->vaultedCreditCardHandler + ); + } + + public function testProcessPayment() + { + $wc_order = Mockery::mock(WC_Order::class); + when('wc_get_order')->justReturn($wc_order); + + $this->orderProcessor->shouldReceive('process') + ->with($wc_order) + ->andReturn(true); + $this->subscriptionHelper->shouldReceive('has_subscription') + ->andReturn(false); + $this->sessionHandler->shouldReceive('destroy_session_data')->once(); + + $result = $this->testee->process_payment(1); + $this->assertEquals('success', $result['result']); + } + + public function testProcessPaymentVaultedCard() + { + $wc_order = Mockery::mock(WC_Order::class); + when('wc_get_order')->justReturn($wc_order); + + $savedCreditCard = 'abc123'; + when('filter_input')->justReturn($savedCreditCard); + + $this->vaultedCreditCardHandler + ->shouldReceive('handle_payment') + ->with($savedCreditCard, $wc_order) + ->andReturn($wc_order); + + $this->sessionHandler->shouldReceive('destroy_session_data')->once(); + + $result = $this->testee->process_payment(1); + $this->assertEquals('success', $result['result']); + } +} diff --git a/tests/stubs/WC_Payment_Gateway_CC.php b/tests/stubs/WC_Payment_Gateway_CC.php index f2e36b984..7c7b26df6 100644 --- a/tests/stubs/WC_Payment_Gateway_CC.php +++ b/tests/stubs/WC_Payment_Gateway_CC.php @@ -3,5 +3,10 @@ declare(strict_types=1); class WC_Payment_Gateway_CC { + public function init_settings() {} + public function process_admin_options() {} + protected function get_return_url($wcOrder) { + return $wcOrder; + } }