diff --git a/modules/ppcp-subscription/services.php b/modules/ppcp-subscription/services.php index dbd78239a..b4f25c52d 100644 --- a/modules/ppcp-subscription/services.php +++ b/modules/ppcp-subscription/services.php @@ -23,12 +23,14 @@ return array( $endpoint = $container->get( 'api.endpoint.order' ); $purchase_unit_factory = $container->get( 'api.factory.purchase-unit' ); $payer_factory = $container->get( 'api.factory.payer' ); + $environment = $container->get( 'onboarding.environment' ); return new RenewalHandler( $logger, $repository, $endpoint, $purchase_unit_factory, - $payer_factory + $payer_factory, + $environment ); }, 'subscription.repository.payment-token' => static function ( ContainerInterface $container ): PaymentTokenRepository { diff --git a/modules/ppcp-subscription/src/RenewalHandler.php b/modules/ppcp-subscription/src/RenewalHandler.php index 5517f27bb..e2815baf6 100644 --- a/modules/ppcp-subscription/src/RenewalHandler.php +++ b/modules/ppcp-subscription/src/RenewalHandler.php @@ -10,21 +10,26 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Subscription; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; -use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; -use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; +use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; -use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; +use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait; +use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait; +use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait; /** * Class RenewalHandler */ class RenewalHandler { + use OrderMetaTrait; + use TransactionIdHandlingTrait; + use PaymentsStatusHandlingTrait; + /** * The logger. * @@ -60,6 +65,13 @@ class RenewalHandler { */ private $payer_factory; + /** + * The environment. + * + * @var Environment + */ + protected $environment; + /** * RenewalHandler constructor. * @@ -68,13 +80,15 @@ class RenewalHandler { * @param OrderEndpoint $order_endpoint The order endpoint. * @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory. * @param PayerFactory $payer_factory The payer factory. + * @param Environment $environment The environment. */ public function __construct( LoggerInterface $logger, PaymentTokenRepository $repository, OrderEndpoint $order_endpoint, PurchaseUnitFactory $purchase_unit_factory, - PayerFactory $payer_factory + PayerFactory $payer_factory, + Environment $environment ) { $this->logger = $logger; @@ -82,6 +96,7 @@ class RenewalHandler { $this->order_endpoint = $order_endpoint; $this->purchase_unit_factory = $purchase_unit_factory; $this->payer_factory = $payer_factory; + $this->environment = $environment; } /** @@ -90,52 +105,23 @@ class RenewalHandler { * @param \WC_Order $wc_order The WooCommerce order. */ public function renew( \WC_Order $wc_order ) { - - $this->logger->log( - 'info', - sprintf( - // translators: %d is the id of the order. - __( 'Start moneytransfer for order %d', 'woocommerce-paypal-payments' ), - (int) $wc_order->get_id() - ), - array( - 'order' => $wc_order, - ) - ); - try { $this->process_order( $wc_order ); } catch ( \Exception $error ) { - $this->logger->log( - 'error', + $this->logger->error( sprintf( - // translators: %1$d is the order number, %2$s the error message. - __( - 'An error occured while trying to renew the subscription for order %1$d: %2$s', - 'woocommerce-paypal-payments' - ), - (int) $wc_order->get_id(), + 'An error occurred while trying to renew the subscription for order %1$d: %2$s', + $wc_order->get_id(), $error->getMessage() - ), - array( - 'order' => $wc_order, ) ); return; } - $this->logger->log( - 'info', + $this->logger->info( sprintf( - // translators: %d is the order number. - __( - 'Moneytransfer for order %d is completed.', - 'woocommerce-paypal-payments' - ), - (int) $wc_order->get_id() - ), - array( - 'order' => $wc_order, + 'Renewal for order %d is completed.', + $wc_order->get_id() ) ); } @@ -164,7 +150,19 @@ class RenewalHandler { $token ); - $this->capture_order( $order, $wc_order ); + $this->add_paypal_meta( $wc_order, $order, $this->environment ); + + 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 ); } /** @@ -185,12 +183,8 @@ class RenewalHandler { if ( ! $tokens ) { $error_message = sprintf( - // translators: %d is the customer id. - __( - 'Payment failed. No payment tokens found for customer %d.', - 'woocommerce-paypal-payments' - ), - (int) $customer->get_id() + 'Payment failed. No payment tokens found for customer %d.', + $customer->get_id() ); $wc_order->update_status( @@ -198,14 +192,7 @@ class RenewalHandler { $error_message ); - $this->logger->log( - 'error', - $error_message, - array( - 'customer' => $customer, - 'order' => $wc_order, - ) - ); + $this->logger->error( $error_message ); } $subscription = function_exists( 'wcs_get_subscription' ) ? wcs_get_subscription( $wc_order->get_meta( '_subscription_renewal' ) ) : null; @@ -223,25 +210,4 @@ class RenewalHandler { return current( $tokens ); } - - /** - * If the PayPal order is captured/authorized the WooCommerce order gets updated accordingly. - * - * @param Order $order The PayPal order. - * @param \WC_Order $wc_order The related WooCommerce order. - */ - private function capture_order( Order $order, \WC_Order $wc_order ) { - - if ( $order->intent() === 'CAPTURE' && $order->status()->is( OrderStatus::COMPLETED ) ) { - $wc_order->update_status( - 'processing', - __( 'Payment received.', 'woocommerce-paypal-payments' ) - ); - } - - if ( $order->intent() === 'AUTHORIZE' ) { - $this->order_endpoint->authorize( $order ); - $wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'false' ); - } - } } diff --git a/tests/PHPUnit/Subscription/RenewalHandlerTest.php b/tests/PHPUnit/Subscription/RenewalHandlerTest.php index 09c82f3cc..fc92fcf9f 100644 --- a/tests/PHPUnit/Subscription/RenewalHandlerTest.php +++ b/tests/PHPUnit/Subscription/RenewalHandlerTest.php @@ -3,88 +3,139 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Subscription; +use Dhii\Container\Dictionary; +use Exception; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; +use WooCommerce\PayPalCommerce\ApiClient\Entity\Capture; +use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatus; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer; +use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; +use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\TestCase; use Mockery; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; class RenewalHandlerTest extends TestCase { - use MockeryPHPUnitIntegration; + use MockeryPHPUnitIntegration; - private $logger; - private $repository; - private $orderEndpoint; - private $purchaseUnitFactory; - private $payerFactory; - private $sut; + private $logger; + private $repository; + private $orderEndpoint; + private $purchaseUnitFactory; + private $payerFactory; + private $environment; + private $sut; - public function setUp(): void - { - parent::setUp(); + public function setUp(): void + { + parent::setUp(); - $this->logger = Mockery::mock(LoggerInterface::class); - $this->repository = Mockery::mock(PaymentTokenRepository::class); - $this->orderEndpoint = Mockery::mock(OrderEndpoint::class); - $this->purchaseUnitFactory = Mockery::mock(PurchaseUnitFactory::class); - $this->payerFactory = Mockery::mock(PayerFactory::class); + $this->logger = Mockery::mock(LoggerInterface::class); + $this->repository = Mockery::mock(PaymentTokenRepository::class); + $this->orderEndpoint = Mockery::mock(OrderEndpoint::class); + $this->purchaseUnitFactory = Mockery::mock(PurchaseUnitFactory::class); + $this->payerFactory = Mockery::mock(PayerFactory::class); + $this->environment = new Environment(new Dictionary([])); - $this->sut = new RenewalHandler( - $this->logger, - $this->repository, - $this->orderEndpoint, - $this->purchaseUnitFactory, - $this->payerFactory - ); - } + $this->logger->shouldReceive('error')->andReturnUsing(function ($msg) { + throw new Exception($msg); + }); + $this->logger->shouldReceive('info'); - /** - * @runInSeparateProcess - * @preserveGlobalState disabled - */ - public function testRenewProcessOrder() - { - $wcOrder = Mockery::mock(\WC_Order::class); - $customer = Mockery::mock('overload:WC_Customer'); - $token = Mockery::mock(PaymentToken::class); - $purchaseUnit = Mockery::mock(PurchaseUnit::class); - $payer = Mockery::mock(Payer::class); - $order = Mockery::mock(Order::class); + $this->sut = new RenewalHandler( + $this->logger, + $this->repository, + $this->orderEndpoint, + $this->purchaseUnitFactory, + $this->payerFactory, + $this->environment + ); + } - $this->logger->shouldReceive('log'); - $wcOrder - ->shouldReceive('get_id') - ->andReturn(1); - $wcOrder - ->shouldReceive('get_customer_id') - ->andReturn(1); - $this->repository->shouldReceive('for_user_id') - ->andReturn($token); - $customer->shouldReceive('get_id') - ->andReturn(1); - $this->purchaseUnitFactory->shouldReceive('from_wc_order') - ->andReturn($purchaseUnit); - $this->payerFactory->shouldReceive('from_customer') - ->andReturn($payer); + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testRenewProcessOrder() + { + $transactionId = 'ABC123'; + $wcOrder = Mockery::mock(\WC_Order::class); + $customer = Mockery::mock('overload:WC_Customer'); + $token = Mockery::mock(PaymentToken::class); + $payer = Mockery::mock(Payer::class); + $order = Mockery::mock(Order::class); - $this->orderEndpoint->shouldReceive('create') - ->with([$purchaseUnit], $payer, $token) - ->andReturn($order); + $capture = Mockery::mock(Capture::class); + $capture->expects('id') + ->andReturn($transactionId); + $capture->expects('status') + ->andReturn(new CaptureStatus(CaptureStatus::COMPLETED)); - $order->shouldReceive('intent') - ->andReturn('CAPTURE'); - $order->shouldReceive('status->is') - ->andReturn(true); - $wcOrder->shouldReceive('update_status'); + $payments = Mockery::mock(Payments::class); + $payments->shouldReceive('captures') + ->andReturn([$capture]); - $this->sut->renew($wcOrder); - } + $purchaseUnit = Mockery::mock(PurchaseUnit::class); + $purchaseUnit->shouldReceive('payments') + ->andReturn($payments); + + $order + ->shouldReceive('id') + ->andReturn('101'); + $order->shouldReceive('intent') + ->andReturn('CAPTURE'); + $order->shouldReceive('status->is') + ->andReturn(true); + $order + ->shouldReceive('purchase_units') + ->andReturn([$purchaseUnit]); + + $wcOrder + ->shouldReceive('get_id') + ->andReturn(1); + $wcOrder + ->shouldReceive('get_customer_id') + ->andReturn(2); + $wcOrder + ->expects('update_meta_data') + ->with(PayPalGateway::ORDER_ID_META_KEY, '101'); + $wcOrder + ->expects('update_meta_data') + ->with(PayPalGateway::INTENT_META_KEY, 'CAPTURE'); + $wcOrder + ->expects('update_meta_data') + ->with(PayPalGateway::ORDER_PAYMENT_MODE_META_KEY, 'live'); + $wcOrder + ->expects('payment_complete'); + $wcOrder + ->expects('set_transaction_id'); + + $this->repository->shouldReceive('all_for_user_id') + ->andReturn([$token]); + + $customer->shouldReceive('get_id') + ->andReturn(1); + + $this->purchaseUnitFactory->shouldReceive('from_wc_order') + ->andReturn($purchaseUnit); + $this->payerFactory->shouldReceive('from_customer') + ->andReturn($payer); + + $this->orderEndpoint->shouldReceive('create') + ->with([$purchaseUnit], $payer, $token) + ->andReturn($order); + + $wcOrder->shouldReceive('update_status'); + + $this->sut->renew($wcOrder); + } }