From 4b6e2ab2d5f9942935e0fad31789d522f5996849 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 23 Jun 2022 19:05:55 +0400 Subject: [PATCH 01/86] Store the customer id for vaulted payment method in usermeta --- modules/ppcp-api-client/src/Endpoint/IdentityToken.php | 2 +- modules/ppcp-api-client/src/Repository/CustomerRepository.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php index a01e746ac..0c27e3fe4 100644 --- a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php +++ b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php @@ -106,7 +106,7 @@ class IdentityToken { && defined( 'PPCP_FLAG_SUBSCRIPTION' ) && PPCP_FLAG_SUBSCRIPTION ) { $customer_id = $this->customer_repository->customer_id_for_user( ( $user_id ) ); - + update_user_meta( $user_id, 'ppcp_customer_id', $customer_id ); $args['body'] = wp_json_encode( array( 'customer_id' => $customer_id, diff --git a/modules/ppcp-api-client/src/Repository/CustomerRepository.php b/modules/ppcp-api-client/src/Repository/CustomerRepository.php index 99063b7e3..c4f172dc8 100644 --- a/modules/ppcp-api-client/src/Repository/CustomerRepository.php +++ b/modules/ppcp-api-client/src/Repository/CustomerRepository.php @@ -57,6 +57,6 @@ class CustomerRepository { return $guest_customer_id; } - return $this->prefix . (string) $user_id; + return get_user_meta( $user_id, 'ppcp_customer_id', true ) ?: $this->prefix . (string) $user_id; } } From 4c89caf0d9493d0c79c28b4ce5c30a77f2fa9847 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 23 Jun 2022 19:18:29 +0400 Subject: [PATCH 02/86] Fix the tests --- tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php b/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php index 1f6a30e80..6b0b2ef14 100644 --- a/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php +++ b/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php @@ -46,6 +46,7 @@ class IdentityTokenTest extends TestCase public function testGenerateForCustomerReturnsToken() { + $id = 1; define( 'PPCP_FLAG_SUBSCRIPTION', true ); $token = Mockery::mock(Token::class); $token @@ -60,6 +61,7 @@ class IdentityTokenTest extends TestCase $this->settings->shouldReceive('has')->andReturn(true); $this->settings->shouldReceive('get')->andReturn(true); $this->customer_repository->shouldReceive('customer_id_for_user')->andReturn('prefix1'); + expect('update_user_meta')->with($id, 'ppcp_customer_id', 'prefix1'); $rawResponse = [ 'body' => '{"client_token":"abc123", "expires_in":3600}', @@ -97,6 +99,7 @@ class IdentityTokenTest extends TestCase public function testGenerateForCustomerFailsBecauseWpError() { + $id = 1; $token = Mockery::mock(Token::class); $token ->expects('token')->andReturn('bearer'); @@ -111,7 +114,8 @@ class IdentityTokenTest extends TestCase $this->logger->shouldReceive('debug'); $this->settings->shouldReceive('has')->andReturn(true); $this->settings->shouldReceive('get')->andReturn(true); - $this->customer_repository->shouldReceive('customer_id_for_user'); + $this->customer_repository->shouldReceive('customer_id_for_user')->andReturn('prefix1'); + expect('update_user_meta')->with($id, 'ppcp_customer_id', 'prefix1'); $this->expectException(RuntimeException::class); $this->sut->generate_for_user(1); @@ -119,6 +123,7 @@ class IdentityTokenTest extends TestCase public function testGenerateForCustomerFailsBecauseResponseCodeIsNot200() { + $id = 1; $token = Mockery::mock(Token::class); $token ->expects('token')->andReturn('bearer'); @@ -137,7 +142,8 @@ class IdentityTokenTest extends TestCase $this->logger->shouldReceive('debug'); $this->settings->shouldReceive('has')->andReturn(true); $this->settings->shouldReceive('get')->andReturn(true); - $this->customer_repository->shouldReceive('customer_id_for_user'); + $this->customer_repository->shouldReceive('customer_id_for_user')->andReturn('prefix1'); + expect('update_user_meta')->with($id, 'ppcp_customer_id', 'prefix1'); $this->expectException(PayPalApiException::class); $this->sut->generate_for_user(1); From 036e7d0c33c5992df8b31c86f61fc2124ed3c8cc Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 27 Jun 2022 15:47:21 +0400 Subject: [PATCH 03/86] Fix button_renderer logic & the behavior for single products --- .../ppcp-button/src/Assets/SmartButton.php | 68 +++++++++---------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index d6b772fbf..64c3b6bd8 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -411,16 +411,23 @@ class SmartButton implements SmartButtonInterface { ) { add_action( $this->single_product_renderer_hook(), - array( - $this, - 'button_renderer', - ), + function () { + $product = wc_get_product(); + + if ( + is_a( $product, WC_Product::class ) + && ! $this->product_supports_payment( $product ) + ) { + + return; + } + + $this->button_renderer(); + }, 31 ); } - add_action( $this->pay_order_renderer_hook(), array( $this, 'button_renderer' ), 10 ); - $not_enabled_on_minicart = $this->settings->has( 'button_mini_cart_enabled' ) && ! $this->settings->get( 'button_mini_cart_enabled' ); if ( @@ -447,21 +454,26 @@ class SmartButton implements SmartButtonInterface { ); } - add_action( $this->checkout_button_renderer_hook(), array( $this, 'button_renderer' ), 10 ); + $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); - $not_enabled_on_cart = $this->settings->has( 'button_cart_enabled' ) && - ! $this->settings->get( 'button_cart_enabled' ); - add_action( - $this->proceed_to_checkout_button_renderer_hook(), - function() use ( $not_enabled_on_cart ) { - if ( ! is_cart() || $not_enabled_on_cart || $this->is_free_trial_cart() || $this->is_cart_price_total_zero() ) { - return; - } + if ( isset( $available_gateways['ppcp-gateway'] ) ) { + add_action( $this->pay_order_renderer_hook(), array( $this, 'button_renderer' ), 10 ); + add_action( $this->checkout_button_renderer_hook(), array( $this, 'button_renderer' ), 10 ); - $this->button_renderer(); - }, - 20 - ); + $not_enabled_on_cart = $this->settings->has( 'button_cart_enabled' ) && + ! $this->settings->get( 'button_cart_enabled' ); + add_action( + $this->proceed_to_checkout_button_renderer_hook(), + function() use ( $not_enabled_on_cart ) { + if ( ! is_cart() || $not_enabled_on_cart || $this->is_free_trial_cart() || $this->is_cart_price_total_zero() ) { + return; + } + + $this->button_renderer(); + }, + 20 + ); + } return true; } @@ -521,22 +533,6 @@ class SmartButton implements SmartButtonInterface { return; } - $product = wc_get_product(); - - if ( - ! is_checkout() && is_a( $product, WC_Product::class ) - && ! $this->product_supports_payment( $product ) - ) { - - return; - } - - $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); - - if ( ! isset( $available_gateways['ppcp-gateway'] ) ) { - return; - } - // The wrapper is needed for the loading spinner, // otherwise jQuery block() prevents buttons rendering. echo '
'; @@ -843,7 +839,7 @@ class SmartButton implements SmartButtonInterface { 'messages' => $this->message_values(), 'labels' => array( 'error' => array( - 'generic' => __( + 'generic' => __( 'Something went wrong. Please try again or choose another payment source.', 'woocommerce-paypal-payments' ), From 90c2a01350cbf27a7e8b4b1838b7dc4114f7003b Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Wed, 29 Jun 2022 16:52:19 +0400 Subject: [PATCH 04/86] Capture virtual renewal orders functionality for subscriptions --- modules/ppcp-subscription/services.php | 6 +- .../ppcp-subscription/src/RenewalHandler.php | 92 ++++++++++++++++--- .../Subscription/RenewalHandlerTest.php | 11 ++- 3 files changed, 94 insertions(+), 15 deletions(-) diff --git a/modules/ppcp-subscription/services.php b/modules/ppcp-subscription/services.php index b4f25c52d..e3d290395 100644 --- a/modules/ppcp-subscription/services.php +++ b/modules/ppcp-subscription/services.php @@ -24,13 +24,17 @@ return array( $purchase_unit_factory = $container->get( 'api.factory.purchase-unit' ); $payer_factory = $container->get( 'api.factory.payer' ); $environment = $container->get( 'onboarding.environment' ); + $settings = $container->get( 'wcgateway.settings' ); + $authorized_payments_processor = $container->get( 'wcgateway.processor.authorized-payments' ); return new RenewalHandler( $logger, $repository, $endpoint, $purchase_unit_factory, $payer_factory, - $environment + $environment, + $settings, + $authorized_payments_processor ); }, '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 2103e443d..e0021cb1f 100644 --- a/modules/ppcp-subscription/src/RenewalHandler.php +++ b/modules/ppcp-subscription/src/RenewalHandler.php @@ -10,16 +10,19 @@ 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\PaymentToken; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; use Psr\Log\LoggerInterface; +use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait; +use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** * Class RenewalHandler @@ -72,15 +75,31 @@ class RenewalHandler { */ protected $environment; + /** + * The settings + * + * @var Settings + */ + protected $settings; + + /** + * The processor for authorized payments. + * + * @var AuthorizedPaymentsProcessor + */ + protected $authorized_payments_processor; + /** * RenewalHandler constructor. * - * @param LoggerInterface $logger The logger. - * @param PaymentTokenRepository $repository The payment token repository. - * @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. + * @param LoggerInterface $logger The logger. + * @param PaymentTokenRepository $repository The payment token repository. + * @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. + * @param Settings $settings The Settings. + * @param AuthorizedPaymentsProcessor $authorized_payments_processor The Authorized Payments Processor. */ public function __construct( LoggerInterface $logger, @@ -88,15 +107,19 @@ class RenewalHandler { OrderEndpoint $order_endpoint, PurchaseUnitFactory $purchase_unit_factory, PayerFactory $payer_factory, - Environment $environment + Environment $environment, + Settings $settings, + AuthorizedPaymentsProcessor $authorized_payments_processor ) { - $this->logger = $logger; - $this->repository = $repository; - $this->order_endpoint = $order_endpoint; - $this->purchase_unit_factory = $purchase_unit_factory; - $this->payer_factory = $payer_factory; - $this->environment = $environment; + $this->logger = $logger; + $this->repository = $repository; + $this->order_endpoint = $order_endpoint; + $this->purchase_unit_factory = $purchase_unit_factory; + $this->payer_factory = $payer_factory; + $this->environment = $environment; + $this->settings = $settings; + $this->authorized_payments_processor = $authorized_payments_processor; } /** @@ -163,6 +186,14 @@ class RenewalHandler { } $this->handle_new_order_status( $order, $wc_order ); + + if ( $this->capture_authorized_downloads( $order ) && AuthorizedPaymentsProcessor::SUCCESSFUL === $this->authorized_payments_processor->process( $wc_order ) ) { + $wc_order->add_order_note( + __( 'Payment successfully captured.', 'woocommerce-paypal-payments' ) + ); + $wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'true' ); + $wc_order->update_status( 'completed' ); + } } /** @@ -213,4 +244,39 @@ class RenewalHandler { return current( $tokens ); } + + /** + * Returns if an order should be captured immediately. + * + * @param Order $order The PayPal order. + * + * @return bool + * @throws NotFoundException When a setting was not found. + */ + protected function capture_authorized_downloads( Order $order ): bool { + if ( + ! $this->settings->has( 'capture_for_virtual_only' ) + || ! $this->settings->get( 'capture_for_virtual_only' ) + ) { + return false; + } + + if ( $order->intent() === 'CAPTURE' ) { + return false; + } + + /** + * We fetch the order again as the authorize endpoint (from which the Order derives) + * drops the item's category, making it impossible to check, if purchase units contain + * physical goods. + */ + $order = $this->order_endpoint->order( $order->id() ); + + foreach ( $order->purchase_units() as $unit ) { + if ( $unit->contains_physical_goods() ) { + return false; + } + } + return true; + } } diff --git a/tests/PHPUnit/Subscription/RenewalHandlerTest.php b/tests/PHPUnit/Subscription/RenewalHandlerTest.php index 036e8fd85..3dadc4889 100644 --- a/tests/PHPUnit/Subscription/RenewalHandlerTest.php +++ b/tests/PHPUnit/Subscription/RenewalHandlerTest.php @@ -22,6 +22,8 @@ use Mockery; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; +use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; +use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; class RenewalHandlerTest extends TestCase { @@ -45,6 +47,11 @@ class RenewalHandlerTest extends TestCase $this->purchaseUnitFactory = Mockery::mock(PurchaseUnitFactory::class); $this->payerFactory = Mockery::mock(PayerFactory::class); $this->environment = new Environment(new Dictionary([])); + $authorizedPaymentProcessor = Mockery::mock(AuthorizedPaymentsProcessor::class); + $settings = Mockery::mock(Settings::class); + $settings + ->shouldReceive('has') + ->andReturnFalse(); $this->logger->shouldReceive('error')->andReturnUsing(function ($msg) { throw new Exception($msg); @@ -57,7 +64,9 @@ class RenewalHandlerTest extends TestCase $this->orderEndpoint, $this->purchaseUnitFactory, $this->payerFactory, - $this->environment + $this->environment, + $settings, + $authorizedPaymentProcessor ); } From 3472a1709f3002673f1800e1a402bd3e60d7cdd1 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 1 Jul 2022 19:01:21 +0400 Subject: [PATCH 05/86] Add PAYMENT.AUTHORIZATION.VOIDED event to a refund handler --- modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php b/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php index 7a4db8bf1..d879a2345 100644 --- a/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php +++ b/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php @@ -46,7 +46,7 @@ class PaymentCaptureRefunded implements RequestHandler { * @return string[] */ public function event_types(): array { - return array( 'PAYMENT.CAPTURE.REFUNDED' ); + return array( 'PAYMENT.CAPTURE.REFUNDED', 'PAYMENT.AUTHORIZATION.VOIDED' ); } /** From b15c03d4181b6af88e3376e5dd93e808996d7271 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 4 Jul 2022 17:30:29 +0400 Subject: [PATCH 06/86] Fix the intent value for single products. --- modules/ppcp-button/src/Assets/SmartButton.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 64c3b6bd8..37a0955bf 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -886,7 +886,9 @@ class SmartButton implements SmartButtonInterface { * @throws \WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException If a setting was not found. */ private function url(): string { - $intent = ( $this->settings->has( 'intent' ) ) ? $this->settings->get( 'intent' ) : 'capture'; + $intent = ( $this->settings->has( 'intent' ) ) ? $this->settings->get( 'intent' ) : 'capture'; + $product_intent = $this->subscription_helper->current_product_is_subscription() ? 'authorize' : $intent; + $other_context_intent = $this->subscription_helper->cart_contains_subscription() ? 'authorize' : $intent; $params = array( 'client-id' => $this->client_id, @@ -895,9 +897,7 @@ class SmartButton implements SmartButtonInterface { 'components' => implode( ',', $this->components() ), 'vault' => $this->can_save_vault_token() ? 'true' : 'false', 'commit' => is_checkout() ? 'true' : 'false', - 'intent' => ( $this->subscription_helper->cart_contains_subscription() || $this->subscription_helper->current_product_is_subscription() ) - ? 'authorize' - : $intent, + 'intent' => $this->context() === 'product' ? $product_intent : $other_context_intent, ); if ( $this->environment->current_environment_is( Environment::SANDBOX ) From 2b67884900d5528cf069c544847497b435968b88 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 5 Jul 2022 11:30:20 +0200 Subject: [PATCH 07/86] Introduce oxxo payment (WIP) --- modules/ppcp-wc-gateway/services.php | 24 +++- .../ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 75 ++++++++++ .../src/Gateway/OXXO/OXXOGateway.php | 74 ++++++++++ .../Gateway/PayUponInvoice/PayUponInvoice.php | 20 ++- .../PayUponInvoice/PayUponInvoiceGateway.php | 15 +- .../src/Helper/CheckoutHelper.php | 128 ++++++++++++++++++ .../src/Helper/PayUponInvoiceHelper.php | 99 ++------------ .../ppcp-wc-gateway/src/WCGatewayModule.php | 4 + .../PayUponInvoiceGatewayTest.php | 6 +- .../Helper/PayUponInvoiceHelperTest.php | 3 +- 10 files changed, 344 insertions(+), 104 deletions(-) create mode 100644 modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php create mode 100644 modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php create mode 100644 modules/ppcp-wc-gateway/src/Helper/CheckoutHelper.php diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 63a5a4814..ace8877ee 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -30,6 +30,8 @@ use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways; use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint; use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXO; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\FraudNet; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\FraudNetSessionId; @@ -38,6 +40,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PaymentSourceFac use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoice; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper; use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus; @@ -2155,6 +2158,9 @@ return array( $container->get( 'wcgateway.settings' ) ); }, + 'wcgateway.checkout-helper' => static function ( ContainerInterface $container ): CheckoutHelper { + return new CheckoutHelper(); + }, 'wcgateway.pay-upon-invoice-order-endpoint' => static function ( ContainerInterface $container ): PayUponInvoiceOrderEndpoint { return new PayUponInvoiceOrderEndpoint( $container->get( 'api.host' ), @@ -2175,7 +2181,8 @@ return array( $container->get( 'onboarding.environment' ), $container->get( 'wcgateway.transaction-url-provider' ), $container->get( 'woocommerce.logger.woocommerce' ), - $container->get( 'wcgateway.pay-upon-invoice-helper' ) + $container->get( 'wcgateway.pay-upon-invoice-helper' ), + $container->get( 'wcgateway.checkout-helper' ) ); }, 'wcgateway.pay-upon-invoice-fraudnet-session-id' => static function ( ContainerInterface $container ): FraudNetSessionId { @@ -2193,7 +2200,9 @@ return array( ); }, 'wcgateway.pay-upon-invoice-helper' => static function( ContainerInterface $container ): PayUponInvoiceHelper { - return new PayUponInvoiceHelper(); + return new PayUponInvoiceHelper( + $container->get( 'wcgateway.checkout-helper' ) + ); }, 'wcgateway.pay-upon-invoice-product-status' => static function( ContainerInterface $container ): PayUponInvoiceProductStatus { return new PayUponInvoiceProductStatus( @@ -2214,9 +2223,18 @@ return array( $container->get( 'wcgateway.is-ppcp-settings-page' ), $container->get( 'wcgateway.current-ppcp-settings-page-id' ), $container->get( 'wcgateway.pay-upon-invoice-product-status' ), - $container->get( 'wcgateway.pay-upon-invoice-helper' ) + $container->get( 'wcgateway.pay-upon-invoice-helper' ), + $container->get( 'wcgateway.checkout-helper' ) ); }, + 'wcgateway.oxxo' => static function( ContainerInterface $container ): OXXO { + return new OXXO( + $container->get( 'wcgateway.checkout-helper' ) + ); + }, + 'wcgateway.oxxo-gateway' => static function( ContainerInterface $container ): OXXOGateway { + return new OXXOGateway(); + }, 'wcgateway.logging.is-enabled' => function ( ContainerInterface $container ) : bool { $settings = $container->get( 'wcgateway.settings' ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php new file mode 100644 index 000000000..5645a4ff6 --- /dev/null +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -0,0 +1,75 @@ +checkout_helper = $checkout_helper; + } + + /** + * Initializes OXXO integration. + */ + public function init() { + + add_filter( + 'woocommerce_available_payment_gateways', + function ( array $methods ): array { + + if ( ! $this->checkout_allowed_for_oxxo() ) { + unset( $methods[ OXXOGateway::ID ] ); + } + + return $methods; + } + ); + } + + /** + * Checks if checkout is allowed for OXXO. + * + * @return bool + */ + private function checkout_allowed_for_oxxo(): bool { + if ( 'MXN' !== get_woocommerce_currency() ) { + return false; + } + + $billing_country = filter_input( INPUT_POST, 'country', FILTER_SANITIZE_STRING ) ?? null; + if ( $billing_country && 'MX' !== $billing_country ) { + return false; + } + + if ( ! $this->checkout_helper->is_checkout_amount_allowed( 0, 10000 ) ) { + return false; + } + + return true; + } +} diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php new file mode 100644 index 000000000..120f5a3a8 --- /dev/null +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php @@ -0,0 +1,74 @@ +id = self::ID; + + $this->method_title = __( 'OXXO', 'woocommerce-paypal-payments' ); + $this->method_description = __( 'OXXO is a Mexican chain of convenience stores.', 'woocommerce-paypal-payments' ); + + $gateway_settings = get_option( 'woocommerce_ppcp-oxxo-gateway_settings' ); + $this->title = $gateway_settings['title'] ?? $this->method_title; + $this->description = $gateway_settings['description'] ?? __( 'OXXO allows you to pay bills and online purchases in-store with cash.', 'woocommerce-paypal-payments' ); + + $this->init_form_fields(); + $this->init_settings(); + + add_action( + 'woocommerce_update_options_payment_gateways_' . $this->id, + array( + $this, + 'process_admin_options', + ) + ); + } + + /** + * Initialize the form fields. + */ + public function init_form_fields() { + $this->form_fields = array( + 'enabled' => array( + 'title' => __( 'Enable/Disable', 'woocommerce-paypal-payments' ), + 'type' => 'checkbox', + 'label' => __( 'OXXO', 'woocommerce-paypal-payments' ), + 'default' => 'no', + 'desc_tip' => true, + 'description' => __( 'Enable/Disable OXXO payment gateway.', 'woocommerce-paypal-payments' ), + ), + 'title' => array( + 'title' => __( 'Title', 'woocommerce-paypal-payments' ), + 'type' => 'text', + 'default' => $this->title, + 'desc_tip' => true, + 'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce-paypal-payments' ), + ), + 'description' => array( + 'title' => __( 'Description', 'woocommerce-paypal-payments' ), + 'type' => 'text', + 'default' => $this->description, + 'desc_tip' => true, + 'description' => __( 'This controls the description which the user sees during checkout.', 'woocommerce-paypal-payments' ), + ), + ); + } +} diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index 0ebec8c17..becb29b39 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -11,14 +11,10 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice; use Psr\Log\LoggerInterface; use WC_Order; -use WC_Order_Item; -use WC_Order_Item_Product; -use WC_Product; -use WC_Product_Variable; -use WC_Product_Variation; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PayUponInvoiceOrderEndpoint; use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Onboarding\Environment; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus; @@ -118,6 +114,13 @@ class PayUponInvoice { */ protected $pui_product_status; + /** + * The checkout helper. + * + * @var CheckoutHelper + */ + protected $checkout_helper; + /** * PayUponInvoice constructor. * @@ -133,6 +136,7 @@ class PayUponInvoice { * @param string $current_ppcp_settings_page_id Current PayPal settings page id. * @param PayUponInvoiceProductStatus $pui_product_status The PUI product status. * @param PayUponInvoiceHelper $pui_helper The PUI helper. + * @param CheckoutHelper $checkout_helper The checkout helper. */ public function __construct( string $module_url, @@ -146,7 +150,8 @@ class PayUponInvoice { bool $is_ppcp_settings_page, string $current_ppcp_settings_page_id, PayUponInvoiceProductStatus $pui_product_status, - PayUponInvoiceHelper $pui_helper + PayUponInvoiceHelper $pui_helper, + CheckoutHelper $checkout_helper ) { $this->module_url = $module_url; $this->fraud_net = $fraud_net; @@ -160,6 +165,7 @@ class PayUponInvoice { $this->current_ppcp_settings_page_id = $current_ppcp_settings_page_id; $this->pui_product_status = $pui_product_status; $this->pui_helper = $pui_helper; + $this->checkout_helper = $checkout_helper; } /** @@ -342,7 +348,7 @@ class PayUponInvoice { } $birth_date = filter_input( INPUT_POST, 'billing_birth_date', FILTER_SANITIZE_STRING ); - if ( ( $birth_date && ! $this->pui_helper->validate_birth_date( $birth_date ) ) || $birth_date === '' ) { + if ( ( $birth_date && ! $this->checkout_helper->validate_birth_date( $birth_date ) ) || $birth_date === '' ) { $errors->add( 'validation', __( 'Invalid birth date.', 'woocommerce-paypal-payments' ) ); } diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php index b6923942d..e46f1fbee 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php @@ -20,6 +20,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait; @@ -81,6 +82,13 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { */ protected $pui_helper; + /** + * The checkout helper. + * + * @var CheckoutHelper + */ + protected $checkout_helper; + /** * PayUponInvoiceGateway constructor. * @@ -91,6 +99,7 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { * @param TransactionUrlProvider $transaction_url_provider The transaction URL provider. * @param LoggerInterface $logger The logger. * @param PayUponInvoiceHelper $pui_helper The PUI helper. + * @param CheckoutHelper $checkout_helper The checkout helper. */ public function __construct( PayUponInvoiceOrderEndpoint $order_endpoint, @@ -99,7 +108,8 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { Environment $environment, TransactionUrlProvider $transaction_url_provider, LoggerInterface $logger, - PayUponInvoiceHelper $pui_helper + PayUponInvoiceHelper $pui_helper, + CheckoutHelper $checkout_helper ) { $this->id = self::ID; @@ -128,6 +138,7 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { $this->environment = $environment; $this->transaction_url_provider = $transaction_url_provider; $this->pui_helper = $pui_helper; + $this->checkout_helper = $checkout_helper; } /** @@ -198,7 +209,7 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { $pay_for_order = filter_input( INPUT_GET, 'pay_for_order', FILTER_SANITIZE_STRING ); if ( 'true' === $pay_for_order ) { - if ( ! $this->pui_helper->validate_birth_date( $birth_date ) ) { + if ( ! $this->checkout_helper->validate_birth_date( $birth_date ) ) { wc_add_notice( 'Invalid birth date.', 'error' ); return array( 'result' => 'failure', diff --git a/modules/ppcp-wc-gateway/src/Helper/CheckoutHelper.php b/modules/ppcp-wc-gateway/src/Helper/CheckoutHelper.php new file mode 100644 index 000000000..1513bd8a1 --- /dev/null +++ b/modules/ppcp-wc-gateway/src/Helper/CheckoutHelper.php @@ -0,0 +1,128 @@ +cart ?? null; + if ( $cart && ! is_checkout_pay_page() ) { + $cart_total = (float) $cart->get_total( 'numeric' ); + if ( $cart_total < $minimum || $cart_total > $maximum ) { + return false; + } + + $items = $cart->get_cart_contents(); + foreach ( $items as $item ) { + $product = wc_get_product( $item['product_id'] ); + if ( is_a( $product, WC_Product::class ) && ! $this->is_physical_product( $product ) ) { + return false; + } + } + } + + if ( is_wc_endpoint_url( 'order-pay' ) ) { + /** + * Needed for WordPress `query_vars`. + * + * @psalm-suppress InvalidGlobal + */ + global $wp; + + if ( isset( $wp->query_vars['order-pay'] ) && absint( $wp->query_vars['order-pay'] ) > 0 ) { + $order_id = absint( $wp->query_vars['order-pay'] ); + $order = wc_get_order( $order_id ); + if ( is_a( $order, WC_Order::class ) ) { + $order_total = (float) $order->get_total(); + if ( $order_total < $minimum || $order_total > $maximum ) { + return false; + } + + foreach ( $order->get_items() as $item_id => $item ) { + if ( is_a( $item, WC_Order_Item_Product::class ) ) { + $product = wc_get_product( $item->get_product_id() ); + if ( is_a( $product, WC_Product::class ) && ! $this->is_physical_product( $product ) ) { + return false; + } + } + } + } + } + } + + return true; + } + + /** + * Ensures date is valid and at least 18 years back. + * + * @param string $date The date. + * @param string $format The date format. + * @return bool + */ + public function validate_birth_date( string $date, string $format = 'Y-m-d' ): bool { + $d = DateTime::createFromFormat( $format, $date ); + if ( false === $d ) { + return false; + } + + if ( $date !== $d->format( $format ) ) { + return false; + } + + $date_time = strtotime( $date ); + if ( $date_time && time() < strtotime( '+18 years', $date_time ) ) { + return false; + } + + return true; + } + + /** + * Ensures product is ready for PUI. + * + * @param WC_Product $product WC product. + * @return bool + */ + public function is_physical_product( WC_Product $product ):bool { + if ( $product->is_downloadable() || $product->is_virtual() ) { + return false; + } + + if ( is_a( $product, WC_Product_Variable::class ) ) { + foreach ( $product->get_available_variations( 'object' ) as $variation ) { + if ( is_a( $variation, WC_Product_Variation::class ) ) { + if ( true === $variation->is_downloadable() || true === $variation->is_virtual() ) { + return false; + } + } + } + } + + return true; + } +} diff --git a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php index 32391bb0a..698bc71d5 100644 --- a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php +++ b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php @@ -9,65 +9,25 @@ declare( strict_types=1 ); namespace WooCommerce\PayPalCommerce\WcGateway\Helper; -use DateTime; -use WC_Order; -use WC_Order_Item_Product; -use WC_Product; -use WC_Product_Variable; -use WC_Product_Variation; - /** * Class PayUponInvoiceHelper */ class PayUponInvoiceHelper { /** - * Ensures date is valid and at least 18 years back. + * The checkout helper. * - * @param string $date The date. - * @param string $format The date format. - * @return bool + * @var CheckoutHelper */ - public function validate_birth_date( string $date, string $format = 'Y-m-d' ): bool { - $d = DateTime::createFromFormat( $format, $date ); - if ( false === $d ) { - return false; - } - - if ( $date !== $d->format( $format ) ) { - return false; - } - - $date_time = strtotime( $date ); - if ( $date_time && time() < strtotime( '+18 years', $date_time ) ) { - return false; - } - - return true; - } + protected $checkout_helper; /** - * Ensures product is ready for PUI. + * PayUponInvoiceHelper constructor. * - * @param WC_Product $product WC product. - * @return bool + * @param CheckoutHelper $checkout_helper The checkout helper. */ - public function product_ready_for_pui( WC_Product $product ):bool { - if ( $product->is_downloadable() || $product->is_virtual() ) { - return false; - } - - if ( is_a( $product, WC_Product_Variable::class ) ) { - foreach ( $product->get_available_variations( 'object' ) as $variation ) { - if ( is_a( $variation, WC_Product_Variation::class ) ) { - if ( true === $variation->is_downloadable() || true === $variation->is_virtual() ) { - return false; - } - } - } - } - - return true; + public function __construct( CheckoutHelper $checkout_helper ) { + $this->checkout_helper = $checkout_helper; } /** @@ -90,49 +50,8 @@ class PayUponInvoiceHelper { return false; } - $cart = WC()->cart ?? null; - if ( $cart && ! is_checkout_pay_page() ) { - $cart_total = (float) $cart->get_total( 'numeric' ); - if ( $cart_total < 5 || $cart_total > 2500 ) { - return false; - } - - $items = $cart->get_cart_contents(); - foreach ( $items as $item ) { - $product = wc_get_product( $item['product_id'] ); - if ( is_a( $product, WC_Product::class ) && ! $this->product_ready_for_pui( $product ) ) { - return false; - } - } - } - - if ( is_wc_endpoint_url( 'order-pay' ) ) { - /** - * Needed for WordPress `query_vars`. - * - * @psalm-suppress InvalidGlobal - */ - global $wp; - - if ( isset( $wp->query_vars['order-pay'] ) && absint( $wp->query_vars['order-pay'] ) > 0 ) { - $order_id = absint( $wp->query_vars['order-pay'] ); - $order = wc_get_order( $order_id ); - if ( is_a( $order, WC_Order::class ) ) { - $order_total = (float) $order->get_total(); - if ( $order_total < 5 || $order_total > 2500 ) { - return false; - } - - foreach ( $order->get_items() as $item_id => $item ) { - if ( is_a( $item, WC_Order_Item_Product::class ) ) { - $product = wc_get_product( $item->get_product_id() ); - if ( is_a( $product, WC_Product::class ) && ! $this->product_ready_for_pui( $product ) ) { - return false; - } - } - } - } - } + if ( ! $this->checkout_helper->is_checkout_amount_allowed( 5, 2500 ) ) { + return false; } return true; diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 5d85638f1..d37d3bd1f 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -231,6 +231,8 @@ class WCGatewayModule implements ModuleInterface { if ( 'DE' === $c->get( 'api.shop.country' ) && 'EUR' === get_woocommerce_currency() ) { ( $c->get( 'wcgateway.pay-upon-invoice' ) )->init(); } + + ( $c->get( 'wcgateway.oxxo' ) )->init(); } ); @@ -288,6 +290,8 @@ class WCGatewayModule implements ModuleInterface { $methods[] = $container->get( 'wcgateway.pay-upon-invoice-gateway' ); } + $methods[] = $container->get( 'wcgateway.oxxo-gateway' ); + return (array) $methods; } ); diff --git a/tests/PHPUnit/WcGateway/Gateway/PayUponInvoice/PayUponInvoiceGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/PayUponInvoice/PayUponInvoiceGatewayTest.php index ca9fe38c5..02268e917 100644 --- a/tests/PHPUnit/WcGateway/Gateway/PayUponInvoice/PayUponInvoiceGatewayTest.php +++ b/tests/PHPUnit/WcGateway/Gateway/PayUponInvoice/PayUponInvoiceGatewayTest.php @@ -13,6 +13,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\TestCase; use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper; use function Brain\Monkey\Functions\when; @@ -26,6 +27,7 @@ class PayUponInvoiceGatewayTest extends TestCase private $logger; private $testee; private $pui_helper; + private $checkout_helper; public function setUp(): void { @@ -38,6 +40,7 @@ class PayUponInvoiceGatewayTest extends TestCase $this->logger = Mockery::mock(LoggerInterface::class); $this->transaction_url_provider = Mockery::mock(TransactionUrlProvider::class); $this->pui_helper = Mockery::mock(PayUponInvoiceHelper::class); + $this->checkout_helper = Mockery::mock(CheckoutHelper::class); $this->setInitStubs(); @@ -48,7 +51,8 @@ class PayUponInvoiceGatewayTest extends TestCase $this->environment, $this->transaction_url_provider, $this->logger, - $this->pui_helper + $this->pui_helper, + $this->checkout_helper ); } diff --git a/tests/PHPUnit/WcGateway/Helper/PayUponInvoiceHelperTest.php b/tests/PHPUnit/WcGateway/Helper/PayUponInvoiceHelperTest.php index 3a1d9bdc0..a6a3ba7c2 100644 --- a/tests/PHPUnit/WcGateway/Helper/PayUponInvoiceHelperTest.php +++ b/tests/PHPUnit/WcGateway/Helper/PayUponInvoiceHelperTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\WcGateway\Helper; use DateTime; +use Mockery; use WooCommerce\PayPalCommerce\TestCase; class PayUponInvoiceHelperTest extends TestCase @@ -13,7 +14,7 @@ class PayUponInvoiceHelperTest extends TestCase */ public function testValidateBirthDate($input, $output) { - $this->assertSame((new PayUponInvoiceHelper())->validate_birth_date($input), $output); + $this->assertSame((new CheckoutHelper())->validate_birth_date($input), $output); } public function datesProvider(): array{ From c27d49d86d5df0b057cf369cbc20a7794dd803b3 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 5 Jul 2022 11:38:14 +0200 Subject: [PATCH 08/86] Fix psalm --- modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index 5645a4ff6..2bb6105d5 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -36,7 +36,7 @@ class OXXO { /** * Initializes OXXO integration. */ - public function init() { + public function init(): void { add_filter( 'woocommerce_available_payment_gateways', From 93778b15398e80958567ab883df1bdd8f03daabb Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 5 Jul 2022 15:49:59 +0200 Subject: [PATCH 09/86] Add payer action to wc order (WIP) --- .../src/Endpoint/OrderEndpoint.php | 53 +++++++++ modules/ppcp-wc-gateway/services.php | 6 +- .../ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 12 +++ .../src/Gateway/OXXO/OXXOGateway.php | 101 +++++++++++++++++- .../PayUponInvoice/PayUponInvoiceGateway.php | 2 - .../src/Helper/CheckoutHelper.php | 2 +- 6 files changed, 170 insertions(+), 6 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php index b12d1a025..56ff664cf 100644 --- a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient\Endpoint; +use stdClass; use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext; use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus; @@ -28,6 +29,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Repository\ApplicationContextRepository use WooCommerce\PayPalCommerce\ApiClient\Repository\PayPalRequestIdRepository; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper; +use WP_Error; /** * Class OrderEndpoint @@ -599,6 +601,57 @@ class OrderEndpoint { return $new_order; } + /** + * Confirms payment source. + * + * @param string $id The PayPal order ID. + * @return stdClass + * @throws PayPalApiException If the request fails. + * @throws RuntimeException If something unexpected happens. + */ + public function confirm_payment_source( string $id ): stdClass { + $bearer = $this->bearer->bearer(); + $url = trailingslashit( $this->host ) . 'v2/checkout/orders/' . $id . '/confirm-payment-source'; + + $data = array( + 'payment_source' => array( + 'oxxo' => array( + 'name' => 'jhon Doe', + 'email' => 'jhon@example.com', + 'country_code' => 'MX', + 'expiry_date' => '2022-07-06', + ), + ), + 'processing_instruction' => 'ORDER_COMPLETE_ON_PAYMENT_APPROVAL', + 'application_context' => array( + 'locale' => 'es-MX', + ), + ); + + $args = array( + 'method' => 'POST', + 'headers' => array( + 'Authorization' => 'Bearer ' . $bearer->token(), + 'Content-Type' => 'application/json', + 'Prefer' => 'return=representation', + ), + 'body' => wp_json_encode( $data ), + ); + + $response = $this->request( $url, $args ); + if ( $response instanceof WP_Error ) { + throw new RuntimeException( $response->get_error_message() ); + } + + $json = json_decode( $response['body'] ); + $status_code = (int) wp_remote_retrieve_response_code( $response ); + if ( 200 !== $status_code ) { + throw new PayPalApiException( $json, $status_code ); + } + + return $json; + } + /** * Checks if there is at least one item without shipping. * diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index ace8877ee..25775fd14 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -2233,7 +2233,11 @@ return array( ); }, 'wcgateway.oxxo-gateway' => static function( ContainerInterface $container ): OXXOGateway { - return new OXXOGateway(); + return new OXXOGateway( + $container->get( 'api.endpoint.order' ), + $container->get( 'api.factory.purchase-unit' ), + $container->get( 'woocommerce.logger.woocommerce' ) + ); }, 'wcgateway.logging.is-enabled' => function ( ContainerInterface $container ) : bool { $settings = $container->get( 'wcgateway.settings' ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index 2bb6105d5..b53cb827b 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO; +use WC_Order; use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper; /** @@ -49,6 +50,17 @@ class OXXO { return $methods; } ); + + add_filter( + 'woocommerce_thankyou_order_received_text', + function( string $message, WC_Order $order ) { + $payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' ) ?? ''; + + return $message . ' ' . $payer_action; + }, + 10, + 2 + ); } /** diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php index 120f5a3a8..7360dd14d 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php @@ -9,7 +9,12 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO; +use Psr\Log\LoggerInterface; use WC_Payment_Gateway; +use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; +use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; +use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; +use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; /** * Class PayUponInvoiceGateway. @@ -18,9 +23,38 @@ class OXXOGateway extends WC_Payment_Gateway { const ID = 'ppcp-oxxo-gateway'; /** - * OXXOGateway constructor. + * The order endpoint. + * + * @var OrderEndpoint */ - public function __construct() { + protected $order_endpoint; + + /** + * The purchase unit factory. + * + * @var PurchaseUnitFactory + */ + protected $purchase_unit_factory; + + /** + * The logger. + * + * @var LoggerInterface + */ + protected $logger; + + /** + * OXXOGateway constructor. + * + * @param OrderEndpoint $order_endpoint The order endpoint. + * @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory. + * @param LoggerInterface $logger The logger. + */ + public function __construct( + OrderEndpoint $order_endpoint, + PurchaseUnitFactory $purchase_unit_factory, + LoggerInterface $logger + ) { $this->id = self::ID; $this->method_title = __( 'OXXO', 'woocommerce-paypal-payments' ); @@ -40,6 +74,10 @@ class OXXOGateway extends WC_Payment_Gateway { 'process_admin_options', ) ); + + $this->order_endpoint = $order_endpoint; + $this->purchase_unit_factory = $purchase_unit_factory; + $this->logger = $logger; } /** @@ -71,4 +109,63 @@ class OXXOGateway extends WC_Payment_Gateway { ), ); } + + /** + * Processes the order. + * + * @param int $order_id The WC order ID. + * @return array + */ + public function process_payment( $order_id ) { + $wc_order = wc_get_order( $order_id ); + $wc_order->update_status( 'on-hold', __( 'Awaiting OXXO payment.', 'woocommerce-paypal-payments' ) ); + + $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); + + try { + $order = $this->order_endpoint->create( array( $purchase_unit ) ); + $payment_method = $this->order_endpoint->confirm_payment_source( $order->id() ); + + foreach ( $payment_method->links as $link ) { + if ( $link->rel === 'payer-action' ) { + $wc_order->add_meta_data( 'ppcp_oxxo_payer_action', $link->href ); + $wc_order->save_meta_data(); + } + } + } catch ( RuntimeException $exception ) { + $error = $exception->getMessage(); + + if ( is_a( $exception, PayPalApiException::class ) && is_array( $exception->details() ) ) { + $details = ''; + foreach ( $exception->details() as $detail ) { + $issue = $detail->issue ?? ''; + $field = $detail->field ?? ''; + $description = $detail->description ?? ''; + $details .= $issue . ' ' . $field . ' ' . $description . '
'; + } + + $error = $details; + } + + $this->logger->error( $error ); + wc_add_notice( $error, 'error' ); + + $wc_order->update_status( + 'failed', + $error + ); + + return array( + 'result' => 'failure', + 'redirect' => wc_get_checkout_url(), + ); + } + + WC()->cart->empty_cart(); + + return array( + 'result' => 'success', + 'redirect' => $this->get_return_url( $wc_order ), + ); + } } diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php index e46f1fbee..426eff134 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php @@ -12,9 +12,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice; use Psr\Log\LoggerInterface; use RuntimeException; use WC_Order; -use WC_Order_Item_Product; use WC_Payment_Gateway; -use WC_Product; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PayUponInvoiceOrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; diff --git a/modules/ppcp-wc-gateway/src/Helper/CheckoutHelper.php b/modules/ppcp-wc-gateway/src/Helper/CheckoutHelper.php index 1513bd8a1..a6ef08cd6 100644 --- a/modules/ppcp-wc-gateway/src/Helper/CheckoutHelper.php +++ b/modules/ppcp-wc-gateway/src/Helper/CheckoutHelper.php @@ -103,7 +103,7 @@ class CheckoutHelper { } /** - * Ensures product is ready for PUI. + * Ensures product is neither downloadable nor virtual. * * @param WC_Product $product WC product. * @return bool From 631529adcefc378003a8973df1714def9c349318 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 5 Jul 2022 16:05:23 +0200 Subject: [PATCH 10/86] Set oxxo payment source from wc order --- .../ppcp-api-client/src/Endpoint/OrderEndpoint.php | 12 +++--------- .../ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php | 9 ++++++++- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php index 56ff664cf..081dd18c5 100644 --- a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php @@ -605,23 +605,17 @@ class OrderEndpoint { * Confirms payment source. * * @param string $id The PayPal order ID. + * @param array $payment_source The payment source. * @return stdClass * @throws PayPalApiException If the request fails. * @throws RuntimeException If something unexpected happens. */ - public function confirm_payment_source( string $id ): stdClass { + public function confirm_payment_source( string $id, array $payment_source ): stdClass { $bearer = $this->bearer->bearer(); $url = trailingslashit( $this->host ) . 'v2/checkout/orders/' . $id . '/confirm-payment-source'; $data = array( - 'payment_source' => array( - 'oxxo' => array( - 'name' => 'jhon Doe', - 'email' => 'jhon@example.com', - 'country_code' => 'MX', - 'expiry_date' => '2022-07-06', - ), - ), + 'payment_source' => $payment_source, 'processing_instruction' => 'ORDER_COMPLETE_ON_PAYMENT_APPROVAL', 'application_context' => array( 'locale' => 'es-MX', diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php index 7360dd14d..fcfc50e3c 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php @@ -124,7 +124,14 @@ class OXXOGateway extends WC_Payment_Gateway { try { $order = $this->order_endpoint->create( array( $purchase_unit ) ); - $payment_method = $this->order_endpoint->confirm_payment_source( $order->id() ); + $payment_source = array( + 'oxxo' => array( + 'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(), + 'email' => $wc_order->get_billing_email(), + 'country_code' => $wc_order->get_billing_country(), + ), + ); + $payment_method = $this->order_endpoint->confirm_payment_source( $order->id(), $payment_source ); foreach ( $payment_method->links as $link ) { if ( $link->rel === 'payer-action' ) { From faa406fbd89aedd666d7498a8f1843ef5594ffa9 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 6 Jul 2022 10:35:53 +0200 Subject: [PATCH 11/86] Introduce payment capture pending webhook (WIP) --- .../ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 7 +- .../src/Gateway/OXXO/OXXOGateway.php | 2 - modules/ppcp-webhooks/services.php | 2 + .../src/Handler/PaymentCapturePending.php | 79 +++++++++++++++++++ 4 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index b53cb827b..e1769860e 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -56,7 +56,12 @@ class OXXO { function( string $message, WC_Order $order ) { $payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' ) ?? ''; - return $message . ' ' . $payer_action; + $button = ''; + if ( $payer_action ) { + $button = '

See OXXO Voucher/Ticket

'; + } + + return $message . ' ' . $button; }, 10, 2 diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php index fcfc50e3c..a222992a3 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php @@ -119,7 +119,6 @@ class OXXOGateway extends WC_Payment_Gateway { public function process_payment( $order_id ) { $wc_order = wc_get_order( $order_id ); $wc_order->update_status( 'on-hold', __( 'Awaiting OXXO payment.', 'woocommerce-paypal-payments' ) ); - $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); try { @@ -132,7 +131,6 @@ class OXXOGateway extends WC_Payment_Gateway { ), ); $payment_method = $this->order_endpoint->confirm_payment_source( $order->id(), $payment_source ); - foreach ( $payment_method->links as $link ) { if ( $link->rel === 'payer-action' ) { $wc_order->add_meta_data( 'ppcp_oxxo_payer_action', $link->href ); diff --git a/modules/ppcp-webhooks/services.php b/modules/ppcp-webhooks/services.php index 0b29eb718..02703e8b3 100644 --- a/modules/ppcp-webhooks/services.php +++ b/modules/ppcp-webhooks/services.php @@ -20,6 +20,7 @@ use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulationStateEndpoint; use WooCommerce\PayPalCommerce\Webhooks\Handler\CheckoutOrderApproved; use WooCommerce\PayPalCommerce\Webhooks\Handler\CheckoutOrderCompleted; use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureCompleted; +use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCapturePending; use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureRefunded; use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureReversed; use Psr\Container\ContainerInterface; @@ -78,6 +79,7 @@ return array( new PaymentCaptureCompleted( $logger, $prefix, $order_endpoint ), new VaultPaymentTokenCreated( $logger, $prefix, $authorized_payments_processor ), new VaultCreditCardCreated( $logger, $prefix ), + new PaymentCapturePending( $logger ), ); }, diff --git a/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php b/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php new file mode 100644 index 000000000..8fe28a24e --- /dev/null +++ b/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php @@ -0,0 +1,79 @@ +logger = $logger; + } + + /** + * The event types a handler handles. + * + * @return string[] + */ + public function event_types(): array { + return array( 'PAYMENT.CAPTURE.PENDING' ); + } + + /** + * Whether a handler is responsible for a given request or not. + * + * @param \WP_REST_Request $request The request. + * + * @return bool + */ + public function responsible_for_request( \WP_REST_Request $request ): bool { + return in_array( $request['event_type'], $this->event_types(), true ); + } + + /** + * Responsible for handling the request. + * + * @param \WP_REST_Request $request The request. + * + * @return WP_REST_Response + */ + public function handle_request( \WP_REST_Request $request ): WP_REST_Response { + $resource = $request['resource']; + if ( ! is_array( $resource ) ) { + $message = 'Resource data not found in webhook request.'; + $this->logger->warning( $message, array( 'request' => $request ) ); + $response['message'] = $message; + return new WP_REST_Response( $response ); + } + + $this->logger->info( wc_print_r( $resource, true ) ); + + $response['success'] = true; + return new WP_REST_Response( $response ); + } +} From 8da7bbfc872047991a3fcbc842f5950c87b5f766 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 6 Jul 2022 10:50:52 +0200 Subject: [PATCH 12/86] Fix psalm --- modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php b/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php index 8fe28a24e..18ebef228 100644 --- a/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php +++ b/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php @@ -63,6 +63,7 @@ class PaymentCapturePending implements RequestHandler { * @return WP_REST_Response */ public function handle_request( \WP_REST_Request $request ): WP_REST_Response { + $response = array( 'success' => false ); $resource = $request['resource']; if ( ! is_array( $resource ) ) { $message = 'Resource data not found in webhook request.'; @@ -71,7 +72,7 @@ class PaymentCapturePending implements RequestHandler { return new WP_REST_Response( $response ); } - $this->logger->info( wc_print_r( $resource, true ) ); + $this->logger->info( (string) wc_print_r( $resource, true ) ); $response['success'] = true; return new WP_REST_Response( $response ); From 1336180fd9f0052ddcca7b6bfcfb361c595e7685 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 8 Jul 2022 19:52:22 +0400 Subject: [PATCH 13/86] fix PHPcs problems --- modules/ppcp-subscription/src/RenewalHandler.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-subscription/src/RenewalHandler.php b/modules/ppcp-subscription/src/RenewalHandler.php index e08ed9e1b..44b99b7b9 100644 --- a/modules/ppcp-subscription/src/RenewalHandler.php +++ b/modules/ppcp-subscription/src/RenewalHandler.php @@ -100,13 +100,13 @@ class RenewalHandler { /** * RenewalHandler constructor. * - * @param LoggerInterface $logger The logger. - * @param PaymentTokenRepository $repository The payment token repository. - * @param OrderEndpoint $order_endpoint The order endpoint. - * @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 Environment $environment The environment. + * @param LoggerInterface $logger The logger. + * @param PaymentTokenRepository $repository The payment token repository. + * @param OrderEndpoint $order_endpoint The order endpoint. + * @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 Environment $environment The environment. * @param Settings $settings The Settings. * @param AuthorizedPaymentsProcessor $authorized_payments_processor The Authorized Payments Processor. */ @@ -126,7 +126,7 @@ class RenewalHandler { $this->repository = $repository; $this->order_endpoint = $order_endpoint; $this->purchase_unit_factory = $purchase_unit_factory; - $this->shipping_preference_factory = $shipping_preference_factory; + $this->shipping_preference_factory = $shipping_preference_factory; $this->payer_factory = $payer_factory; $this->environment = $environment; $this->settings = $settings; From cbe10b5f6efda082f7d620f5028501299ac6aba2 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 11 Jul 2022 12:30:29 +0200 Subject: [PATCH 14/86] Fix merge issues --- .../src/Endpoint/OrderEndpoint.php | 47 +++++++++++++++++++ modules/ppcp-wc-gateway/services.php | 1 + .../src/Gateway/OXXO/OXXOGateway.php | 30 +++++++++--- .../Gateway/PayUponInvoice/PayUponInvoice.php | 13 +++-- 4 files changed, 77 insertions(+), 14 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php index 41448a542..abefeb38a 100644 --- a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient\Endpoint; +use stdClass; use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext; use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus; @@ -28,6 +29,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Repository\ApplicationContextRepository use WooCommerce\PayPalCommerce\ApiClient\Repository\PayPalRequestIdRepository; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper; +use WP_Error; /** * Class OrderEndpoint @@ -564,4 +566,49 @@ class OrderEndpoint { $new_order = $this->order( $order_to_update->id() ); return $new_order; } + + /** + * Confirms payment source. + * + * @param string $id The PayPal order ID. + * @param array $payment_source The payment source. + * @return stdClass + * @throws PayPalApiException If the request fails. + * @throws RuntimeException If something unexpected happens. + */ + public function confirm_payment_source( string $id, array $payment_source ): stdClass { + $bearer = $this->bearer->bearer(); + $url = trailingslashit( $this->host ) . 'v2/checkout/orders/' . $id . '/confirm-payment-source'; + + $data = array( + 'payment_source' => $payment_source, + 'processing_instruction' => 'ORDER_COMPLETE_ON_PAYMENT_APPROVAL', + 'application_context' => array( + 'locale' => 'es-MX', + ), + ); + + $args = array( + 'method' => 'POST', + 'headers' => array( + 'Authorization' => 'Bearer ' . $bearer->token(), + 'Content-Type' => 'application/json', + 'Prefer' => 'return=representation', + ), + 'body' => wp_json_encode( $data ), + ); + + $response = $this->request( $url, $args ); + if ( $response instanceof WP_Error ) { + throw new RuntimeException( $response->get_error_message() ); + } + + $json = json_decode( $response['body'] ); + $status_code = (int) wp_remote_retrieve_response_code( $response ); + if ( 200 !== $status_code ) { + throw new PayPalApiException( $json, $status_code ); + } + + return $json; + } } diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index aae86676b..6eb0cc5c2 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -2238,6 +2238,7 @@ return array( return new OXXOGateway( $container->get( 'api.endpoint.order' ), $container->get( 'api.factory.purchase-unit' ), + $container->get( 'api.factory.shipping-preference' ), $container->get( 'woocommerce.logger.woocommerce' ) ); }, diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php index a222992a3..681cea86c 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php @@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; +use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; /** * Class PayUponInvoiceGateway. @@ -36,6 +37,13 @@ class OXXOGateway extends WC_Payment_Gateway { */ protected $purchase_unit_factory; + /** + * The shipping preference factory. + * + * @var ShippingPreferenceFactory + */ + protected $shipping_preference_factory; + /** * The logger. * @@ -46,13 +54,15 @@ class OXXOGateway extends WC_Payment_Gateway { /** * OXXOGateway constructor. * - * @param OrderEndpoint $order_endpoint The order endpoint. - * @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory. - * @param LoggerInterface $logger The logger. + * @param OrderEndpoint $order_endpoint The order endpoint. + * @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory. + * @param ShippingPreferenceFactory $shipping_preference_factory The shipping preference factory. + * @param LoggerInterface $logger The logger. */ public function __construct( OrderEndpoint $order_endpoint, PurchaseUnitFactory $purchase_unit_factory, + ShippingPreferenceFactory $shipping_preference_factory, LoggerInterface $logger ) { $this->id = self::ID; @@ -75,9 +85,10 @@ class OXXOGateway extends WC_Payment_Gateway { ) ); - $this->order_endpoint = $order_endpoint; - $this->purchase_unit_factory = $purchase_unit_factory; - $this->logger = $logger; + $this->order_endpoint = $order_endpoint; + $this->purchase_unit_factory = $purchase_unit_factory; + $this->shipping_preference_factory = $shipping_preference_factory; + $this->logger = $logger; } /** @@ -122,7 +133,12 @@ class OXXOGateway extends WC_Payment_Gateway { $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); try { - $order = $this->order_endpoint->create( array( $purchase_unit ) ); + $shipping_preference = $this->shipping_preference_factory->from_state( + $purchase_unit, + 'checkout' + ); + + $order = $this->order_endpoint->create( array( $purchase_unit ), $shipping_preference ); $payment_source = array( 'oxxo' => array( 'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(), diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index 87f89997a..d3941282a 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -16,6 +16,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\CaptureFactory; use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus; @@ -116,11 +117,9 @@ class PayUponInvoice { protected $pui_product_status; /** - * The capture factory. - * - * @var CaptureFactory + * @var CheckoutHelper */ - protected $capture_factory; + protected $checkout_helper; /** * PayUponInvoice constructor. @@ -137,7 +136,7 @@ class PayUponInvoice { * @param string $current_ppcp_settings_page_id Current PayPal settings page id. * @param PayUponInvoiceProductStatus $pui_product_status The PUI product status. * @param PayUponInvoiceHelper $pui_helper The PUI helper. - * @param CaptureFactory $capture_factory The capture factory. + * @param CheckoutHelper $checkout_helper The checkout helper. */ public function __construct( string $module_url, @@ -152,7 +151,7 @@ class PayUponInvoice { string $current_ppcp_settings_page_id, PayUponInvoiceProductStatus $pui_product_status, PayUponInvoiceHelper $pui_helper, - CaptureFactory $capture_factory + CheckoutHelper $checkout_helper ) { $this->module_url = $module_url; $this->fraud_net = $fraud_net; @@ -166,7 +165,7 @@ class PayUponInvoice { $this->current_ppcp_settings_page_id = $current_ppcp_settings_page_id; $this->pui_product_status = $pui_product_status; $this->pui_helper = $pui_helper; - $this->capture_factory = $capture_factory; + $this->checkout_helper = $checkout_helper; } /** From e1ff5c240ba770866d8d238af7f1447bd8ac739c Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 11 Jul 2022 12:54:43 +0200 Subject: [PATCH 15/86] Fix merge conflicts --- .../src/Gateway/PayUponInvoice/PayUponInvoice.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index d3941282a..00290ebec 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -359,7 +359,7 @@ class PayUponInvoice { } $birth_date = filter_input( INPUT_POST, 'billing_birth_date', FILTER_SANITIZE_STRING ); - if ( ( $birth_date && ! $this->pui_helper->validate_birth_date( $birth_date ) ) || $birth_date === '' ) { + if ( ( $birth_date && ! $this->checkout_helper->validate_birth_date( $birth_date ) ) || $birth_date === '' ) { $errors->add( 'validation', __( 'Invalid birth date.', 'woocommerce-paypal-payments' ) ); } From 966e3169e45bd0235fda2c6bcfbb0f13b6ec421f Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 12 Jul 2022 12:30:57 +0300 Subject: [PATCH 16/86] Refactor gateway error/success handling --- .../src/Exception/GatewayGenericException.php | 31 +++ .../src/Gateway/ProcessPaymentTrait.php | 192 ++++++++++-------- .../WcGateway/Gateway/WcGatewayTest.php | 10 +- 3 files changed, 146 insertions(+), 87 deletions(-) create mode 100644 modules/ppcp-wc-gateway/src/Exception/GatewayGenericException.php diff --git a/modules/ppcp-wc-gateway/src/Exception/GatewayGenericException.php b/modules/ppcp-wc-gateway/src/Exception/GatewayGenericException.php new file mode 100644 index 000000000..b43a69269 --- /dev/null +++ b/modules/ppcp-wc-gateway/src/Exception/GatewayGenericException.php @@ -0,0 +1,31 @@ +getCode() : 0, + $inner + ); + } +} diff --git a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php index 10e0e1313..cb4b4b5f7 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php +++ b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php @@ -10,12 +10,15 @@ declare( strict_types=1 ); namespace WooCommerce\PayPalCommerce\WcGateway\Gateway; use Exception; +use Throwable; +use WC_Order; use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; +use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait; @@ -38,20 +41,12 @@ trait ProcessPaymentTrait { * @throws RuntimeException When processing payment fails. */ public function process_payment( $order_id ) { - - $failure_data = array( - 'result' => 'failure', - 'redirect' => wc_get_checkout_url(), - ); - $wc_order = wc_get_order( $order_id ); - if ( ! is_a( $wc_order, \WC_Order::class ) ) { - wc_add_notice( - __( 'Couldn\'t find order to process', 'woocommerce-paypal-payments' ), - 'error' + if ( ! is_a( $wc_order, WC_Order::class ) ) { + return $this->handle_payment_failure( + null, + new GatewayGenericException( new Exception( 'WC order was not found.' ) ) ); - - return $failure_data; } $payment_method = filter_input( INPUT_POST, 'payment_method', FILTER_SANITIZE_STRING ); @@ -77,7 +72,10 @@ trait ProcessPaymentTrait { } if ( ! $selected_token ) { - return null; + 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 ); @@ -99,8 +97,10 @@ trait ProcessPaymentTrait { $this->add_paypal_meta( $wc_order, $order, $this->environment() ); if ( ! $order->status()->is( OrderStatus::COMPLETED ) ) { - $this->logger->warning( "Unexpected status for order {$order->id()} using a saved credit card: " . $order->status()->name() ); - return null; + 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( @@ -108,8 +108,10 @@ trait ProcessPaymentTrait { array( 'CAPTURE', 'AUTHORIZE' ), true ) ) { - $this->logger->warning( "Could neither capture nor authorize order {$order->id()} using a saved credit card:" . 'Status: ' . $order->status()->name() . ' Intent: ' . $order->intent() ); - return null; + 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' ) { @@ -132,14 +134,9 @@ trait ProcessPaymentTrait { $this->authorized_payments_processor->capture_authorized_payment( $wc_order ); } - $this->session_handler->destroy_session_data(); - return array( - 'result' => 'success', - 'redirect' => $this->get_return_url( $wc_order ), - ); + return $this->handle_payment_success( $wc_order ); } catch ( RuntimeException $error ) { - $this->handle_failure( $wc_order, $error ); - return null; + return $this->handle_payment_failure( $wc_order, $error ); } } @@ -152,17 +149,12 @@ trait ProcessPaymentTrait { return isset( $token->source()->paypal ); } ) ) { - $this->handle_failure( $wc_order, new Exception( 'No saved PayPal account.' ) ); - return null; + return $this->handle_payment_failure( $wc_order, new Exception( 'No saved PayPal account.' ) ); } $wc_order->payment_complete(); - $this->session_handler->destroy_session_data(); - return array( - 'result' => 'success', - 'redirect' => $this->get_return_url( $wc_order ), - ); + return $this->handle_payment_success( $wc_order ); } /** @@ -172,35 +164,23 @@ trait ProcessPaymentTrait { if ( 'ppcp-credit-card-gateway' === $this->id && $saved_credit_card ) { update_post_meta( $order_id, 'payment_token_id', $saved_credit_card ); - $this->session_handler->destroy_session_data(); - return array( - 'result' => 'success', - 'redirect' => $this->get_return_url( $wc_order ), - ); + return $this->handle_payment_success( $wc_order ); } $saved_paypal_payment = filter_input( INPUT_POST, 'saved_paypal_payment', FILTER_SANITIZE_STRING ); if ( 'ppcp-gateway' === $this->id && $saved_paypal_payment ) { update_post_meta( $order_id, 'payment_token_id', $saved_paypal_payment ); - $this->session_handler->destroy_session_data(); - return array( - 'result' => 'success', - 'redirect' => $this->get_return_url( $wc_order ), - ); + return $this->handle_payment_success( $wc_order ); } } /** - * If the WC_Order is payed through the approved webhook. + * If the WC_Order is paid through the approved webhook. */ //phpcs:disable WordPress.Security.NonceVerification.Recommended if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) { - $this->session_handler->destroy_session_data(); - return array( - 'result' => 'success', - 'redirect' => $this->get_return_url( $wc_order ), - ); + return $this->handle_payment_success( $wc_order ); } //phpcs:enable WordPress.Security.NonceVerification.Recommended @@ -218,13 +198,8 @@ trait ProcessPaymentTrait { ); } - WC()->cart->empty_cart(); - $this->session_handler->destroy_session_data(); - - return array( - 'result' => 'success', - 'redirect' => $this->get_return_url( $wc_order ), - ); + WC()->cart->empty_cart(); // Probably redundant. + return $this->handle_payment_success( $wc_order ); } } catch ( PayPalApiException $error ) { if ( $error->has_detail( 'INSTRUMENT_DECLINED' ) ) { @@ -234,17 +209,20 @@ trait ProcessPaymentTrait { ); $this->session_handler->increment_insufficient_funding_tries(); + if ( $this->session_handler->insufficient_funding_tries() >= 3 ) { + return $this->handle_payment_failure( + null, + new Exception( + __( 'Please use a different payment method.', 'woocommerce-paypal-payments' ), + $error->getCode(), + $error + ) + ); + } + $host = $this->config->has( 'sandbox_on' ) && $this->config->get( 'sandbox_on' ) ? 'https://www.sandbox.paypal.com/' : 'https://www.paypal.com/'; $url = $host . 'checkoutnow?token=' . $this->session_handler->order()->id(); - if ( $this->session_handler->insufficient_funding_tries() >= 3 ) { - $this->session_handler->destroy_session_data(); - wc_add_notice( - __( 'Please use a different payment method.', 'woocommerce-paypal-payments' ), - 'error' - ); - return $failure_data; - } return array( 'result' => 'success', 'redirect' => $url, @@ -262,25 +240,25 @@ trait ProcessPaymentTrait { ) ); } - wc_add_notice( $error_message, 'error' ); - $this->session_handler->destroy_session_data(); + return $this->handle_payment_failure( + $wc_order, + new Exception( + $error_message, + $error->getCode(), + $error + ) + ); } catch ( RuntimeException $error ) { - $this->handle_failure( $wc_order, $error ); - return $failure_data; + return $this->handle_payment_failure( $wc_order, $error ); } - wc_add_notice( - $this->order_processor->last_error(), - 'error' + return $this->handle_payment_failure( + $wc_order, + new Exception( + $this->order_processor->last_error() + ) ); - - $wc_order->update_status( - 'failed', - __( 'Could not process order. ', 'woocommerce-paypal-payments' ) . $this->order_processor->last_error() - ); - - return $failure_data; } /** @@ -314,20 +292,66 @@ trait ProcessPaymentTrait { /** * Handles the payment failure. * - * @param \WC_Order $wc_order The order. - * @param Exception $error The error causing the failure. + * @param WC_Order|null $wc_order The order. + * @param Exception $error The error causing the failure. + * @return array The data that can be returned by the gateway process_payment method. */ - protected function handle_failure( \WC_Order $wc_order, Exception $error ): void { - $this->logger->error( 'Payment failed: ' . $error->getMessage() ); + protected function handle_payment_failure( ?WC_Order $wc_order, Exception $error ): array { + $this->logger->error( 'Payment failed: ' . $this->format_exception( $error ) ); - $wc_order->update_status( - 'failed', - __( 'Could not process order. ', 'woocommerce-paypal-payments' ) . $error->getMessage() - ); + if ( $wc_order ) { + $wc_order->update_status( + 'failed', + $this->format_exception( $error ) + ); + } $this->session_handler->destroy_session_data(); wc_add_notice( $error->getMessage(), 'error' ); + + return array( + 'result' => 'failure', + 'redirect' => wc_get_checkout_url(), + ); + } + + /** + * Handles the payment completion. + * + * @param WC_Order|null $wc_order The order. + * @param string|null $url The redirect URL. + * @return array The data that can be returned by the gateway process_payment method. + */ + protected function handle_payment_success( ?WC_Order $wc_order, string $url = null ): array { + if ( ! $url ) { + $url = $this->get_return_url( $wc_order ); + } + + $this->session_handler->destroy_session_data(); + + return array( + 'result' => 'success', + 'redirect' => $url, + ); + } + + /** + * Outputs the exception, including the inner exception. + * + * @param Throwable $exception The exception to format. + * @return string + */ + protected function format_exception( Throwable $exception ): string { + $output = $exception->getMessage() . ' ' . $exception->getFile() . ':' . $exception->getLine(); + $prev = $exception->getPrevious(); + if ( ! $prev ) { + return $output; + } + if ( $exception instanceof GatewayGenericException ) { + $output = ''; + } + return $output . ' ' . $this->format_exception( $prev ); } /** diff --git a/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php index b9b2a87cb..b33e705e0 100644 --- a/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php +++ b/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php @@ -87,6 +87,7 @@ class WcGatewayTest extends TestCase $this->settings->shouldReceive('has')->andReturnFalse(); $this->logger->shouldReceive('info'); + $this->logger->shouldReceive('error'); } private function createGateway() @@ -173,8 +174,10 @@ class WcGatewayTest extends TestCase when('wc_get_checkout_url') ->justReturn($redirectUrl); - expect('wc_add_notice') - ->with('Couldn\'t find order to process','error'); + $this->sessionHandler + ->shouldReceive('destroy_session_data'); + + expect('wc_add_notice'); $this->assertEquals( [ @@ -195,7 +198,6 @@ class WcGatewayTest extends TestCase ->andReturnFalse(); $this->orderProcessor ->expects('last_error') - ->twice() ->andReturn($lastError); $this->subscriptionHelper->shouldReceive('has_subscription')->with($orderId)->andReturn(true); $this->subscriptionHelper->shouldReceive('is_subscription_change_payment')->andReturn(true); @@ -206,6 +208,8 @@ class WcGatewayTest extends TestCase expect('wc_get_order') ->with($orderId) ->andReturn($wcOrder); + $this->sessionHandler + ->shouldReceive('destroy_session_data'); expect('wc_add_notice') ->with($lastError, 'error'); From 52c3cc72a89d03e924e6d57528bf3251220aec0f Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 12 Jul 2022 14:44:08 +0200 Subject: [PATCH 17/86] Open payer action in modal window --- modules/ppcp-wc-gateway/resources/js/oxxo.js | 15 +++++++ modules/ppcp-wc-gateway/services.php | 7 ++- .../ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 45 ++++++++++++++++++- .../Gateway/PayUponInvoice/PayUponInvoice.php | 18 ++++++-- modules/ppcp-wc-gateway/webpack.config.js | 1 + 5 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 modules/ppcp-wc-gateway/resources/js/oxxo.js diff --git a/modules/ppcp-wc-gateway/resources/js/oxxo.js b/modules/ppcp-wc-gateway/resources/js/oxxo.js new file mode 100644 index 000000000..e2b9cb254 --- /dev/null +++ b/modules/ppcp-wc-gateway/resources/js/oxxo.js @@ -0,0 +1,15 @@ +window.addEventListener('load', function() { + const oxxoButton = document.getElementById('ppcp-oxxo-payer-action'); + if(oxxoButton) { + oxxoButton.addEventListener('click', (event) => { + event.preventDefault(); + window.open( + oxxoButton.href, + '_blank', + 'popup' + ); + }); + + window.open(oxxoButton.href); + } +}); diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 6eb0cc5c2..1f865d484 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -2226,12 +2226,15 @@ return array( $container->get( 'wcgateway.current-ppcp-settings-page-id' ), $container->get( 'wcgateway.pay-upon-invoice-product-status' ), $container->get( 'wcgateway.pay-upon-invoice-helper' ), - $container->get( 'wcgateway.checkout-helper' ) + $container->get( 'wcgateway.checkout-helper' ), + $container->get( 'api.factory.capture' ) ); }, 'wcgateway.oxxo' => static function( ContainerInterface $container ): OXXO { return new OXXO( - $container->get( 'wcgateway.checkout-helper' ) + $container->get( 'wcgateway.checkout-helper' ), + $container->get( 'wcgateway.url' ), + $container->get( 'ppcp.asset-version' ) ); }, 'wcgateway.oxxo-gateway' => static function( ContainerInterface $container ): OXXOGateway { diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index e1769860e..eadb41835 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -24,14 +24,36 @@ class OXXO { */ protected $checkout_helper; + /** + * The module URL. + * + * @var string + */ + protected $module_url; + + /** + * The asset version. + * + * @var string + */ + protected $asset_version; + /** * OXXO constructor. * * @param CheckoutHelper $checkout_helper The checkout helper. + * @param string $module_url The module URL. + * @param string $asset_version The asset version. */ - public function __construct( CheckoutHelper $checkout_helper ) { + public function __construct( + CheckoutHelper $checkout_helper, + string $module_url, + string $asset_version + ) { $this->checkout_helper = $checkout_helper; + $this->module_url = $module_url; + $this->asset_version = $asset_version; } /** @@ -58,7 +80,7 @@ class OXXO { $button = ''; if ( $payer_action ) { - $button = '

See OXXO Voucher/Ticket

'; + $button = '

See OXXO Voucher/Ticket

'; } return $message . ' ' . $button; @@ -66,6 +88,11 @@ class OXXO { 10, 2 ); + + add_action( + 'wp_enqueue_scripts', + array( $this, 'register_assets' ) + ); } /** @@ -89,4 +116,18 @@ class OXXO { return true; } + + public function register_assets(): void { + $gateway_settings = get_option( 'woocommerce_ppcp-oxxo-gateway_settings' ); + $gateway_enabled = $gateway_settings['enabled'] ?? ''; + if ( $gateway_enabled === 'yes' && is_checkout() && !empty( is_wc_endpoint_url('order-received') ) ) { + wp_enqueue_script( + 'ppcp-pay-upon-invoice', + trailingslashit($this->module_url) . 'assets/js/oxxo.js', + array(), + $this->asset_version, + true + ); + } + } } diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index 00290ebec..6ac81f118 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -117,10 +117,19 @@ class PayUponInvoice { protected $pui_product_status; /** + * The checkout helper. + * * @var CheckoutHelper */ protected $checkout_helper; + /** + * The capture factory. + * + * @var CaptureFactory + */ + protected $capture_factory; + /** * PayUponInvoice constructor. * @@ -136,7 +145,8 @@ class PayUponInvoice { * @param string $current_ppcp_settings_page_id Current PayPal settings page id. * @param PayUponInvoiceProductStatus $pui_product_status The PUI product status. * @param PayUponInvoiceHelper $pui_helper The PUI helper. - * @param CheckoutHelper $checkout_helper The checkout helper. + * @param CheckoutHelper $checkout_helper The checkout helper. + * @param CaptureFactory $capture_factory The capture factory. */ public function __construct( string $module_url, @@ -151,7 +161,8 @@ class PayUponInvoice { string $current_ppcp_settings_page_id, PayUponInvoiceProductStatus $pui_product_status, PayUponInvoiceHelper $pui_helper, - CheckoutHelper $checkout_helper + CheckoutHelper $checkout_helper, + CaptureFactory $capture_factory ) { $this->module_url = $module_url; $this->fraud_net = $fraud_net; @@ -165,7 +176,8 @@ class PayUponInvoice { $this->current_ppcp_settings_page_id = $current_ppcp_settings_page_id; $this->pui_product_status = $pui_product_status; $this->pui_helper = $pui_helper; - $this->checkout_helper = $checkout_helper; + $this->checkout_helper = $checkout_helper; + $this->capture_factory = $capture_factory; } /** diff --git a/modules/ppcp-wc-gateway/webpack.config.js b/modules/ppcp-wc-gateway/webpack.config.js index 5abb1f94a..d66aad695 100644 --- a/modules/ppcp-wc-gateway/webpack.config.js +++ b/modules/ppcp-wc-gateway/webpack.config.js @@ -8,6 +8,7 @@ module.exports = { entry: { 'gateway-settings': path.resolve('./resources/js/gateway-settings.js'), 'pay-upon-invoice': path.resolve('./resources/js/pay-upon-invoice.js'), + 'oxxo': path.resolve('./resources/js/oxxo.js'), }, output: { path: path.resolve(__dirname, 'assets/'), From 81c786150d8bfbfa0988daad73dca6a98d1bec80 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 12 Jul 2022 15:46:34 +0300 Subject: [PATCH 18/86] Remove attempt to use non-existing .issues property I think it never worked, and the API docs do not mention it (only the single .issue property), and either way this usage here does not seem helpful, issue/description still contains the same unfriendly text as in the exception message already --- .../src/Exception/PayPalApiException.php | 9 --------- .../src/Gateway/ProcessPaymentTrait.php | 15 ++------------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/modules/ppcp-api-client/src/Exception/PayPalApiException.php b/modules/ppcp-api-client/src/Exception/PayPalApiException.php index d5720532b..d2752cb72 100644 --- a/modules/ppcp-api-client/src/Exception/PayPalApiException.php +++ b/modules/ppcp-api-client/src/Exception/PayPalApiException.php @@ -111,15 +111,6 @@ class PayPalApiException extends RuntimeException { return false; } - /** - * Returns response issues. - * - * @return array - */ - public function issues(): array { - return $this->response->issues ?? array(); - } - /** * The HTTP status code. * diff --git a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php index cb4b4b5f7..2fb338ff1 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php +++ b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php @@ -229,22 +229,11 @@ trait ProcessPaymentTrait { ); } - $error_message = $error->getMessage(); - if ( $error->issues() ) { - $error_message = implode( - array_map( - function( $issue ) { - return $issue->issue . ' ' . $issue->description . '
'; - }, - $error->issues() - ) - ); - } - return $this->handle_payment_failure( $wc_order, new Exception( - $error_message, + __( 'Failed to process the payment. Please try again or contact the shop admin.', 'woocommerce-paypal-payments' ) + . ' ' . $error->getMessage(), $error->getCode(), $error ) From f7d831557793767d3eddca5a55b53756be9bb528 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 12 Jul 2022 14:50:04 +0200 Subject: [PATCH 19/86] Fix phpcs --- modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 15 +++++++++------ .../src/Gateway/OXXO/OXXOGateway.php | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index eadb41835..a1c37d959 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -42,8 +42,8 @@ class OXXO { * OXXO constructor. * * @param CheckoutHelper $checkout_helper The checkout helper. - * @param string $module_url The module URL. - * @param string $asset_version The asset version. + * @param string $module_url The module URL. + * @param string $asset_version The asset version. */ public function __construct( CheckoutHelper $checkout_helper, @@ -52,8 +52,8 @@ class OXXO { ) { $this->checkout_helper = $checkout_helper; - $this->module_url = $module_url; - $this->asset_version = $asset_version; + $this->module_url = $module_url; + $this->asset_version = $asset_version; } /** @@ -117,13 +117,16 @@ class OXXO { return true; } + /** + * Register OXXO assets. + */ public function register_assets(): void { $gateway_settings = get_option( 'woocommerce_ppcp-oxxo-gateway_settings' ); $gateway_enabled = $gateway_settings['enabled'] ?? ''; - if ( $gateway_enabled === 'yes' && is_checkout() && !empty( is_wc_endpoint_url('order-received') ) ) { + if ( $gateway_enabled === 'yes' && is_checkout() && ! empty( is_wc_endpoint_url( 'order-received' ) ) ) { wp_enqueue_script( 'ppcp-pay-upon-invoice', - trailingslashit($this->module_url) . 'assets/js/oxxo.js', + trailingslashit( $this->module_url ) . 'assets/js/oxxo.js', array(), $this->asset_version, true diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php index 681cea86c..7c5a4e1e8 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php @@ -65,7 +65,7 @@ class OXXOGateway extends WC_Payment_Gateway { ShippingPreferenceFactory $shipping_preference_factory, LoggerInterface $logger ) { - $this->id = self::ID; + $this->id = self::ID; $this->method_title = __( 'OXXO', 'woocommerce-paypal-payments' ); $this->method_description = __( 'OXXO is a Mexican chain of convenience stores.', 'woocommerce-paypal-payments' ); From d248a278bec296c1e0feeccace6ad3eebacdc91a Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 12 Jul 2022 15:19:24 +0200 Subject: [PATCH 20/86] Do not complete payment on `CHECKOUT.ORDER.APPROVED` handler --- modules/ppcp-webhooks/src/Handler/CheckoutOrderApproved.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-webhooks/src/Handler/CheckoutOrderApproved.php b/modules/ppcp-webhooks/src/Handler/CheckoutOrderApproved.php index fdbdf8abc..350d0e168 100644 --- a/modules/ppcp-webhooks/src/Handler/CheckoutOrderApproved.php +++ b/modules/ppcp-webhooks/src/Handler/CheckoutOrderApproved.php @@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\Webhooks\Handler; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use Psr\Log\LoggerInterface; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway; /** @@ -189,7 +190,7 @@ class CheckoutOrderApproved implements RequestHandler { } foreach ( $wc_orders as $wc_order ) { - if ( PayUponInvoiceGateway::ID === $wc_order->get_payment_method() ) { + if ( PayUponInvoiceGateway::ID === $wc_order->get_payment_method() || OXXOGateway::ID === $wc_order->get_payment_method() ) { continue; } From 0ce3af03c30970ceea72d78e3720585d979d6922 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 12 Jul 2022 21:19:21 +0300 Subject: [PATCH 21/86] Extract error message --- .../src/Exception/GatewayGenericException.php | 3 ++- .../ppcp-wc-gateway/src/Gateway/Messages.php | 27 +++++++++++++++++++ .../src/Gateway/ProcessPaymentTrait.php | 3 +-- 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 modules/ppcp-wc-gateway/src/Gateway/Messages.php diff --git a/modules/ppcp-wc-gateway/src/Exception/GatewayGenericException.php b/modules/ppcp-wc-gateway/src/Exception/GatewayGenericException.php index b43a69269..cbf157b03 100644 --- a/modules/ppcp-wc-gateway/src/Exception/GatewayGenericException.php +++ b/modules/ppcp-wc-gateway/src/Exception/GatewayGenericException.php @@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Exception; use Exception; use Throwable; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\Messages; /** * Class GatewayGenericException @@ -23,7 +24,7 @@ class GatewayGenericException extends Exception { */ public function __construct( ?Throwable $inner = null ) { parent::__construct( - __( 'Failed to process the payment. Please try again or contact the shop admin.', 'woocommerce-paypal-payments' ), + Messages::generic_payment_error_message(), $inner ? (int) $inner->getCode() : 0, $inner ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/Messages.php b/modules/ppcp-wc-gateway/src/Gateway/Messages.php new file mode 100644 index 000000000..ea5124578 --- /dev/null +++ b/modules/ppcp-wc-gateway/src/Gateway/Messages.php @@ -0,0 +1,27 @@ +handle_payment_failure( $wc_order, new Exception( - __( 'Failed to process the payment. Please try again or contact the shop admin.', 'woocommerce-paypal-payments' ) - . ' ' . $error->getMessage(), + Messages::generic_payment_error_message() . ' ' . $error->getMessage(), $error->getCode(), $error ) From 1a5eb500a508c887166a425d49f7c597ce7f043b Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 13 Jul 2022 09:51:23 +0300 Subject: [PATCH 22/86] Cleanup gateways, move process_payment --- modules/ppcp-wc-gateway/services.php | 7 - .../src/Gateway/CreditCardGateway.php | 199 ++++++++++++-- .../src/Gateway/PayPalGateway.php | 249 +++++++++++------- .../src/Gateway/ProcessPaymentTrait.php | 238 ----------------- .../WcGateway/Gateway/WcGatewayTest.php | 19 -- 5 files changed, 342 insertions(+), 370 deletions(-) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index a9e8f44a0..b405ac99c 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -59,7 +59,6 @@ return array( $order_processor = $container->get( 'wcgateway.order-processor' ); $settings_renderer = $container->get( 'wcgateway.settings.render' ); $funding_source_renderer = $container->get( 'wcgateway.funding-source.renderer' ); - $authorized_payments = $container->get( 'wcgateway.processor.authorized-payments' ); $settings = $container->get( 'wcgateway.settings' ); $session_handler = $container->get( 'session.handler' ); $refund_processor = $container->get( 'wcgateway.processor.refunds' ); @@ -68,8 +67,6 @@ return array( $subscription_helper = $container->get( 'subscription.helper' ); $page_id = $container->get( 'wcgateway.current-ppcp-settings-page-id' ); $payment_token_repository = $container->get( 'vaulting.repository.payment-token' ); - $payments_endpoint = $container->get( 'api.endpoint.payments' ); - $order_endpoint = $container->get( 'api.endpoint.order' ); $environment = $container->get( 'onboarding.environment' ); $logger = $container->get( 'woocommerce.logger.woocommerce' ); $api_shop_country = $container->get( 'api.shop.country' ); @@ -77,7 +74,6 @@ return array( $settings_renderer, $funding_source_renderer, $order_processor, - $authorized_payments, $settings, $session_handler, $refund_processor, @@ -87,10 +83,7 @@ return array( $page_id, $environment, $payment_token_repository, - $container->get( 'api.factory.shipping-preference' ), $logger, - $payments_endpoint, - $order_endpoint, $api_shop_country ); }, diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index b11116878..7856102b7 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -9,20 +9,30 @@ declare(strict_types=1); 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\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; @@ -31,7 +41,7 @@ use Psr\Container\ContainerInterface; */ class CreditCardGateway extends \WC_Payment_Gateway_CC { - use ProcessPaymentTrait; + use ProcessPaymentTrait, OrderMetaTrait, TransactionIdHandlingTrait, PaymentsStatusHandlingTrait, FreeTrialHandlerTrait; const ID = 'ppcp-credit-card-gateway'; @@ -203,15 +213,25 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { Environment $environment, PaymentsEndpoint $payments_endpoint ) { - $this->id = self::ID; + $this->settings_renderer = $settings_renderer; $this->order_processor = $order_processor; $this->authorized_payments_processor = $authorized_payments_processor; - $this->settings_renderer = $settings_renderer; $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; if ( $state->current_state() === State::STATE_ONBOARDED ) { $this->supports = array( 'refunds' ); @@ -261,18 +281,6 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { 'process_admin_options', ) ); - - $this->module_url = $module_url; - $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->transaction_url_provider = $transaction_url_provider; - $this->subscription_helper = $subscription_helper; - $this->logger = $logger; - $this->payments_endpoint = $payments_endpoint; - $this->state = $state; } /** @@ -301,7 +309,6 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { * @return string */ public function generate_ppcp_html(): string { - ob_start(); $this->settings_renderer->render(); $content = ob_get_contents(); @@ -409,6 +416,166 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { return $this->is_enabled(); } + /** + * Process payment for a WooCommerce order. + * + * @param int $order_id The WooCommerce order id. + * + * @return array + */ + public function process_payment( $order_id ) { + $wc_order = wc_get_order( $order_id ); + if ( ! is_a( $wc_order, WC_Order::class ) ) { + return $this->handle_payment_failure( + null, + new GatewayGenericException( new Exception( 'WC order was not found.' ) ) + ); + } + + /** + * 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, + '' + ); + + 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 ) ) { + 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 ) { + 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. + */ + //phpcs:disable WordPress.Security.NonceVerification.Recommended + if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) { + return $this->handle_payment_success( $wc_order ); + } + //phpcs:enable WordPress.Security.NonceVerification.Recommended + + try { + if ( ! $this->order_processor->process( $wc_order ) ) { + return $this->handle_payment_failure( + $wc_order, + new Exception( + $this->order_processor->last_error() + ) + ); + } + + if ( $this->subscription_helper->has_subscription( $order_id ) ) { + as_schedule_single_action( + time() + ( 1 * MINUTE_IN_SECONDS ), + 'woocommerce_paypal_payments_check_saved_payment', + array( + 'order_id' => $order_id, + 'customer_id' => $wc_order->get_customer_id(), + 'intent' => $this->config->has( 'intent' ) ? $this->config->get( 'intent' ) : '', + ) + ); + } + + return $this->handle_payment_success( $wc_order ); + } catch ( PayPalApiException $error ) { + return $this->handle_payment_failure( + $wc_order, + new Exception( + Messages::generic_payment_error_message() . ' ' . $error->getMessage(), + $error->getCode(), + $error + ) + ); + } catch ( RuntimeException $error ) { + return $this->handle_payment_failure( $wc_order, $error ); + } + } /** * Process refund. diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index 1aabbfc5a..21d8e831b 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -9,18 +9,21 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\WcGateway\Gateway; +use Exception; use Psr\Log\LoggerInterface; -use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; -use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint; -use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; +use WC_Order; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; +use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; +use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; 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\WcGateway\Exception\GatewayGenericException; use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway; -use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor; use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer; @@ -32,7 +35,7 @@ use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage; */ class PayPalGateway extends \WC_Payment_Gateway { - use ProcessPaymentTrait; + use ProcessPaymentTrait, FreeTrialHandlerTrait; const ID = 'ppcp-gateway'; const INTENT_META_KEY = '_ppcp_paypal_intent'; @@ -63,13 +66,6 @@ class PayPalGateway extends \WC_Payment_Gateway { */ protected $order_processor; - /** - * The processor for authorized payments. - * - * @var AuthorizedPaymentsProcessor - */ - protected $authorized_payments_processor; - /** * The settings. * @@ -119,27 +115,6 @@ class PayPalGateway extends \WC_Payment_Gateway { */ protected $payment_token_repository; - /** - * The shipping_preference factory. - * - * @var ShippingPreferenceFactory - */ - private $shipping_preference_factory; - - /** - * The payments endpoint - * - * @var PaymentsEndpoint - */ - protected $payments_endpoint; - - /** - * The order endpoint. - * - * @var OrderEndpoint - */ - protected $order_endpoint; - /** * Whether the plugin is in onboarded state. * @@ -178,30 +153,25 @@ class PayPalGateway extends \WC_Payment_Gateway { /** * PayPalGateway constructor. * - * @param SettingsRenderer $settings_renderer The Settings Renderer. - * @param FundingSourceRenderer $funding_source_renderer The funding source renderer. - * @param OrderProcessor $order_processor The Order Processor. - * @param AuthorizedPaymentsProcessor $authorized_payments_processor The Authorized Payments Processor. - * @param ContainerInterface $config The settings. - * @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 providing transaction view URL based on order. - * @param SubscriptionHelper $subscription_helper The subscription helper. - * @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page. - * @param Environment $environment The environment. - * @param PaymentTokenRepository $payment_token_repository The payment token repository. - * @param ShippingPreferenceFactory $shipping_preference_factory The shipping_preference factory. - * @param LoggerInterface $logger The logger. - * @param PaymentsEndpoint $payments_endpoint The payments endpoint. - * @param OrderEndpoint $order_endpoint The order endpoint. - * @param string $api_shop_country The api shop country. + * @param SettingsRenderer $settings_renderer The Settings Renderer. + * @param FundingSourceRenderer $funding_source_renderer The funding source renderer. + * @param OrderProcessor $order_processor The Order Processor. + * @param ContainerInterface $config The settings. + * @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 providing transaction view URL based on order. + * @param SubscriptionHelper $subscription_helper The subscription helper. + * @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page. + * @param Environment $environment The environment. + * @param PaymentTokenRepository $payment_token_repository The payment token repository. + * @param LoggerInterface $logger The logger. + * @param string $api_shop_country The api shop country. */ public function __construct( SettingsRenderer $settings_renderer, FundingSourceRenderer $funding_source_renderer, OrderProcessor $order_processor, - AuthorizedPaymentsProcessor $authorized_payments_processor, ContainerInterface $config, SessionHandler $session_handler, RefundProcessor $refund_processor, @@ -211,37 +181,25 @@ class PayPalGateway extends \WC_Payment_Gateway { string $page_id, Environment $environment, PaymentTokenRepository $payment_token_repository, - ShippingPreferenceFactory $shipping_preference_factory, LoggerInterface $logger, - PaymentsEndpoint $payments_endpoint, - OrderEndpoint $order_endpoint, string $api_shop_country ) { - - $this->id = self::ID; - $this->order_processor = $order_processor; - $this->authorized_payments_processor = $authorized_payments_processor; - $this->settings_renderer = $settings_renderer; - $this->funding_source_renderer = $funding_source_renderer; - $this->config = $config; - $this->session_handler = $session_handler; - $this->refund_processor = $refund_processor; - $this->transaction_url_provider = $transaction_url_provider; - $this->page_id = $page_id; - $this->environment = $environment; - $this->onboarded = $state->current_state() === State::STATE_ONBOARDED; - $this->id = self::ID; - $this->order_processor = $order_processor; - $this->authorized_payments = $authorized_payments_processor; - $this->shipping_preference_factory = $shipping_preference_factory; - $this->settings_renderer = $settings_renderer; - $this->config = $config; - $this->session_handler = $session_handler; - $this->refund_processor = $refund_processor; - $this->transaction_url_provider = $transaction_url_provider; - $this->page_id = $page_id; - $this->environment = $environment; - $this->logger = $logger; + $this->id = self::ID; + $this->settings_renderer = $settings_renderer; + $this->funding_source_renderer = $funding_source_renderer; + $this->order_processor = $order_processor; + $this->config = $config; + $this->session_handler = $session_handler; + $this->refund_processor = $refund_processor; + $this->state = $state; + $this->transaction_url_provider = $transaction_url_provider; + $this->subscription_helper = $subscription_helper; + $this->page_id = $page_id; + $this->environment = $environment; + $this->onboarded = $state->current_state() === State::STATE_ONBOARDED; + $this->payment_token_repository = $payment_token_repository; + $this->logger = $logger; + $this->api_shop_country = $api_shop_country; if ( $this->onboarded ) { $this->supports = array( 'refunds' ); @@ -291,13 +249,6 @@ class PayPalGateway extends \WC_Payment_Gateway { 'process_admin_options', ) ); - $this->subscription_helper = $subscription_helper; - $this->payment_token_repository = $payment_token_repository; - $this->logger = $logger; - $this->payments_endpoint = $payments_endpoint; - $this->order_endpoint = $order_endpoint; - $this->state = $state; - $this->api_shop_country = $api_shop_country; } /** @@ -306,7 +257,6 @@ class PayPalGateway extends \WC_Payment_Gateway { * @return bool */ public function needs_setup(): bool { - return ! $this->onboarded; } @@ -340,9 +290,8 @@ class PayPalGateway extends \WC_Payment_Gateway { * @return string */ public function generate_ppcp_html(): string { - ob_start(); - $this->settings_renderer->render( false ); + $this->settings_renderer->render(); $content = ob_get_contents(); ob_end_clean(); return $content; @@ -450,6 +399,126 @@ class PayPalGateway extends \WC_Payment_Gateway { } // phpcs:enable WordPress.Security.NonceVerification.Recommended + /** + * Process payment for a WooCommerce order. + * + * @param int $order_id The WooCommerce order id. + * + * @return array + */ + public function process_payment( $order_id ) { + $wc_order = wc_get_order( $order_id ); + if ( ! is_a( $wc_order, WC_Order::class ) ) { + return $this->handle_payment_failure( + null, + new GatewayGenericException( new Exception( 'WC order was not found.' ) ) + ); + } + + $funding_source = filter_input( INPUT_POST, 'ppcp-funding-source', FILTER_SANITIZE_STRING ); + + if ( 'card' !== $funding_source && $this->is_free_trial_order( $wc_order ) ) { + $user_id = (int) $wc_order->get_customer_id(); + $tokens = $this->payment_token_repository->all_for_user_id( $user_id ); + if ( ! array_filter( + $tokens, + function ( PaymentToken $token ): bool { + return isset( $token->source()->paypal ); + } + ) ) { + return $this->handle_payment_failure( $wc_order, new Exception( 'No saved PayPal account.' ) ); + } + + $wc_order->payment_complete(); + + return $this->handle_payment_success( $wc_order ); + } + + /** + * If customer has chosen change Subscription payment. + */ + if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) { + $saved_paypal_payment = filter_input( INPUT_POST, 'saved_paypal_payment', FILTER_SANITIZE_STRING ); + if ( $saved_paypal_payment ) { + update_post_meta( $order_id, 'payment_token_id', $saved_paypal_payment ); + + return $this->handle_payment_success( $wc_order ); + } + } + + /** + * If the WC_Order is paid through the approved webhook. + */ + //phpcs:disable WordPress.Security.NonceVerification.Recommended + if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) { + return $this->handle_payment_success( $wc_order ); + } + //phpcs:enable WordPress.Security.NonceVerification.Recommended + + try { + if ( ! $this->order_processor->process( $wc_order ) ) { + return $this->handle_payment_failure( + $wc_order, + new Exception( + $this->order_processor->last_error() + ) + ); + } + + if ( $this->subscription_helper->has_subscription( $order_id ) ) { + as_schedule_single_action( + time() + ( 1 * MINUTE_IN_SECONDS ), + 'woocommerce_paypal_payments_check_saved_payment', + array( + 'order_id' => $order_id, + 'customer_id' => $wc_order->get_customer_id(), + 'intent' => $this->config->has( 'intent' ) ? $this->config->get( 'intent' ) : '', + ) + ); + } + + return $this->handle_payment_success( $wc_order ); + } catch ( PayPalApiException $error ) { + if ( $error->has_detail( 'INSTRUMENT_DECLINED' ) ) { + $wc_order->update_status( + 'failed', + __( 'Instrument declined. ', 'woocommerce-paypal-payments' ) . $error->details()[0]->description ?? '' + ); + + $this->session_handler->increment_insufficient_funding_tries(); + if ( $this->session_handler->insufficient_funding_tries() >= 3 ) { + return $this->handle_payment_failure( + null, + new Exception( + __( 'Please use a different payment method.', 'woocommerce-paypal-payments' ), + $error->getCode(), + $error + ) + ); + } + + $host = $this->config->has( 'sandbox_on' ) && $this->config->get( 'sandbox_on' ) ? + 'https://www.sandbox.paypal.com/' : 'https://www.paypal.com/'; + $url = $host . 'checkoutnow?token=' . $this->session_handler->order()->id(); + return array( + 'result' => 'success', + 'redirect' => $url, + ); + } + + return $this->handle_payment_failure( + $wc_order, + new Exception( + Messages::generic_payment_error_message() . ' ' . $error->getMessage(), + $error->getCode(), + $error + ) + ); + } catch ( RuntimeException $error ) { + return $this->handle_payment_failure( $wc_order, $error ); + } + } + /** * Process refund. * diff --git a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php index 3fb692920..405790461 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php +++ b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php @@ -12,243 +12,12 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Gateway; use Exception; use Throwable; use WC_Order; -use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; -use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; -use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; -use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; -use WooCommerce\PayPalCommerce\Onboarding\Environment; -use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException; -use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; -use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait; -use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait; -use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait; /** * Trait ProcessPaymentTrait */ trait ProcessPaymentTrait { - - use OrderMetaTrait, PaymentsStatusHandlingTrait, TransactionIdHandlingTrait, FreeTrialHandlerTrait; - - /** - * Process a payment for an WooCommerce order. - * - * @param int $order_id The WooCommerce order id. - * - * @return array - * - * @throws RuntimeException When processing payment fails. - */ - public function process_payment( $order_id ) { - $wc_order = wc_get_order( $order_id ); - if ( ! is_a( $wc_order, WC_Order::class ) ) { - return $this->handle_payment_failure( - null, - new GatewayGenericException( new Exception( 'WC order was not found.' ) ) - ); - } - - $payment_method = filter_input( INPUT_POST, 'payment_method', FILTER_SANITIZE_STRING ); - $funding_source = filter_input( INPUT_POST, 'ppcp-funding-source', FILTER_SANITIZE_STRING ); - - /** - * 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 ( CreditCardGateway::ID === $payment_method && $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, - '' - ); - - 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 ) ) { - 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 ) { - return $this->handle_payment_failure( $wc_order, $error ); - } - } - - if ( PayPalGateway::ID === $payment_method && 'card' !== $funding_source && $this->is_free_trial_order( $wc_order ) ) { - $user_id = (int) $wc_order->get_customer_id(); - $tokens = $this->payment_token_repository->all_for_user_id( $user_id ); - if ( ! array_filter( - $tokens, - function ( PaymentToken $token ): bool { - return isset( $token->source()->paypal ); - } - ) ) { - return $this->handle_payment_failure( $wc_order, new Exception( 'No saved PayPal account.' ) ); - } - - $wc_order->payment_complete(); - - return $this->handle_payment_success( $wc_order ); - } - - /** - * If customer has chosen change Subscription payment. - */ - if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) { - if ( 'ppcp-credit-card-gateway' === $this->id && $saved_credit_card ) { - update_post_meta( $order_id, 'payment_token_id', $saved_credit_card ); - - return $this->handle_payment_success( $wc_order ); - } - - $saved_paypal_payment = filter_input( INPUT_POST, 'saved_paypal_payment', FILTER_SANITIZE_STRING ); - if ( 'ppcp-gateway' === $this->id && $saved_paypal_payment ) { - update_post_meta( $order_id, 'payment_token_id', $saved_paypal_payment ); - - return $this->handle_payment_success( $wc_order ); - } - } - - /** - * If the WC_Order is paid through the approved webhook. - */ - //phpcs:disable WordPress.Security.NonceVerification.Recommended - if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) { - return $this->handle_payment_success( $wc_order ); - } - //phpcs:enable WordPress.Security.NonceVerification.Recommended - - try { - if ( $this->order_processor->process( $wc_order ) ) { - if ( $this->subscription_helper->has_subscription( $order_id ) ) { - as_schedule_single_action( - time() + ( 1 * MINUTE_IN_SECONDS ), - 'woocommerce_paypal_payments_check_saved_payment', - array( - 'order_id' => $order_id, - 'customer_id' => $wc_order->get_customer_id(), - 'intent' => $this->config->has( 'intent' ) ? $this->config->get( 'intent' ) : '', - ) - ); - } - - WC()->cart->empty_cart(); // Probably redundant. - return $this->handle_payment_success( $wc_order ); - } - } catch ( PayPalApiException $error ) { - if ( $error->has_detail( 'INSTRUMENT_DECLINED' ) ) { - $wc_order->update_status( - 'failed', - __( 'Instrument declined. ', 'woocommerce-paypal-payments' ) . $error->details()[0]->description ?? '' - ); - - $this->session_handler->increment_insufficient_funding_tries(); - if ( $this->session_handler->insufficient_funding_tries() >= 3 ) { - return $this->handle_payment_failure( - null, - new Exception( - __( 'Please use a different payment method.', 'woocommerce-paypal-payments' ), - $error->getCode(), - $error - ) - ); - } - - $host = $this->config->has( 'sandbox_on' ) && $this->config->get( 'sandbox_on' ) ? - 'https://www.sandbox.paypal.com/' : 'https://www.paypal.com/'; - $url = $host . 'checkoutnow?token=' . $this->session_handler->order()->id(); - return array( - 'result' => 'success', - 'redirect' => $url, - ); - } - - return $this->handle_payment_failure( - $wc_order, - new Exception( - Messages::generic_payment_error_message() . ' ' . $error->getMessage(), - $error->getCode(), - $error - ) - ); - } catch ( RuntimeException $error ) { - return $this->handle_payment_failure( $wc_order, $error ); - } - - return $this->handle_payment_failure( - $wc_order, - new Exception( - $this->order_processor->last_error() - ) - ); - } - /** * Checks if PayPal or Credit Card gateways are enabled. * @@ -341,11 +110,4 @@ trait ProcessPaymentTrait { } return $output . ' ' . $this->format_exception( $prev ); } - - /** - * Returns the environment. - * - * @return Environment - */ - abstract protected function environment(): Environment; } diff --git a/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php index b33e705e0..ec901bafa 100644 --- a/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php +++ b/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php @@ -3,14 +3,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\WcGateway\Gateway; -use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; -use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; -use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint; -use Psr\Log\NullLogger; -use WooCommerce\PayPalCommerce\ApiClient\Entity\Capture; -use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatus; -use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Session\SessionHandler; @@ -37,7 +30,6 @@ class WcGatewayTest extends TestCase private $settingsRenderer; private $funding_source_renderer; private $orderProcessor; - private $authorizedOrdersProcessor; private $settings; private $refundProcessor; private $onboardingState; @@ -45,10 +37,7 @@ class WcGatewayTest extends TestCase private $subscriptionHelper; private $environment; private $paymentTokenRepository; - private $shipping_preference_factory; private $logger; - private $paymentsEndpoint; - private $orderEndpoint; private $apiShopCountry; public function setUp(): void { @@ -60,7 +49,6 @@ class WcGatewayTest extends TestCase $this->settingsRenderer = Mockery::mock(SettingsRenderer::class); $this->orderProcessor = Mockery::mock(OrderProcessor::class); - $this->authorizedOrdersProcessor = Mockery::mock(AuthorizedPaymentsProcessor::class); $this->settings = Mockery::mock(Settings::class); $this->sessionHandler = Mockery::mock(SessionHandler::class); $this->refundProcessor = Mockery::mock(RefundProcessor::class); @@ -69,10 +57,7 @@ class WcGatewayTest extends TestCase $this->subscriptionHelper = Mockery::mock(SubscriptionHelper::class); $this->environment = Mockery::mock(Environment::class); $this->paymentTokenRepository = Mockery::mock(PaymentTokenRepository::class); - $this->shipping_preference_factory = Mockery::mock(ShippingPreferenceFactory::class); $this->logger = Mockery::mock(LoggerInterface::class); - $this->paymentsEndpoint = Mockery::mock(PaymentsEndpoint::class); - $this->orderEndpoint = Mockery::mock(OrderEndpoint::class); $this->funding_source_renderer = new FundingSourceRenderer($this->settings); $this->apiShopCountry = 'DE'; @@ -96,7 +81,6 @@ class WcGatewayTest extends TestCase $this->settingsRenderer, $this->funding_source_renderer, $this->orderProcessor, - $this->authorizedOrdersProcessor, $this->settings, $this->sessionHandler, $this->refundProcessor, @@ -106,10 +90,7 @@ class WcGatewayTest extends TestCase PayPalGateway::ID, $this->environment, $this->paymentTokenRepository, - $this->shipping_preference_factory, $this->logger, - $this->paymentsEndpoint, - $this->orderEndpoint, $this->apiShopCountry ); } From 5bdc4b78c1a7bb4b374a16754a4243b5f823206b Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 13 Jul 2022 10:02:15 +0300 Subject: [PATCH 23/86] Extract generate_ppcp_html --- .../src/Gateway/CreditCardGateway.php | 24 +++--------- .../Gateway/GatewaySettingsRendererTrait.php | 37 +++++++++++++++++++ .../src/Gateway/PayPalGateway.php | 23 +++--------- 3 files changed, 48 insertions(+), 36 deletions(-) create mode 100644 modules/ppcp-wc-gateway/src/Gateway/GatewaySettingsRendererTrait.php diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index 7856102b7..aeaf02f14 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -41,7 +41,8 @@ use Psr\Container\ContainerInterface; */ class CreditCardGateway extends \WC_Payment_Gateway_CC { - use ProcessPaymentTrait, OrderMetaTrait, TransactionIdHandlingTrait, PaymentsStatusHandlingTrait, FreeTrialHandlerTrait; + use ProcessPaymentTrait, OrderMetaTrait, TransactionIdHandlingTrait, PaymentsStatusHandlingTrait, FreeTrialHandlerTrait, + GatewaySettingsRendererTrait; const ID = 'ppcp-credit-card-gateway'; @@ -303,19 +304,6 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { remove_action( 'gettext', 'replace_credit_card_cvv_label' ); } - /** - * Renders the settings. - * - * @return string - */ - public function generate_ppcp_html(): string { - ob_start(); - $this->settings_renderer->render(); - $content = ob_get_contents(); - ob_end_clean(); - return $content; - } - /** * Replace WooCommerce credit card field label. * @@ -667,11 +655,11 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { } /** - * Returns the environment. + * Returns the settings renderer. * - * @return Environment + * @return SettingsRenderer */ - protected function environment(): Environment { - return $this->environment; + protected function settings_renderer(): SettingsRenderer { + return $this->settings_renderer; } } diff --git a/modules/ppcp-wc-gateway/src/Gateway/GatewaySettingsRendererTrait.php b/modules/ppcp-wc-gateway/src/Gateway/GatewaySettingsRendererTrait.php new file mode 100644 index 000000000..804ea5fdd --- /dev/null +++ b/modules/ppcp-wc-gateway/src/Gateway/GatewaySettingsRendererTrait.php @@ -0,0 +1,37 @@ +settings_renderer()->render(); + $content = ob_get_contents(); + ob_end_clean(); + return $content; + } + + /** + * Returns the settings renderer. + * + * @return SettingsRenderer + */ + abstract protected function settings_renderer(): SettingsRenderer; +} diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index 21d8e831b..435d0bba8 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -35,7 +35,7 @@ use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage; */ class PayPalGateway extends \WC_Payment_Gateway { - use ProcessPaymentTrait, FreeTrialHandlerTrait; + use ProcessPaymentTrait, FreeTrialHandlerTrait, GatewaySettingsRendererTrait; const ID = 'ppcp-gateway'; const INTENT_META_KEY = '_ppcp_paypal_intent'; @@ -284,19 +284,6 @@ class PayPalGateway extends \WC_Payment_Gateway { } } - /** - * Renders the settings. - * - * @return string - */ - public function generate_ppcp_html(): string { - ob_start(); - $this->settings_renderer->render(); - $content = ob_get_contents(); - ob_end_clean(); - return $content; - } - /** * Defines the method title. If we are on the credit card tab in the settings, we want to change this. * @@ -572,11 +559,11 @@ class PayPalGateway extends \WC_Payment_Gateway { } /** - * Returns the environment. + * Returns the settings renderer. * - * @return Environment + * @return SettingsRenderer */ - protected function environment(): Environment { - return $this->environment; + protected function settings_renderer(): SettingsRenderer { + return $this->settings_renderer; } } From fb0bd85da6b50b9770d3b724fa09f6f18e90f527 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 13 Jul 2022 15:20:39 +0300 Subject: [PATCH 24/86] Extract saved payment check scheduling --- .../src/Gateway/CreditCardGateway.php | 10 +--------- .../src/Gateway/PayPalGateway.php | 10 +--------- .../src/Gateway/ProcessPaymentTrait.php | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index aeaf02f14..95ee8b8b7 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -539,15 +539,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { } if ( $this->subscription_helper->has_subscription( $order_id ) ) { - as_schedule_single_action( - time() + ( 1 * MINUTE_IN_SECONDS ), - 'woocommerce_paypal_payments_check_saved_payment', - array( - 'order_id' => $order_id, - 'customer_id' => $wc_order->get_customer_id(), - 'intent' => $this->config->has( 'intent' ) ? $this->config->get( 'intent' ) : '', - ) - ); + $this->schedule_saved_payment_check( $order_id, $wc_order->get_customer_id() ); } return $this->handle_payment_success( $wc_order ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index 435d0bba8..5cc391370 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -453,15 +453,7 @@ class PayPalGateway extends \WC_Payment_Gateway { } if ( $this->subscription_helper->has_subscription( $order_id ) ) { - as_schedule_single_action( - time() + ( 1 * MINUTE_IN_SECONDS ), - 'woocommerce_paypal_payments_check_saved_payment', - array( - 'order_id' => $order_id, - 'customer_id' => $wc_order->get_customer_id(), - 'intent' => $this->config->has( 'intent' ) ? $this->config->get( 'intent' ) : '', - ) - ); + $this->schedule_saved_payment_check( $order_id, $wc_order->get_customer_id() ); } return $this->handle_payment_success( $wc_order ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php index 405790461..f78871d1a 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php +++ b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php @@ -46,6 +46,24 @@ trait ProcessPaymentTrait { return false; } + /** + * Scheduled the vaulted payment check. + * + * @param int $wc_order_id The WC order ID. + * @param int $customer_id The customer ID. + */ + protected function schedule_saved_payment_check( int $wc_order_id, int $customer_id ): void { + as_schedule_single_action( + time() + ( 1 * MINUTE_IN_SECONDS ), + 'woocommerce_paypal_payments_check_saved_payment', + array( + 'order_id' => $wc_order_id, + 'customer_id' => $customer_id, + 'intent' => $this->config->has( 'intent' ) ? $this->config->get( 'intent' ) : '', + ) + ); + } + /** * Handles the payment failure. * From 09ef106a80a891a7e638c63e9ad330c5d2303040 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 13 Jul 2022 16:11:33 +0200 Subject: [PATCH 25/86] Refactor to create PayPal order first --- modules/ppcp-wc-gateway/resources/js/oxxo.js | 31 ++++ modules/ppcp-wc-gateway/services.php | 10 ++ .../ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 18 ++- .../src/Gateway/OXXO/OXXOEndpoint.php | 135 ++++++++++++++++++ .../src/Gateway/OXXO/OXXOGateway.php | 53 +------ .../ppcp-wc-gateway/src/WCGatewayModule.php | 8 ++ 6 files changed, 204 insertions(+), 51 deletions(-) create mode 100644 modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php diff --git a/modules/ppcp-wc-gateway/resources/js/oxxo.js b/modules/ppcp-wc-gateway/resources/js/oxxo.js index e2b9cb254..76ecc83c3 100644 --- a/modules/ppcp-wc-gateway/resources/js/oxxo.js +++ b/modules/ppcp-wc-gateway/resources/js/oxxo.js @@ -1,4 +1,33 @@ window.addEventListener('load', function() { + + const oxxoButton = document.getElementById('ppcp-oxxo'); + oxxoButton?.addEventListener('click', (event) => { + event.preventDefault(); + + fetch(OXXOConfig.oxxo_endpoint, { + method: 'POST', + body: JSON.stringify({ + nonce: OXXOConfig.oxxo_nonce, + }) + }).then((res)=>{ + return res.json(); + }).then((data)=>{ + if (!data.success) { + alert('Could not update signup buttons: ' + JSON.stringify(data)); + return; + } + + window.open( + data.data.payer_action, + '_blank', + 'popup' + ); + + document.querySelector('#place_order').click() + }); + }); + + /* const oxxoButton = document.getElementById('ppcp-oxxo-payer-action'); if(oxxoButton) { oxxoButton.addEventListener('click', (event) => { @@ -12,4 +41,6 @@ window.addEventListener('load', function() { window.open(oxxoButton.href); } + + */ }); diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 1f865d484..80198ff2f 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -31,6 +31,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint; use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXO; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOEndpoint; use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\FraudNet; @@ -2245,6 +2246,15 @@ return array( $container->get( 'woocommerce.logger.woocommerce' ) ); }, + 'wcgateway.endpoint.oxxo' => static function (ContainerInterface $container): OXXOEndpoint { + return new OXXOEndpoint( + $container->get( 'button.request-data' ), + $container->get( 'api.endpoint.order' ), + $container->get( 'api.factory.purchase-unit' ), + $container->get( 'api.factory.shipping-preference' ), + $container->get( 'woocommerce.logger.woocommerce' ) + ); + }, 'wcgateway.logging.is-enabled' => function ( ContainerInterface $container ) : bool { $settings = $container->get( 'wcgateway.settings' ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index a1c37d959..9d158a998 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -93,6 +93,11 @@ class OXXO { 'wp_enqueue_scripts', array( $this, 'register_assets' ) ); + + add_action('woocommerce_review_order_after_payment', function () { + + echo ''; + }); } /** @@ -123,14 +128,23 @@ class OXXO { public function register_assets(): void { $gateway_settings = get_option( 'woocommerce_ppcp-oxxo-gateway_settings' ); $gateway_enabled = $gateway_settings['enabled'] ?? ''; - if ( $gateway_enabled === 'yes' && is_checkout() && ! empty( is_wc_endpoint_url( 'order-received' ) ) ) { + if ( $gateway_enabled === 'yes' && is_checkout() ) { // && ! empty( is_wc_endpoint_url( 'order-received' ) ) wp_enqueue_script( - 'ppcp-pay-upon-invoice', + 'ppcp-oxxo', trailingslashit( $this->module_url ) . 'assets/js/oxxo.js', array(), $this->asset_version, true ); } + + wp_localize_script( + 'ppcp-oxxo', + 'OXXOConfig', + array( + 'oxxo_endpoint' => \WC_AJAX::get_endpoint( 'ppc-oxxo' ), + 'oxxo_nonce' => wp_create_nonce( 'ppc-oxxo' ), + ) + ); } } diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php new file mode 100644 index 000000000..1fc1833d2 --- /dev/null +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php @@ -0,0 +1,135 @@ +request_data = $request_data; + $this->purchase_unit_factory = $purchase_unit_factory; + $this->shipping_preference_factory = $shipping_preference_factory; + $this->order_endpoint = $order_endpoint; + $this->logger = $logger; + } + + public static function nonce(): string + { + return 'ppc-oxxo'; + } + + public function handle_request(): bool + { + $data = $this->request_data->read_request( $this->nonce() ); + + $purchase_unit = $this->purchase_unit_factory->from_wc_cart(); + + $payer_action = ''; + try { + $shipping_preference = $this->shipping_preference_factory->from_state( + $purchase_unit, + 'checkout' + ); + + $order = $this->order_endpoint->create(array($purchase_unit), $shipping_preference); + + $payment_source = array( + 'oxxo' => array( + 'name' => 'John Doe', + 'email' => 'foo@bar.com', + 'country_code' => 'MX', + ), + ); + + $payment_method = $this->order_endpoint->confirm_payment_source( $order->id(), $payment_source ); + + foreach ( $payment_method->links as $link ) { + if ( $link->rel === 'payer-action' ) { + $payer_action = $link->href; + } + } + + } catch ( RuntimeException $exception ) { + $error = $exception->getMessage(); + + if ( is_a( $exception, PayPalApiException::class ) && is_array( $exception->details() ) ) { + $details = ''; + foreach ( $exception->details() as $detail ) { + $issue = $detail->issue ?? ''; + $field = $detail->field ?? ''; + $description = $detail->description ?? ''; + $details .= $issue . ' ' . $field . ' ' . $description . '
'; + } + + $error = $details; + } + + + $this->logger->error( $error ); + wc_add_notice( $error, 'error' ); + } + + WC()->session->set( 'ppcp_payer_action', $payer_action ); + + wp_send_json_success( + array('payer_action' => $payer_action,) + ); + + return true; + } +} diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php index 7c5a4e1e8..ff5548e51 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php @@ -130,56 +130,11 @@ class OXXOGateway extends WC_Payment_Gateway { public function process_payment( $order_id ) { $wc_order = wc_get_order( $order_id ); $wc_order->update_status( 'on-hold', __( 'Awaiting OXXO payment.', 'woocommerce-paypal-payments' ) ); - $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); - try { - $shipping_preference = $this->shipping_preference_factory->from_state( - $purchase_unit, - 'checkout' - ); - - $order = $this->order_endpoint->create( array( $purchase_unit ), $shipping_preference ); - $payment_source = array( - 'oxxo' => array( - 'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(), - 'email' => $wc_order->get_billing_email(), - 'country_code' => $wc_order->get_billing_country(), - ), - ); - $payment_method = $this->order_endpoint->confirm_payment_source( $order->id(), $payment_source ); - foreach ( $payment_method->links as $link ) { - if ( $link->rel === 'payer-action' ) { - $wc_order->add_meta_data( 'ppcp_oxxo_payer_action', $link->href ); - $wc_order->save_meta_data(); - } - } - } catch ( RuntimeException $exception ) { - $error = $exception->getMessage(); - - if ( is_a( $exception, PayPalApiException::class ) && is_array( $exception->details() ) ) { - $details = ''; - foreach ( $exception->details() as $detail ) { - $issue = $detail->issue ?? ''; - $field = $detail->field ?? ''; - $description = $detail->description ?? ''; - $details .= $issue . ' ' . $field . ' ' . $description . '
'; - } - - $error = $details; - } - - $this->logger->error( $error ); - wc_add_notice( $error, 'error' ); - - $wc_order->update_status( - 'failed', - $error - ); - - return array( - 'result' => 'failure', - 'redirect' => wc_get_checkout_url(), - ); + $payer_action = WC()->session->get( 'ppcp_payer_action' ); + if($payer_action) { + $wc_order->add_meta_data( 'ppcp_oxxo_payer_action', $payer_action ); + $wc_order->save_meta_data(); } WC()->cart->empty_cart(); diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 46bbf9491..12136089c 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -262,6 +262,14 @@ class WCGatewayModule implements ModuleInterface { 10, 2 ); + + add_action( + 'wc_ajax_ppc-oxxo', + static function () use ( $c ) { + $endpoint = $c->get( 'wcgateway.endpoint.oxxo' ); + $endpoint->handle_request(); + } + ); } /** From ece598921c949e86019e483cb74eab0bd5329a0e Mon Sep 17 00:00:00 2001 From: dinamiko Date: Thu, 14 Jul 2022 10:38:27 +0200 Subject: [PATCH 26/86] Run checkout js validation before calling PayPal --- modules/ppcp-wc-gateway/resources/js/oxxo.js | 32 ++++++++----------- .../ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 25 +++++++++++---- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/modules/ppcp-wc-gateway/resources/js/oxxo.js b/modules/ppcp-wc-gateway/resources/js/oxxo.js index 76ecc83c3..2c55d2454 100644 --- a/modules/ppcp-wc-gateway/resources/js/oxxo.js +++ b/modules/ppcp-wc-gateway/resources/js/oxxo.js @@ -1,9 +1,22 @@ +import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler'; + window.addEventListener('load', function() { const oxxoButton = document.getElementById('ppcp-oxxo'); oxxoButton?.addEventListener('click', (event) => { event.preventDefault(); + const requiredFields = jQuery('form.woocommerce-checkout .validate-required:visible :input'); + requiredFields.each((i, input) => { + jQuery(input).trigger('validate'); + }); + if (jQuery('form.woocommerce-checkout .validate-required.woocommerce-invalid:visible').length) { + const errorHandler = new ErrorHandler(OXXOConfig.error.generic); + errorHandler.clear(); + errorHandler.message(OXXOConfig.error.js_validation); + return; + } + fetch(OXXOConfig.oxxo_endpoint, { method: 'POST', body: JSON.stringify({ @@ -13,7 +26,7 @@ window.addEventListener('load', function() { return res.json(); }).then((data)=>{ if (!data.success) { - alert('Could not update signup buttons: ' + JSON.stringify(data)); + alert('Could not get payer action from PayPal: ' + JSON.stringify(data)); return; } @@ -26,21 +39,4 @@ window.addEventListener('load', function() { document.querySelector('#place_order').click() }); }); - - /* - const oxxoButton = document.getElementById('ppcp-oxxo-payer-action'); - if(oxxoButton) { - oxxoButton.addEventListener('click', (event) => { - event.preventDefault(); - window.open( - oxxoButton.href, - '_blank', - 'popup' - ); - }); - - window.open(oxxoButton.href); - } - - */ }); diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index 9d158a998..0c6cb2cf4 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -94,10 +94,13 @@ class OXXO { array( $this, 'register_assets' ) ); - add_action('woocommerce_review_order_after_payment', function () { + add_action( + 'woocommerce_review_order_after_payment', + function () { - echo ''; - }); + echo ''; + } + ); } /** @@ -128,7 +131,7 @@ class OXXO { public function register_assets(): void { $gateway_settings = get_option( 'woocommerce_ppcp-oxxo-gateway_settings' ); $gateway_enabled = $gateway_settings['enabled'] ?? ''; - if ( $gateway_enabled === 'yes' && is_checkout() ) { // && ! empty( is_wc_endpoint_url( 'order-received' ) ) + if ( $gateway_enabled === 'yes' && is_checkout() ) { wp_enqueue_script( 'ppcp-oxxo', trailingslashit( $this->module_url ) . 'assets/js/oxxo.js', @@ -142,8 +145,18 @@ class OXXO { 'ppcp-oxxo', 'OXXOConfig', array( - 'oxxo_endpoint' => \WC_AJAX::get_endpoint( 'ppc-oxxo' ), - 'oxxo_nonce' => wp_create_nonce( 'ppc-oxxo' ), + 'oxxo_endpoint' => \WC_AJAX::get_endpoint( 'ppc-oxxo' ), + 'oxxo_nonce' => wp_create_nonce( 'ppc-oxxo' ), + 'error' => array( + 'generic' => __( + 'Something went wrong. Please try again or choose another payment source.', + 'woocommerce-paypal-payments' + ), + 'js_validation' => __( + 'Required form fields are not filled or invalid.', + 'woocommerce-paypal-payments' + ), + ), ) ); } From 07da639f9d96c458862593e0c71de3037fcf798e Mon Sep 17 00:00:00 2001 From: dinamiko Date: Thu, 14 Jul 2022 12:27:53 +0200 Subject: [PATCH 27/86] Add oxxo button to show hide gateway visibility --- .../resources/js/modules/ContextBootstrap/CheckoutBootstap.js | 4 +++- .../resources/js/modules/Helper/CheckoutMethodState.js | 1 + modules/ppcp-wc-gateway/resources/js/oxxo.js | 2 +- modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 3 +-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index bc83c736e..f00242f4e 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -84,8 +84,9 @@ class CheckoutBootstap { const currentPaymentMethod = getCurrentPaymentMethod(); const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; const isCard = currentPaymentMethod === PaymentMethods.CARDS; + const isOXXO = currentPaymentMethod === PaymentMethods.OXXO; const isSavedCard = isCard && isSavedCardSelected(); - const isNotOurGateway = !isPaypal && !isCard; + const isNotOurGateway = !isPaypal && !isCard && !isOXXO; const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart; const hasVaultedPaypal = PayPalCommerceGateway.vaulted_paypal_email !== ''; @@ -94,6 +95,7 @@ class CheckoutBootstap { setVisible(this.gateway.button.wrapper, isPaypal && !(isFreeTrial && hasVaultedPaypal)); setVisible(this.gateway.messages.wrapper, isPaypal && !isFreeTrial); setVisible(this.gateway.hosted_fields.wrapper, isCard && !isSavedCard); + setVisible('#ppcp-oxxo', isOXXO && !isSavedCard && !isPaypal); if (isPaypal && !isFreeTrial) { this.messages.render(); diff --git a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js index 196ad596b..c98ef779a 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js +++ b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js @@ -1,6 +1,7 @@ export const PaymentMethods = { PAYPAL: 'ppcp-gateway', CARDS: 'ppcp-credit-card-gateway', + OXXO: 'ppcp-oxxo-gateway', }; export const ORDER_BUTTON_SELECTOR = '#place_order'; diff --git a/modules/ppcp-wc-gateway/resources/js/oxxo.js b/modules/ppcp-wc-gateway/resources/js/oxxo.js index 2c55d2454..14fc9bfff 100644 --- a/modules/ppcp-wc-gateway/resources/js/oxxo.js +++ b/modules/ppcp-wc-gateway/resources/js/oxxo.js @@ -1,6 +1,6 @@ import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler'; -window.addEventListener('load', function() { +window.addEventListener('load', function () { const oxxoButton = document.getElementById('ppcp-oxxo'); oxxoButton?.addEventListener('click', (event) => { diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index 0c6cb2cf4..f25ad57ff 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -97,8 +97,7 @@ class OXXO { add_action( 'woocommerce_review_order_after_payment', function () { - - echo ''; + echo ''; } ); } From 7985999192616bf7e4e51404092deef66a04a7b1 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Thu, 14 Jul 2022 12:40:04 +0200 Subject: [PATCH 28/86] Resize and center oxxo modal window --- modules/ppcp-wc-gateway/resources/js/oxxo.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/resources/js/oxxo.js b/modules/ppcp-wc-gateway/resources/js/oxxo.js index 14fc9bfff..9b4942035 100644 --- a/modules/ppcp-wc-gateway/resources/js/oxxo.js +++ b/modules/ppcp-wc-gateway/resources/js/oxxo.js @@ -30,10 +30,14 @@ window.addEventListener('load', function () { return; } + const width = screen.width/2; + const height = screen.height/2; + const left = (screen.width/2)-(width/2); + const top = (screen.height/2)-(height/2); window.open( data.data.payer_action, '_blank', - 'popup' + 'popup, width='+width+', height='+height+', top='+top+', left='+left ); document.querySelector('#place_order').click() From 400d834e5285ebf247530e7634b3159c566a3dc9 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Thu, 14 Jul 2022 12:51:43 +0200 Subject: [PATCH 29/86] Do not render oxxo payer action button in thank you page for now --- .../ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index f25ad57ff..fe143aa41 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -73,22 +73,6 @@ class OXXO { } ); - add_filter( - 'woocommerce_thankyou_order_received_text', - function( string $message, WC_Order $order ) { - $payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' ) ?? ''; - - $button = ''; - if ( $payer_action ) { - $button = '

See OXXO Voucher/Ticket

'; - } - - return $message . ' ' . $button; - }, - 10, - 2 - ); - add_action( 'wp_enqueue_scripts', array( $this, 'register_assets' ) From 859c99aa494f6a23870b0069eb0540a8225431bd Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 15 Jul 2022 12:13:38 +0200 Subject: [PATCH 30/86] Get payer action from gateway response and use it for opening the modal --- modules/ppcp-wc-gateway/resources/js/oxxo.js | 29 ++++++++++--------- .../ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 18 +----------- .../src/Gateway/OXXO/OXXOGateway.php | 12 ++++++-- 3 files changed, 27 insertions(+), 32 deletions(-) diff --git a/modules/ppcp-wc-gateway/resources/js/oxxo.js b/modules/ppcp-wc-gateway/resources/js/oxxo.js index e2b9cb254..0dcb26f29 100644 --- a/modules/ppcp-wc-gateway/resources/js/oxxo.js +++ b/modules/ppcp-wc-gateway/resources/js/oxxo.js @@ -1,15 +1,18 @@ -window.addEventListener('load', function() { - const oxxoButton = document.getElementById('ppcp-oxxo-payer-action'); - if(oxxoButton) { - oxxoButton.addEventListener('click', (event) => { - event.preventDefault(); - window.open( - oxxoButton.href, - '_blank', - 'popup' - ); +document.addEventListener( + 'DOMContentLoaded', + function() { + jQuery('form.checkout').on('checkout_place_order_success', function(type, data) { + if(data.payer_action && data.payer_action !== '') { + const width = screen.width / 2; + const height = screen.height / 2; + const left = (screen.width / 2) - (width / 2); + const top = (screen.height / 2) - (height / 2); + window.open( + data.payer_action, + '_blank', + 'popup, width=' + width + ', height=' + height + ', top=' + top + ', left=' + left + ); + } }); - - window.open(oxxoButton.href); } -}); +); diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index a1c37d959..800dc4d24 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -73,22 +73,6 @@ class OXXO { } ); - add_filter( - 'woocommerce_thankyou_order_received_text', - function( string $message, WC_Order $order ) { - $payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' ) ?? ''; - - $button = ''; - if ( $payer_action ) { - $button = '

See OXXO Voucher/Ticket

'; - } - - return $message . ' ' . $button; - }, - 10, - 2 - ); - add_action( 'wp_enqueue_scripts', array( $this, 'register_assets' ) @@ -123,7 +107,7 @@ class OXXO { public function register_assets(): void { $gateway_settings = get_option( 'woocommerce_ppcp-oxxo-gateway_settings' ); $gateway_enabled = $gateway_settings['enabled'] ?? ''; - if ( $gateway_enabled === 'yes' && is_checkout() && ! empty( is_wc_endpoint_url( 'order-received' ) ) ) { + if ( $gateway_enabled === 'yes' && is_checkout() ) { wp_enqueue_script( 'ppcp-pay-upon-invoice', trailingslashit( $this->module_url ) . 'assets/js/oxxo.js', diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php index 7c5a4e1e8..b5a5dbe83 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php @@ -131,6 +131,7 @@ class OXXOGateway extends WC_Payment_Gateway { $wc_order = wc_get_order( $order_id ); $wc_order->update_status( 'on-hold', __( 'Awaiting OXXO payment.', 'woocommerce-paypal-payments' ) ); $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); + $payer_action = ''; try { $shipping_preference = $this->shipping_preference_factory->from_state( @@ -149,7 +150,8 @@ class OXXOGateway extends WC_Payment_Gateway { $payment_method = $this->order_endpoint->confirm_payment_source( $order->id(), $payment_source ); foreach ( $payment_method->links as $link ) { if ( $link->rel === 'payer-action' ) { - $wc_order->add_meta_data( 'ppcp_oxxo_payer_action', $link->href ); + $payer_action = $link->href; + $wc_order->add_meta_data( 'ppcp_oxxo_payer_action', $payer_action ); $wc_order->save_meta_data(); } } @@ -184,9 +186,15 @@ class OXXOGateway extends WC_Payment_Gateway { WC()->cart->empty_cart(); - return array( + $result = array( 'result' => 'success', 'redirect' => $this->get_return_url( $wc_order ), ); + + if ( $payer_action ) { + $result['payer_action'] = $payer_action; + } + + return $result; } } From b655d3a710911e047659a5a44a921bf53c3155eb Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 15 Jul 2022 12:27:04 +0200 Subject: [PATCH 31/86] Fix phpcs --- .../src/Gateway/OXXO/OXXOEndpoint.php | 27 ++++++++----------- .../src/Gateway/OXXO/OXXOGateway.php | 2 +- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php index 1fc1833d2..668c7a4b4 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php @@ -18,8 +18,8 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface; use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData; -class OXXOEndpoint implements EndpointInterface -{ +class OXXOEndpoint implements EndpointInterface { + /** * The request data @@ -59,22 +59,19 @@ class OXXOEndpoint implements EndpointInterface PurchaseUnitFactory $purchase_unit_factory, ShippingPreferenceFactory $shipping_preference_factory, LoggerInterface $logger - ) - { - $this->request_data = $request_data; - $this->purchase_unit_factory = $purchase_unit_factory; + ) { + $this->request_data = $request_data; + $this->purchase_unit_factory = $purchase_unit_factory; $this->shipping_preference_factory = $shipping_preference_factory; - $this->order_endpoint = $order_endpoint; - $this->logger = $logger; + $this->order_endpoint = $order_endpoint; + $this->logger = $logger; } - public static function nonce(): string - { + public static function nonce(): string { return 'ppc-oxxo'; } - public function handle_request(): bool - { + public function handle_request(): bool { $data = $this->request_data->read_request( $this->nonce() ); $purchase_unit = $this->purchase_unit_factory->from_wc_cart(); @@ -86,7 +83,7 @@ class OXXOEndpoint implements EndpointInterface 'checkout' ); - $order = $this->order_endpoint->create(array($purchase_unit), $shipping_preference); + $order = $this->order_endpoint->create( array( $purchase_unit ), $shipping_preference ); $payment_source = array( 'oxxo' => array( @@ -103,7 +100,6 @@ class OXXOEndpoint implements EndpointInterface $payer_action = $link->href; } } - } catch ( RuntimeException $exception ) { $error = $exception->getMessage(); @@ -119,7 +115,6 @@ class OXXOEndpoint implements EndpointInterface $error = $details; } - $this->logger->error( $error ); wc_add_notice( $error, 'error' ); } @@ -127,7 +122,7 @@ class OXXOEndpoint implements EndpointInterface WC()->session->set( 'ppcp_payer_action', $payer_action ); wp_send_json_success( - array('payer_action' => $payer_action,) + array( 'payer_action' => $payer_action ) ); return true; diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php index b5a5dbe83..8fb168769 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php @@ -18,7 +18,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; /** - * Class PayUponInvoiceGateway. + * Class OXXOGateway. */ class OXXOGateway extends WC_Payment_Gateway { const ID = 'ppcp-oxxo-gateway'; From 752d32718867d4195b896567b3bb67828fdeccd6 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 15 Jul 2022 12:38:38 +0200 Subject: [PATCH 32/86] Fix phpcs --- modules/ppcp-wc-gateway/services.php | 2 +- .../src/Gateway/OXXO/OXXOEndpoint.php | 35 +++++++++++++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 80198ff2f..8cff73c1d 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -2246,7 +2246,7 @@ return array( $container->get( 'woocommerce.logger.woocommerce' ) ); }, - 'wcgateway.endpoint.oxxo' => static function (ContainerInterface $container): OXXOEndpoint { + 'wcgateway.endpoint.oxxo' => static function ( ContainerInterface $container ): OXXOEndpoint { return new OXXOEndpoint( $container->get( 'button.request-data' ), $container->get( 'api.endpoint.order' ), diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php index 668c7a4b4..0d6e4fc3c 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php @@ -1,6 +1,6 @@ request_data = $request_data; + $this->request_data = $request_data; $this->purchase_unit_factory = $purchase_unit_factory; $this->shipping_preference_factory = $shipping_preference_factory; $this->order_endpoint = $order_endpoint; $this->logger = $logger; } + /** + * The nonce + * + * @return string + */ public static function nonce(): string { return 'ppc-oxxo'; } + /** + * Handles the request. + * + * @return bool + */ public function handle_request(): bool { - $data = $this->request_data->read_request( $this->nonce() ); - $purchase_unit = $this->purchase_unit_factory->from_wc_cart(); + $payer_action = ''; - $payer_action = ''; try { $shipping_preference = $this->shipping_preference_factory->from_state( $purchase_unit, From d1c4850c4deef7df933a09090c6ff0c7a1dc3b14 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 15 Jul 2022 16:05:13 +0200 Subject: [PATCH 33/86] Do not render custom oxxo button in checkout --- .../ContextBootstrap/CheckoutBootstap.js | 4 +- .../ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 19 +++++-- .../src/Gateway/OXXO/OXXOGateway.php | 1 - .../src/Handler/PaymentCapturePending.php | 49 +++++++++++++++++++ 4 files changed, 64 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index f00242f4e..bc83c736e 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -84,9 +84,8 @@ class CheckoutBootstap { const currentPaymentMethod = getCurrentPaymentMethod(); const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; const isCard = currentPaymentMethod === PaymentMethods.CARDS; - const isOXXO = currentPaymentMethod === PaymentMethods.OXXO; const isSavedCard = isCard && isSavedCardSelected(); - const isNotOurGateway = !isPaypal && !isCard && !isOXXO; + const isNotOurGateway = !isPaypal && !isCard; const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart; const hasVaultedPaypal = PayPalCommerceGateway.vaulted_paypal_email !== ''; @@ -95,7 +94,6 @@ class CheckoutBootstap { setVisible(this.gateway.button.wrapper, isPaypal && !(isFreeTrial && hasVaultedPaypal)); setVisible(this.gateway.messages.wrapper, isPaypal && !isFreeTrial); setVisible(this.gateway.hosted_fields.wrapper, isCard && !isSavedCard); - setVisible('#ppcp-oxxo', isOXXO && !isSavedCard && !isPaypal); if (isPaypal && !isFreeTrial) { this.messages.render(); diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index fe143aa41..edfae0095 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -78,11 +78,20 @@ class OXXO { array( $this, 'register_assets' ) ); - add_action( - 'woocommerce_review_order_after_payment', - function () { - echo ''; - } + add_filter( + 'woocommerce_thankyou_order_received_text', + function( string $message, WC_Order $order ) { + $payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' ) ?? ''; + + $button = ''; + if ( $payer_action ) { + $button = '

See OXXO Voucher/Ticket

'; + } + + return $message . ' ' . $button; + }, + 10, + 2 ); } diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php index 8fb168769..69203d885 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php @@ -129,7 +129,6 @@ class OXXOGateway extends WC_Payment_Gateway { */ public function process_payment( $order_id ) { $wc_order = wc_get_order( $order_id ); - $wc_order->update_status( 'on-hold', __( 'Awaiting OXXO payment.', 'woocommerce-paypal-payments' ) ); $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); $payer_action = ''; diff --git a/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php b/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php index 18ebef228..27e8bdf29 100644 --- a/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php +++ b/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php @@ -17,6 +17,8 @@ use WP_REST_Response; */ class PaymentCapturePending implements RequestHandler { + use PrefixTrait; + /** * The logger. * @@ -74,6 +76,53 @@ class PaymentCapturePending implements RequestHandler { $this->logger->info( (string) wc_print_r( $resource, true ) ); + $order_id = isset( $request['resource']['custom_id'] ) + ? $this->sanitize_custom_id( $request['resource']['custom_id'] ) + : 0; + + if ( ! $order_id ) { + $message = sprintf( + // translators: %s is the PayPal webhook Id. + __( + 'No order for webhook event %s was found.', + 'woocommerce-paypal-payments' + ), + isset( $request['id'] ) ? $request['id'] : '' + ); + $this->logger->log( + 'warning', + $message, + array( + 'request' => $request, + ) + ); + $response['message'] = $message; + return rest_ensure_response( $response ); + } + + $wc_order = wc_get_order( $order_id ); + if ( ! is_a( $wc_order, \WC_Order::class ) ) { + $message = sprintf( + // translators: %s is the PayPal refund Id. + __( 'Order for PayPal refund %s not found.', 'woocommerce-paypal-payments' ), + isset( $request['resource']['id'] ) ? $request['resource']['id'] : '' + ); + $this->logger->log( + 'warning', + $message, + array( + 'request' => $request, + ) + ); + $response['message'] = $message; + return rest_ensure_response( $response ); + } + + if ( $wc_order->get_status() === 'pending' ) { + $wc_order->update_status('on-hold', __('Payment initiation was successful, and is waiting for the buyer to complete the payment.', 'woocommerce-paypal-payments')); + + } + $response['success'] = true; return new WP_REST_Response( $response ); } From abf61a14b827b91d1afaaeda3eb2d8df2b70c79a Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 15 Jul 2022 16:33:49 +0200 Subject: [PATCH 34/86] Do not process payment for oxxo in return url endpoint --- modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php b/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php index 6239e39e2..0270dae48 100644 --- a/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php +++ b/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\WcGateway\Endpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\Webhooks\Handler\PrefixTrait; @@ -72,6 +73,11 @@ class ReturnUrlEndpoint { exit(); } + if ( $wc_order->get_payment_method() === OXXOGateway::ID ) { + wp_safe_redirect( wc_get_checkout_url() ); + exit(); + } + $success = $this->gateway->process_payment( $wc_order_id ); if ( isset( $success['result'] ) && 'success' === $success['result'] ) { add_filter( From 2b8281e0f41d339c8a582d94f9a40086764bf796 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 18 Jul 2022 12:03:52 +0200 Subject: [PATCH 35/86] Introduce payment capture denied webhook (WIP) --- modules/ppcp-webhooks/services.php | 2 + .../src/Handler/PaymentCaptureDenied.php | 81 +++++++++++++++++++ .../src/Handler/PaymentCapturePending.php | 2 - 3 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 modules/ppcp-webhooks/src/Handler/PaymentCaptureDenied.php diff --git a/modules/ppcp-webhooks/services.php b/modules/ppcp-webhooks/services.php index 02703e8b3..8f7a4c499 100644 --- a/modules/ppcp-webhooks/services.php +++ b/modules/ppcp-webhooks/services.php @@ -20,6 +20,7 @@ use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulationStateEndpoint; use WooCommerce\PayPalCommerce\Webhooks\Handler\CheckoutOrderApproved; use WooCommerce\PayPalCommerce\Webhooks\Handler\CheckoutOrderCompleted; use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureCompleted; +use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureDenied; use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCapturePending; use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureRefunded; use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureReversed; @@ -80,6 +81,7 @@ return array( new VaultPaymentTokenCreated( $logger, $prefix, $authorized_payments_processor ), new VaultCreditCardCreated( $logger, $prefix ), new PaymentCapturePending( $logger ), + new PaymentCaptureDenied($logger), ); }, diff --git a/modules/ppcp-webhooks/src/Handler/PaymentCaptureDenied.php b/modules/ppcp-webhooks/src/Handler/PaymentCaptureDenied.php new file mode 100644 index 000000000..dc1c9a867 --- /dev/null +++ b/modules/ppcp-webhooks/src/Handler/PaymentCaptureDenied.php @@ -0,0 +1,81 @@ +logger = $logger; + } + + /** + * The event types a handler handles. + * + * @return string[] + */ + public function event_types(): array + { + return array( 'PAYMENT.CAPTURE.DENIED' ); + } + + /** + * Whether a handler is responsible for a given request or not. + * + * @param \WP_REST_Request $request The request. + * + * @return bool + */ + public function responsible_for_request(WP_REST_Request $request): bool + { + return in_array( $request['event_type'], $this->event_types(), true ); + } + + /** + * Responsible for handling the request. + * + * @param \WP_REST_Request $request The request. + * + * @return WP_REST_Response + */ + public function handle_request(WP_REST_Request $request): WP_REST_Response + { + $response = array( 'success' => false ); + $resource = $request['resource']; + if ( ! is_array( $resource ) ) { + $message = 'Resource data not found in webhook request.'; + $this->logger->warning( $message, array( 'request' => $request ) ); + $response['message'] = $message; + return new WP_REST_Response( $response ); + } + + $this->logger->info( (string) wc_print_r( $resource, true ) ); + + $response['success'] = true; + return new WP_REST_Response( $response ); + } +} diff --git a/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php b/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php index 27e8bdf29..92507c9f3 100644 --- a/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php +++ b/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php @@ -74,8 +74,6 @@ class PaymentCapturePending implements RequestHandler { return new WP_REST_Response( $response ); } - $this->logger->info( (string) wc_print_r( $resource, true ) ); - $order_id = isset( $request['resource']['custom_id'] ) ? $this->sanitize_custom_id( $request['resource']['custom_id'] ) : 0; From 1ce9a5750b6cbc9d18dc9b3b8d5ac64472043ce1 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 18 Jul 2022 12:08:25 +0200 Subject: [PATCH 36/86] Delete payment capture denied handler --- .../src/Handler/PaymentCaptureDenied.php | 81 ------------------- 1 file changed, 81 deletions(-) delete mode 100644 modules/ppcp-webhooks/src/Handler/PaymentCaptureDenied.php diff --git a/modules/ppcp-webhooks/src/Handler/PaymentCaptureDenied.php b/modules/ppcp-webhooks/src/Handler/PaymentCaptureDenied.php deleted file mode 100644 index dc1c9a867..000000000 --- a/modules/ppcp-webhooks/src/Handler/PaymentCaptureDenied.php +++ /dev/null @@ -1,81 +0,0 @@ -logger = $logger; - } - - /** - * The event types a handler handles. - * - * @return string[] - */ - public function event_types(): array - { - return array( 'PAYMENT.CAPTURE.DENIED' ); - } - - /** - * Whether a handler is responsible for a given request or not. - * - * @param \WP_REST_Request $request The request. - * - * @return bool - */ - public function responsible_for_request(WP_REST_Request $request): bool - { - return in_array( $request['event_type'], $this->event_types(), true ); - } - - /** - * Responsible for handling the request. - * - * @param \WP_REST_Request $request The request. - * - * @return WP_REST_Response - */ - public function handle_request(WP_REST_Request $request): WP_REST_Response - { - $response = array( 'success' => false ); - $resource = $request['resource']; - if ( ! is_array( $resource ) ) { - $message = 'Resource data not found in webhook request.'; - $this->logger->warning( $message, array( 'request' => $request ) ); - $response['message'] = $message; - return new WP_REST_Response( $response ); - } - - $this->logger->info( (string) wc_print_r( $resource, true ) ); - - $response['success'] = true; - return new WP_REST_Response( $response ); - } -} From e4b15c78d11b8faebe3ac4bd746966b8e3c7fc9f Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 18 Jul 2022 12:29:30 +0200 Subject: [PATCH 37/86] Delete payment capture denied handler --- modules/ppcp-webhooks/services.php | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/ppcp-webhooks/services.php b/modules/ppcp-webhooks/services.php index 8f7a4c499..5e7ca8674 100644 --- a/modules/ppcp-webhooks/services.php +++ b/modules/ppcp-webhooks/services.php @@ -81,7 +81,6 @@ return array( new VaultPaymentTokenCreated( $logger, $prefix, $authorized_payments_processor ), new VaultCreditCardCreated( $logger, $prefix ), new PaymentCapturePending( $logger ), - new PaymentCaptureDenied($logger), ); }, From 2943c0d8aa4b38e3e22af2ffb24f0765a23729f3 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 18 Jul 2022 14:31:46 +0200 Subject: [PATCH 38/86] Render oxxo voucher link into on-hold email notification --- .../ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 17 +++++++++++++++++ .../Gateway/PayUponInvoice/PayUponInvoice.php | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index edfae0095..2d7f0a385 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -93,6 +93,23 @@ class OXXO { 10, 2 ); + + add_action('woocommerce_email_before_order_table', + function (WC_Order $order, bool $sent_to_admin) { + if( + ! $sent_to_admin + && $order->get_payment_method() === OXXOGateway::ID + && $order->has_status( 'on-hold' ) + ) { + $payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' ) ?? ''; + if($payer_action) { + echo '

OXXO voucher

'; + } + } + }, + 10, + 2 + ); } /** diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index 6ac81f118..38883eb1c 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -309,7 +309,7 @@ class PayUponInvoice { } }, 10, - 3 + 2 ); add_filter( From 8c772588288f66ad6b8b33fcd6c3cc3524e2926b Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 18 Jul 2022 10:35:19 +0300 Subject: [PATCH 39/86] Add option for sending billing data --- .../modules/ActionHandler/CheckoutActionHandler.js | 13 ++++++++++--- modules/ppcp-button/src/Assets/SmartButton.php | 1 + .../src/Endpoint/CreateOrderEndpoint.php | 5 ++++- modules/ppcp-wc-gateway/services.php | 14 ++++++++++++++ .../Button/Endpoint/CreateOrderEndpointTest.php | 1 + 5 files changed, 30 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js index 733b5e339..cc9c75ae7 100644 --- a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js +++ b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js @@ -13,7 +13,7 @@ class CheckoutActionHandler { configuration() { const spinner = this.spinner; const createOrder = (data, actions) => { - const payer = payerData(); + let payer = payerData(); const bnCode = typeof this.config.bn_codes[this.config.context] !== 'undefined' ? this.config.bn_codes[this.config.context] : ''; @@ -26,6 +26,13 @@ class CheckoutActionHandler { const createaccount = jQuery('#createaccount').is(":checked") ? true : false; + const paymentMethod = getCurrentPaymentMethod(); + const fundingSource = window.ppcpFundingSource; + + if (fundingSource === 'card' && !PayPalCommerceGateway.use_form_billing_data_for_cards) { + payer = null; + } + return fetch(this.config.ajax.create_order.endpoint, { method: 'POST', body: JSON.stringify({ @@ -34,8 +41,8 @@ class CheckoutActionHandler { bn_code:bnCode, context:this.config.context, order_id:this.config.order_id, - payment_method: getCurrentPaymentMethod(), - funding_source: window.ppcpFundingSource, + payment_method: paymentMethod, + funding_source: fundingSource, form: formJsonObj, createaccount: createaccount }) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 33a2eb1a4..8db4384c1 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -867,6 +867,7 @@ class SmartButton implements SmartButtonInterface { 'single_product_buttons_enabled' => $this->settings->has( 'button_product_enabled' ) && $this->settings->get( 'button_product_enabled' ), 'mini_cart_buttons_enabled' => $this->settings->has( 'button_mini-cart_enabled' ) && $this->settings->get( 'button_mini-cart_enabled' ), 'basic_checkout_validation_enabled' => $this->basic_checkout_validation_enabled, + 'use_form_billing_data_for_cards' => $this->settings->has( 'use_form_billing_data_for_cards' ) && $this->settings->get( 'use_form_billing_data_for_cards' ), ); if ( $this->style_for_context( 'layout', 'mini-cart' ) !== 'horizontal' ) { diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index c0045c8c4..6131c0e4a 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -404,7 +404,10 @@ class CreateOrderEndpoint implements EndpointInterface { $payer = $this->payer_factory->from_paypal_response( json_decode( wp_json_encode( $data['payer'] ) ) ); } - if ( ! $payer && isset( $data['form'] ) ) { + $use_form_billing_data_for_cards = $this->settings->has( 'use_form_billing_data_for_cards' ) && + (bool) $this->settings->get( 'use_form_billing_data_for_cards' ); + + if ( ! $payer && isset( $data['form'] ) && $use_form_billing_data_for_cards ) { $form_fields = $data['form']; if ( is_array( $form_fields ) && isset( $form_fields['billing_email'] ) && '' !== $form_fields['billing_email'] ) { diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index b405ac99c..8e9a73003 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -854,6 +854,20 @@ return array( 'requirements' => array(), 'gateway' => 'paypal', ), + 'use_form_billing_data_for_cards' => array( + 'title' => __( 'Send billing data for cards', 'woocommerce-paypal-payments' ), + 'type' => 'checkbox', + 'desc_tip' => true, + 'label' => __( 'Send Checkout billing form data to PayPal smart card fields', 'woocommerce-paypal-payments' ), + 'description' => __( 'This increases convenience for the users, but can cause issues if card details do not match the billing data.', 'woocommerce-paypal-payments' ), + 'default' => false, + 'screens' => array( + State::STATE_START, + State::STATE_ONBOARDED, + ), + 'requirements' => array(), + 'gateway' => 'paypal', + ), // General button styles. 'button_style_heading' => array( diff --git a/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php b/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php index 00d863a9e..08cd79c4d 100644 --- a/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php +++ b/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php @@ -152,6 +152,7 @@ class CreateOrderEndpointTest extends TestCase $session_handler = Mockery::mock(SessionHandler::class); $settings = Mockery::mock(Settings::class); $early_order_handler = Mockery::mock(EarlyOrderHandler::class); + $settings->shouldReceive('has')->andReturnFalse(); $testee = new CreateOrderEndpoint( $request_data, From 0ff11054f8435fa74427e7aff1fe927c8c7eb66f Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 18 Jul 2022 16:27:06 +0300 Subject: [PATCH 40/86] Refactor button renderer settings handling --- modules/ppcp-button/package.json | 3 +++ .../js/modules/ContextBootstrap/CartBootstap.js | 4 +--- .../modules/ContextBootstrap/CheckoutBootstap.js | 4 +--- .../modules/ContextBootstrap/MiniCartBootstap.js | 10 +++++++--- .../ContextBootstrap/SingleProductBootstap.js | 4 +--- .../resources/js/modules/Renderer/Renderer.js | 16 +++++++++------- modules/ppcp-button/src/Assets/SmartButton.php | 9 ++++----- modules/ppcp-button/yarn.lock | 5 +++++ 8 files changed, 31 insertions(+), 24 deletions(-) diff --git a/modules/ppcp-button/package.json b/modules/ppcp-button/package.json index ada30892e..03403b794 100644 --- a/modules/ppcp-button/package.json +++ b/modules/ppcp-button/package.json @@ -3,6 +3,9 @@ "version": "1.0.0", "license": "GPL-3.0-or-later", "main": "resources/js/button.js", + "dependencies": { + "deepmerge": "^4.2.2" + }, "devDependencies": { "@babel/core": "^7.9.0", "@babel/preset-env": "^7.9.5", diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js index 29e6a4819..d32ca440f 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js @@ -32,9 +32,7 @@ class CartBootstrap { ); this.renderer.render( - this.gateway.button.wrapper, - this.gateway.hosted_fields.wrapper, - actionHandler.configuration(), + actionHandler.configuration() ); } } diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index bc83c736e..a2186970f 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -69,9 +69,7 @@ class CheckoutBootstap { ); this.renderer.render( - this.gateway.button.wrapper, - this.gateway.hosted_fields.wrapper, - actionHandler.configuration(), + actionHandler.configuration() ); this.buttonChangeObserver.observe( diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js index 1a45e6814..35465c12e 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js @@ -32,9 +32,13 @@ class MiniCartBootstap { } this.renderer.render( - this.gateway.button.mini_cart_wrapper, - this.gateway.hosted_fields.mini_cart_wrapper, - this.actionHandler.configuration() + this.actionHandler.configuration(), + { + button: { + wrapper: this.gateway.button.mini_cart_wrapper, + style: this.gateway.button.mini_cart_style, + }, + } ); } } diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js index 6da17f4e9..ecb44eabe 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js @@ -85,9 +85,7 @@ class SingleProductBootstap { ); this.renderer.render( - this.gateway.button.wrapper, - this.gateway.hosted_fields.wrapper, - actionHandler.configuration(), + actionHandler.configuration() ); } } diff --git a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js index cc811bb58..6ef6a3b2f 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js @@ -1,23 +1,25 @@ +import merge from "deepmerge"; + class Renderer { - constructor(creditCardRenderer, defaultConfig, onSmartButtonClick, onSmartButtonsInit) { - this.defaultConfig = defaultConfig; + constructor(creditCardRenderer, defaultSettings, onSmartButtonClick, onSmartButtonsInit) { + this.defaultSettings = defaultSettings; this.creditCardRenderer = creditCardRenderer; this.onSmartButtonClick = onSmartButtonClick; this.onSmartButtonsInit = onSmartButtonsInit; } - render(wrapper, hostedFieldsWrapper, contextConfig) { + render(contextConfig, settingsOverride = {}) { + const settings = merge(this.defaultSettings, settingsOverride); - this.renderButtons(wrapper, contextConfig); - this.creditCardRenderer.render(hostedFieldsWrapper, contextConfig); + this.renderButtons(settings.button.wrapper, settings.button.style, contextConfig); + this.creditCardRenderer.render(settings.hosted_fields.wrapper, contextConfig); } - renderButtons(wrapper, contextConfig) { + renderButtons(wrapper, style, contextConfig) { if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper) || 'undefined' === typeof paypal.Buttons ) { return; } - const style = wrapper === this.defaultConfig.button.wrapper ? this.defaultConfig.button.style : this.defaultConfig.button.mini_cart_style; paypal.Buttons({ style, ...contextConfig, diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 8db4384c1..ae5428906 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -831,9 +831,8 @@ class SmartButton implements SmartButtonInterface { ), ), 'hosted_fields' => array( - 'wrapper' => '#ppcp-hosted-fields', - 'mini_cart_wrapper' => '#ppcp-hosted-fields-mini-cart', - 'labels' => array( + 'wrapper' => '#ppcp-hosted-fields', + 'labels' => array( 'credit_card_number' => '', 'cvv' => '', 'mm_yy' => __( 'MM/YY', 'woocommerce-paypal-payments' ), @@ -847,8 +846,8 @@ class SmartButton implements SmartButtonInterface { ), 'cardholder_name_required' => __( 'Cardholder\'s first and last name are required, please fill the checkout form required fields.', 'woocommerce-paypal-payments' ), ), - 'valid_cards' => $this->dcc_applies->valid_cards(), - 'contingency' => $this->get_3ds_contingency(), + 'valid_cards' => $this->dcc_applies->valid_cards(), + 'contingency' => $this->get_3ds_contingency(), ), 'messages' => $this->message_values(), 'labels' => array( diff --git a/modules/ppcp-button/yarn.lock b/modules/ppcp-button/yarn.lock index f7f1da65e..a6e69f244 100644 --- a/modules/ppcp-button/yarn.lock +++ b/modules/ppcp-button/yarn.lock @@ -1313,6 +1313,11 @@ debug@^4.1.0, debug@^4.1.1: dependencies: ms "2.1.2" +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" From d4e8bd453c22242c333c9aad0d8110dda72c25ef Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 19 Jul 2022 09:20:26 +0300 Subject: [PATCH 41/86] Add card button gateway --- .../src/Factory/AmountFactory.php | 3 +- modules/ppcp-button/resources/js/button.js | 15 +- .../ContextBootstrap/CheckoutBootstap.js | 13 +- .../js/modules/Helper/CheckoutMethodState.js | 1 + .../resources/js/modules/Renderer/Renderer.js | 19 +- .../ppcp-button/src/Assets/SmartButton.php | 51 ++- .../src/Endpoint/CreateOrderEndpoint.php | 3 +- .../src/Cancellation/CancelController.php | 4 + .../ppcp-vaulting/src/PaymentTokenChecker.php | 3 +- modules/ppcp-wc-gateway/services.php | 17 +- .../src/Checkout/DisableGateways.php | 11 +- .../src/Gateway/CardButtonGateway.php | 331 ++++++++++++++++++ .../src/Settings/SectionsRenderer.php | 8 +- .../ppcp-wc-gateway/src/WCGatewayModule.php | 2 + tests/stubs/WC_Payment_Gateway.php | 4 +- 15 files changed, 454 insertions(+), 31 deletions(-) create mode 100644 modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php diff --git a/modules/ppcp-api-client/src/Factory/AmountFactory.php b/modules/ppcp-api-client/src/Factory/AmountFactory.php index 71f1c1a9d..51a6d8c11 100644 --- a/modules/ppcp-api-client/src/Factory/AmountFactory.php +++ b/modules/ppcp-api-client/src/Factory/AmountFactory.php @@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Item; use WooCommerce\PayPalCommerce\ApiClient\Entity\Money; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; @@ -132,7 +133,7 @@ class AmountFactory { $total_value = (float) $order->get_total(); if ( ( - CreditCardGateway::ID === $order->get_payment_method() + in_array( $order->get_payment_method(), array( CreditCardGateway::ID, CardButtonGateway::ID ), true ) || ( PayPalGateway::ID === $order->get_payment_method() && 'card' === $order->get_meta( PayPalGateway::ORDER_PAYMENT_SOURCE_META_KEY ) ) ) && $this->is_free_trial_order( $order ) diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index ed8c400cf..b64b5e9d8 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -18,7 +18,9 @@ import {hide, setVisible} from "./modules/Helper/Hiding"; import {isChangePaymentPage} from "./modules/Helper/Subscriptions"; import FreeTrialHandler from "./modules/ActionHandler/FreeTrialHandler"; -const buttonsSpinner = new Spinner('.ppc-button-wrapper'); +// TODO: could be a good idea to have a separate spinner for each gateway, +// but I think we care mainly about the script loading, so one spinner should be enough. +const buttonsSpinner = new Spinner(document.querySelector('.ppc-button-wrapper')); const cardsSpinner = new Spinner('#ppcp-hosted-fields'); const bootstrap = () => { @@ -138,6 +140,11 @@ document.addEventListener( return; } + const paypalButtonGatewayIds = [ + PaymentMethods.PAYPAL, + ...Object.entries(PayPalCommerceGateway.separate_buttons).map(([k, data]) => data.id), + ] + // Sometimes PayPal script takes long time to load, // so we additionally hide the standard order button here to avoid failed orders. // Normally it is hidden later after the script load. @@ -153,12 +160,12 @@ document.addEventListener( } const currentPaymentMethod = getCurrentPaymentMethod(); - const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; + const isPaypalButton = paypalButtonGatewayIds.includes(currentPaymentMethod); const isCards = currentPaymentMethod === PaymentMethods.CARDS; - setVisible(ORDER_BUTTON_SELECTOR, !isPaypal && !isCards, true); + setVisible(ORDER_BUTTON_SELECTOR, !isPaypalButton && !isCards, true); - if (isPaypal) { + if (isPaypalButton) { // stopped after the first rendering of the buttons, in onInit buttonsSpinner.block(); } else { diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index a2186970f..9d008d4b6 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -82,16 +82,27 @@ class CheckoutBootstap { const currentPaymentMethod = getCurrentPaymentMethod(); const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; const isCard = currentPaymentMethod === PaymentMethods.CARDS; + const isSeparateButtonGateway = [PaymentMethods.CARD_BUTTON].includes(currentPaymentMethod); const isSavedCard = isCard && isSavedCardSelected(); - const isNotOurGateway = !isPaypal && !isCard; + const isNotOurGateway = !isPaypal && !isCard && !isSeparateButtonGateway; const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart; const hasVaultedPaypal = PayPalCommerceGateway.vaulted_paypal_email !== ''; + const paypalButtonWrappers = { + ...Object.entries(PayPalCommerceGateway.separate_buttons) + .reduce((result, [k, data]) => { + return {...result, [data.id]: data.wrapper} + }, {}), + }; + setVisible(this.standardOrderButtonSelector, (isPaypal && isFreeTrial && hasVaultedPaypal) || isNotOurGateway || isSavedCard, true); setVisible('.ppcp-vaulted-paypal-details', isPaypal); setVisible(this.gateway.button.wrapper, isPaypal && !(isFreeTrial && hasVaultedPaypal)); setVisible(this.gateway.messages.wrapper, isPaypal && !isFreeTrial); setVisible(this.gateway.hosted_fields.wrapper, isCard && !isSavedCard); + for (const [gatewayId, wrapper] of Object.entries(paypalButtonWrappers)) { + setVisible(wrapper, gatewayId === currentPaymentMethod); + } if (isPaypal && !isFreeTrial) { this.messages.render(); diff --git a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js index 196ad596b..eceb07509 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js +++ b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js @@ -1,6 +1,7 @@ export const PaymentMethods = { PAYPAL: 'ppcp-gateway', CARDS: 'ppcp-credit-card-gateway', + CARD_BUTTON: 'ppcp-card-button-gateway', }; export const ORDER_BUTTON_SELECTOR = '#place_order'; diff --git a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js index 6ef6a3b2f..2ceee9a0a 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js @@ -13,6 +13,16 @@ class Renderer { this.renderButtons(settings.button.wrapper, settings.button.style, contextConfig); this.creditCardRenderer.render(settings.hosted_fields.wrapper, contextConfig); + for (const [fundingSource, data] of Object.entries(settings.separate_buttons)) { + this.renderButtons( + data.wrapper, + data.style, + { + ...contextConfig, + fundingSource: fundingSource, + } + ); + } } renderButtons(wrapper, style, contextConfig) { @@ -20,12 +30,17 @@ class Renderer { return; } - paypal.Buttons({ + const btn = paypal.Buttons({ style, ...contextConfig, onClick: this.onSmartButtonClick, onInit: this.onSmartButtonsInit, - }).render(wrapper); + }); + if (!btn.isEligible()) { + return; + } + + btn.render(wrapper); } isAlreadyRendered(wrapper) { diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index ae5428906..c581a26a8 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -28,7 +28,9 @@ use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -421,15 +423,20 @@ class SmartButton implements SmartButtonInterface { ) { add_action( $this->single_product_renderer_hook(), - array( - $this, - 'button_renderer', - ), + function () { + $this->button_renderer( PayPalGateway::ID ); + }, 31 ); } - add_action( $this->pay_order_renderer_hook(), array( $this, 'button_renderer' ), 10 ); + add_action( + $this->pay_order_renderer_hook(), + function (): void { + $this->button_renderer( PayPalGateway::ID ); + $this->button_renderer( CardButtonGateway::ID ); + } + ); $not_enabled_on_minicart = $this->settings->has( 'button_mini_cart_enabled' ) && ! $this->settings->get( 'button_mini_cart_enabled' ); @@ -457,7 +464,13 @@ class SmartButton implements SmartButtonInterface { ); } - add_action( $this->checkout_button_renderer_hook(), array( $this, 'button_renderer' ), 10 ); + add_action( + $this->checkout_button_renderer_hook(), + function (): void { + $this->button_renderer( PayPalGateway::ID ); + $this->button_renderer( CardButtonGateway::ID ); + } + ); $not_enabled_on_cart = $this->settings->has( 'button_cart_enabled' ) && ! $this->settings->get( 'button_cart_enabled' ); @@ -468,7 +481,7 @@ class SmartButton implements SmartButtonInterface { return; } - $this->button_renderer(); + $this->button_renderer( PayPalGateway::ID ); }, 20 ); @@ -524,8 +537,10 @@ class SmartButton implements SmartButtonInterface { /** * Renders the HTML for the buttons. + * + * @param string $gateway_id The gateway ID, like 'ppcp-gateway'. */ - public function button_renderer() { + public function button_renderer( string $gateway_id ) { if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) { return; @@ -543,13 +558,13 @@ class SmartButton implements SmartButtonInterface { $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); - if ( ! isset( $available_gateways['ppcp-gateway'] ) ) { + if ( ! isset( $available_gateways[ $gateway_id ] ) ) { return; } // The wrapper is needed for the loading spinner, // otherwise jQuery block() prevents buttons rendering. - echo '
'; + echo '
'; } /** @@ -810,7 +825,7 @@ class SmartButton implements SmartButtonInterface { 'bn_codes' => $this->bn_codes(), 'payer' => $this->payerData(), 'button' => array( - 'wrapper' => '#ppc-button', + 'wrapper' => '#ppc-button-' . PayPalGateway::ID, 'mini_cart_wrapper' => '#ppc-button-minicart', 'cancel_wrapper' => '#ppcp-cancel', 'url' => $this->url(), @@ -830,6 +845,16 @@ class SmartButton implements SmartButtonInterface { 'tagline' => $this->style_for_context( 'tagline', $this->context() ), ), ), + 'separate_buttons' => array( + 'card' => array( + 'id' => CardButtonGateway::ID, + 'wrapper' => '#ppc-button-' . CardButtonGateway::ID, + 'style' => array( + 'shape' => $this->style_for_context( 'shape', $this->context() ), + // TODO: color black, white from the gateway settings. + ), + ), + ), 'hosted_fields' => array( 'wrapper' => '#ppcp-hosted-fields', 'labels' => array( @@ -1018,6 +1043,7 @@ class SmartButton implements SmartButtonInterface { if ( $this->load_button_component() ) { $components[] = 'buttons'; + $components[] = 'funding-eligibility'; } if ( $this->messages_apply->for_country() @@ -1112,6 +1138,9 @@ class SmartButton implements SmartButtonInterface { if ( $source && $source->card() ) { return false; // Ignore for DCC. } + if ( 'card' === $this->session_handler->funding_source() ) { + return false; // Ignore for card buttons. + } return true; } diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index 6131c0e4a..c0907b4af 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -28,6 +28,7 @@ use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -204,7 +205,7 @@ class CreateOrderEndpoint implements EndpointInterface { // The cart does not have any info about payment method, so we must handle free trial here. if ( ( - CreditCardGateway::ID === $payment_method + in_array( $payment_method, array( CreditCardGateway::ID, CardButtonGateway::ID ), true ) || ( PayPalGateway::ID === $payment_method && 'card' === $funding_source ) ) && $this->is_free_trial_cart() diff --git a/modules/ppcp-session/src/Cancellation/CancelController.php b/modules/ppcp-session/src/Cancellation/CancelController.php index 51d48008c..3c23983f5 100644 --- a/modules/ppcp-session/src/Cancellation/CancelController.php +++ b/modules/ppcp-session/src/Cancellation/CancelController.php @@ -70,6 +70,10 @@ class CancelController { return; // Ignore for DCC. } + if ( 'card' === $this->session_handler->funding_source() ) { + return; // Ignore for card buttons. + } + $url = add_query_arg( array( $param_name => wp_create_nonce( $nonce ) ), wc_get_checkout_url() ); add_action( 'woocommerce_review_order_after_submit', diff --git a/modules/ppcp-vaulting/src/PaymentTokenChecker.php b/modules/ppcp-vaulting/src/PaymentTokenChecker.php index 604c7cc37..849c7a08f 100644 --- a/modules/ppcp-vaulting/src/PaymentTokenChecker.php +++ b/modules/ppcp-vaulting/src/PaymentTokenChecker.php @@ -16,6 +16,7 @@ use WC_Order; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Repository\OrderRepository; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; @@ -118,7 +119,7 @@ class PaymentTokenChecker { if ( $tokens ) { try { if ( $this->is_free_trial_order( $wc_order ) ) { - if ( CreditCardGateway::ID === $wc_order->get_payment_method() + if ( in_array( $wc_order->get_payment_method(), array( CreditCardGateway::ID, CardButtonGateway::ID ), true ) || ( PayPalGateway::ID === $wc_order->get_payment_method() && 'card' === $wc_order->get_meta( PayPalGateway::ORDER_PAYMENT_SOURCE_META_KEY ) ) ) { $order = $this->order_repository->for_wc_order( $wc_order ); diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 8e9a73003..a4c7b35a6 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -29,6 +29,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Checkout\CheckoutPayPalAddressPreset; use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways; use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint; use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\FraudNet; @@ -126,6 +127,20 @@ return array( $payments_endpoint ); }, + 'wcgateway.card-button-gateway' => static function ( ContainerInterface $container ): CardButtonGateway { + return new CardButtonGateway( + $container->get( 'wcgateway.order-processor' ), + $container->get( 'wcgateway.settings' ), + $container->get( 'session.handler' ), + $container->get( 'wcgateway.processor.refunds' ), + $container->get( 'onboarding.state' ), + $container->get( 'wcgateway.transaction-url-provider' ), + $container->get( 'subscription.helper' ), + $container->get( 'onboarding.environment' ), + $container->get( 'vaulting.repository.payment-token' ), + $container->get( 'woocommerce.logger.woocommerce' ) + ); + }, 'wcgateway.disabler' => static function ( ContainerInterface $container ): DisableGateways { $session_handler = $container->get( 'session.handler' ); $settings = $container->get( 'wcgateway.settings' ); @@ -143,7 +158,7 @@ return array( } $section = isset( $_GET['section'] ) ? sanitize_text_field( wp_unslash( $_GET['section'] ) ) : ''; - return in_array( $section, array( PayPalGateway::ID, CreditCardGateway::ID, WebhooksStatusPage::ID, PayUponInvoiceGateway::ID ), true ); + return in_array( $section, array( PayPalGateway::ID, CreditCardGateway::ID, WebhooksStatusPage::ID, PayUponInvoiceGateway::ID, CardButtonGateway::ID ), true ); }, 'wcgateway.current-ppcp-settings-page-id' => static function ( ContainerInterface $container ): string { diff --git a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php index 33fbcef6e..0ff7dc8d1 100644 --- a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php +++ b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\WcGateway\Checkout; use WooCommerce\PayPalCommerce\Session\SessionHandler; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use Psr\Container\ContainerInterface; @@ -59,9 +60,10 @@ class DisableGateways { if ( ! isset( $methods[ PayPalGateway::ID ] ) && ! isset( $methods[ CreditCardGateway::ID ] ) ) { return $methods; } - if ( $this->disable_both_gateways() ) { + if ( $this->disable_all_gateways() ) { unset( $methods[ PayPalGateway::ID ] ); unset( $methods[ CreditCardGateway::ID ] ); + unset( $methods[ CardButtonGateway::ID ] ); return $methods; } @@ -87,11 +89,11 @@ class DisableGateways { } /** - * Whether both gateways should be disabled or not. + * Whether all gateways should be disabled or not. * * @return bool */ - private function disable_both_gateways() : bool { + private function disable_all_gateways() : bool { if ( ! $this->settings->has( 'enabled' ) || ! $this->settings->get( 'enabled' ) ) { return true; } @@ -110,7 +112,8 @@ class DisableGateways { * @return bool */ private function needs_to_disable_gateways(): bool { - return $this->session_handler->order() !== null; + return $this->session_handler->order() !== null && + 'card' !== $this->session_handler->funding_source(); } /** diff --git a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php new file mode 100644 index 000000000..daae61dd3 --- /dev/null +++ b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php @@ -0,0 +1,331 @@ +id = self::ID; + $this->order_processor = $order_processor; + $this->config = $config; + $this->session_handler = $session_handler; + $this->refund_processor = $refund_processor; + $this->state = $state; + $this->transaction_url_provider = $transaction_url_provider; + $this->subscription_helper = $subscription_helper; + $this->environment = $environment; + $this->onboarded = $state->current_state() === State::STATE_ONBOARDED; + $this->payment_token_repository = $payment_token_repository; + $this->logger = $logger; + + if ( $this->onboarded ) { + $this->supports = array( 'refunds' ); + } + if ( + defined( 'PPCP_FLAG_SUBSCRIPTION' ) + && PPCP_FLAG_SUBSCRIPTION + && $this->gateways_enabled() + && $this->vault_setting_enabled() + ) { + $this->supports = array( + 'refunds', + 'products', + 'subscriptions', + 'subscription_cancellation', + 'subscription_suspension', + 'subscription_reactivation', + 'subscription_amount_changes', + 'subscription_date_changes', + 'subscription_payment_method_change', + 'subscription_payment_method_change_customer', + 'subscription_payment_method_change_admin', + 'multiple_subscriptions', + ); + } + + $this->method_title = __( 'PayPal Card Button', 'woocommerce-paypal-payments' ); + $this->method_description = __( 'The separate payment gateway with the card button. If disabled, the button is included in the PayPal gateway.', 'woocommerce-paypal-payments' ); + $this->title = $this->get_option( 'title', __( 'Debit & Credit Cards', 'woocommerce-paypal-payments' ) ); + $this->description = $this->get_option( 'description', '' ); + + $this->init_form_fields(); + $this->init_settings(); + + add_action( + 'woocommerce_update_options_payment_gateways_' . $this->id, + array( + $this, + 'process_admin_options', + ) + ); + } + + /** + * Whether the Gateway needs to be setup. + * + * @return bool + */ + public function needs_setup(): bool { + return ! $this->onboarded; + } + + /** + * Initializes the form fields. + */ + public function init_form_fields() { + $this->form_fields = array( + 'enabled' => array( + 'title' => __( 'Enable/Disable', 'woocommerce-paypal-payments' ), + 'type' => 'checkbox', + 'label' => __( 'PayPal Card Button', 'woocommerce-paypal-payments' ), + 'default' => 'no', + 'desc_tip' => true, + 'description' => __( 'Enable/Disable the separate payment gateway with the card button.', 'woocommerce-paypal-payments' ), + ), + 'title' => array( + 'title' => __( 'Title', 'woocommerce-paypal-payments' ), + 'type' => 'text', + 'default' => $this->title, + 'desc_tip' => true, + 'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce-paypal-payments' ), + ), + 'description' => array( + 'title' => __( 'Description', 'woocommerce-paypal-payments' ), + 'type' => 'text', + 'default' => $this->description, + 'desc_tip' => true, + 'description' => __( 'This controls the description which the user sees during checkout.', 'woocommerce-paypal-payments' ), + ), + ); + } + + /** + * Process payment for a WooCommerce order. + * + * @param int $order_id The WooCommerce order id. + * + * @return array + */ + public function process_payment( $order_id ) { + $wc_order = wc_get_order( $order_id ); + if ( ! is_a( $wc_order, WC_Order::class ) ) { + return $this->handle_payment_failure( + null, + new GatewayGenericException( new Exception( 'WC order was not found.' ) ) + ); + } + + /** + * If customer has chosen change Subscription payment. + */ + if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) { + $saved_paypal_payment = filter_input( INPUT_POST, 'saved_paypal_payment', FILTER_SANITIZE_STRING ); + if ( $saved_paypal_payment ) { + update_post_meta( $order_id, 'payment_token_id', $saved_paypal_payment ); + + return $this->handle_payment_success( $wc_order ); + } + } + + /** + * If the WC_Order is paid through the approved webhook. + */ + //phpcs:disable WordPress.Security.NonceVerification.Recommended + if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) { + return $this->handle_payment_success( $wc_order ); + } + //phpcs:enable WordPress.Security.NonceVerification.Recommended + + try { + if ( ! $this->order_processor->process( $wc_order ) ) { + return $this->handle_payment_failure( + $wc_order, + new Exception( + $this->order_processor->last_error() + ) + ); + } + + if ( $this->subscription_helper->has_subscription( $order_id ) ) { + $this->schedule_saved_payment_check( $order_id, $wc_order->get_customer_id() ); + } + + return $this->handle_payment_success( $wc_order ); + } catch ( PayPalApiException $error ) { + return $this->handle_payment_failure( + $wc_order, + new Exception( + Messages::generic_payment_error_message() . ' ' . $error->getMessage(), + $error->getCode(), + $error + ) + ); + } catch ( RuntimeException $error ) { + return $this->handle_payment_failure( $wc_order, $error ); + } + } + + /** + * Process refund. + * + * If the gateway declares 'refunds' support, this will allow it to refund. + * a passed in amount. + * + * @param int $order_id Order ID. + * @param float $amount Refund amount. + * @param string $reason Refund reason. + * @return boolean True or false based on success, or a WP_Error object. + */ + public function process_refund( $order_id, $amount = null, $reason = '' ) { + $order = wc_get_order( $order_id ); + if ( ! is_a( $order, \WC_Order::class ) ) { + return false; + } + return $this->refund_processor->process( $order, (float) $amount, (string) $reason ); + } + + /** + * Return transaction url for this gateway and given order. + * + * @param \WC_Order $order WC order to get transaction url by. + * + * @return string + */ + public function get_transaction_url( $order ): string { + $this->view_transaction_url = $this->transaction_url_provider->get_transaction_url_base( $order ); + + return parent::get_transaction_url( $order ); + } +} diff --git a/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php b/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php index 24faa5201..631a078c7 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php +++ b/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php @@ -9,6 +9,7 @@ declare( strict_types=1 ); namespace WooCommerce\PayPalCommerce\WcGateway\Settings; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway; @@ -58,7 +59,7 @@ class SectionsRenderer { /** * Renders the Sections tab. */ - public function render() { + public function render(): void { if ( ! $this->should_render() ) { return; } @@ -66,6 +67,7 @@ class SectionsRenderer { $sections = array( PayPalGateway::ID => __( 'PayPal Checkout', 'woocommerce-paypal-payments' ), CreditCardGateway::ID => __( 'PayPal Card Processing', 'woocommerce-paypal-payments' ), + CardButtonGateway::ID => __( 'PayPal Card Button', 'woocommerce-paypal-payments' ), PayUponInvoiceGateway::ID => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ), WebhooksStatusPage::ID => __( 'Webhooks Status', 'woocommerce-paypal-payments' ), ); @@ -80,8 +82,8 @@ class SectionsRenderer { foreach ( $sections as $id => $label ) { $url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway&' . self::KEY . '=' . $id ); - if ( PayUponInvoiceGateway::ID === $id ) { - $url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-pay-upon-invoice-gateway' ); + if ( in_array( $id, array( PayUponInvoiceGateway::ID, CardButtonGateway::ID ), true ) ) { + $url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=' . $id ); } echo '
  • ' . esc_html( $label ) . ' ' . ( end( $array_keys ) === $id ? '' : '|' ) . '
  • '; } diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 8bb469a9d..a828e7684 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -284,6 +284,8 @@ class WCGatewayModule implements ModuleInterface { $methods[] = $container->get( 'wcgateway.credit-card-gateway' ); } + $methods[] = $container->get( 'wcgateway.card-button-gateway' ); + if ( 'DE' === $container->get( 'api.shop.country' ) && 'EUR' === $container->get( 'api.shop.currency' ) ) { $methods[] = $container->get( 'wcgateway.pay-upon-invoice-gateway' ); } diff --git a/tests/stubs/WC_Payment_Gateway.php b/tests/stubs/WC_Payment_Gateway.php index 6fbb823c8..bb87ee010 100644 --- a/tests/stubs/WC_Payment_Gateway.php +++ b/tests/stubs/WC_Payment_Gateway.php @@ -4,7 +4,7 @@ declare(strict_types=1); class WC_Payment_Gateway { - protected function get_option(string $key) : string { + public function get_option(string $key, $empty_value = null) { return $key; } @@ -19,4 +19,4 @@ class WC_Payment_Gateway public function process_admin_options() { } -} \ No newline at end of file +} From 619e9d2552dfdcd87609e1379a5f3e05a3aadb09 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 19 Jul 2022 09:24:19 +0300 Subject: [PATCH 42/86] Change address handling for card button Make the name field to always appear and send at least one address --- .../src/Endpoint/OrderEndpoint.php | 2 +- modules/ppcp-api-client/src/Entity/Payer.php | 57 ++++++++++++------- .../src/Endpoint/CreateOrderEndpoint.php | 24 +++++++- .../ApiClient/Endpoint/OrderEndpointTest.php | 6 -- 4 files changed, 59 insertions(+), 30 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php index 41448a542..f06a91a95 100644 --- a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php @@ -192,7 +192,7 @@ class OrderEndpoint { 'application_context' => $this->application_context_repository ->current_context( $shipping_preference )->to_array(), ); - if ( $payer && ! empty( $payer->email_address() ) && ! empty( $payer->name() ) ) { + if ( $payer && ! empty( $payer->email_address() ) ) { $data['payer'] = $payer->to_array(); } if ( $payment_token ) { diff --git a/modules/ppcp-api-client/src/Entity/Payer.php b/modules/ppcp-api-client/src/Entity/Payer.php index d20fd215a..45164c1f5 100644 --- a/modules/ppcp-api-client/src/Entity/Payer.php +++ b/modules/ppcp-api-client/src/Entity/Payer.php @@ -18,7 +18,7 @@ class Payer { /** * The name. * - * @var PayerName + * @var PayerName|null */ private $name; @@ -46,7 +46,7 @@ class Payer { /** * The address. * - * @var Address + * @var Address|null */ private $address; @@ -67,7 +67,7 @@ class Payer { /** * Payer constructor. * - * @param PayerName $name The name. + * @param PayerName|null $name The name. * @param string $email_address The email. * @param string $payer_id The payer id. * @param Address|null $address The address. @@ -76,7 +76,7 @@ class Payer { * @param PayerTaxInfo|null $tax_info The tax info. */ public function __construct( - PayerName $name, + ?PayerName $name, string $email_address, string $payer_id, Address $address = null, @@ -97,12 +97,21 @@ class Payer { /** * Returns the name. * - * @return PayerName + * @return PayerName|null */ - public function name(): PayerName { + public function name(): ?PayerName { return $this->name; } + /** + * Sets the name. + * + * @param PayerName|null $name The value. + */ + public function set_name( ?PayerName $name ): void { + $this->name = $name; + } + /** * Returns the email address. * @@ -139,6 +148,15 @@ class Payer { return $this->address; } + /** + * Sets the address. + * + * @param Address|null $address The value. + */ + public function set_address( ?Address $address ): void { + $this->address = $address; + } + /** * Returns the phone. * @@ -164,27 +182,26 @@ class Payer { */ public function to_array() { $payer = array( - 'name' => $this->name()->to_array(), 'email_address' => $this->email_address(), ); - if ( $this->address() ) { - $payer['address'] = $this->address->to_array(); - if ( 2 !== strlen( $this->address()->country_code() ) ) { - unset( $payer['address'] ); - } + if ( $this->name ) { + $payer['name'] = $this->name->to_array(); } - if ( $this->payer_id() ) { - $payer['payer_id'] = $this->payer_id(); + if ( $this->address && 2 === strlen( $this->address->country_code() ) ) { + $payer['address'] = $this->address->to_array(); + } + if ( $this->payer_id ) { + $payer['payer_id'] = $this->payer_id; } - if ( $this->phone() ) { - $payer['phone'] = $this->phone()->to_array(); + if ( $this->phone ) { + $payer['phone'] = $this->phone->to_array(); } - if ( $this->tax_info() ) { - $payer['tax_info'] = $this->tax_info()->to_array(); + if ( $this->tax_info ) { + $payer['tax_info'] = $this->tax_info->to_array(); } - if ( $this->birthdate() ) { - $payer['birth_date'] = $this->birthdate()->format( 'Y-m-d' ); + if ( $this->birthdate ) { + $payer['birth_date'] = $this->birthdate->format( 'Y-m-d' ); } return $payer; } diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index c0907b4af..8ff3c9476 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -14,6 +14,7 @@ use Psr\Log\LoggerInterface; use stdClass; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\Amount; +use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext; use WooCommerce\PayPalCommerce\ApiClient\Entity\Money; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer; @@ -332,18 +333,35 @@ class CreateOrderEndpoint implements EndpointInterface { private function create_paypal_order( \WC_Order $wc_order = null ): Order { assert( $this->purchase_unit instanceof PurchaseUnit ); + $funding_source = $this->parsed_request_data['funding_source'] ?? ''; + $payer = $this->payer( $this->parsed_request_data, $wc_order ); + $shipping_preference = $this->shipping_preference_factory->from_state( $this->purchase_unit, $this->parsed_request_data['context'], WC()->cart, - $this->parsed_request_data['funding_source'] ?? '' + $funding_source ); + if ( 'card' === $funding_source ) { + if ( ApplicationContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS === $shipping_preference ) { + if ( $payer ) { + $payer->set_address( null ); + + } + } + if ( ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING === $shipping_preference ) { + if ( $payer ) { + $payer->set_name( null ); + } + } + } + try { return $this->api_endpoint->create( array( $this->purchase_unit ), $shipping_preference, - $this->payer( $this->parsed_request_data, $wc_order ), + $payer, null, $this->payment_method() ); @@ -365,7 +383,7 @@ class CreateOrderEndpoint implements EndpointInterface { return $this->api_endpoint->create( array( $this->purchase_unit ), $shipping_preference, - $this->payer( $this->parsed_request_data, $wc_order ), + $payer, null, $this->payment_method() ); diff --git a/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php b/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php index 642d9c4b6..4052febd2 100644 --- a/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php +++ b/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php @@ -1046,8 +1046,6 @@ class OrderEndpointTest extends TestCase $payer = Mockery::mock(Payer::class); $payer->expects('email_address')->andReturn('email@email.com'); - $payerName = Mockery::mock(PayerName::class); - $payer->expects('name')->andReturn($payerName); $payer->expects('to_array')->andReturn(['payer']); $result = $testee->create([$purchaseUnit], ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE, $payer); $this->assertEquals($expectedOrder, $result); @@ -1138,8 +1136,6 @@ class OrderEndpointTest extends TestCase $payer = Mockery::mock(Payer::class); $payer->expects('email_address')->andReturn('email@email.com'); - $payerName = Mockery::mock(PayerName::class); - $payer->expects('name')->andReturn($payerName); $payer->expects('to_array')->andReturn(['payer']); $testee->create([$purchaseUnit], ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING, $payer); } @@ -1229,8 +1225,6 @@ class OrderEndpointTest extends TestCase $this->expectException(RuntimeException::class); $payer = Mockery::mock(Payer::class); $payer->expects('email_address')->andReturn('email@email.com'); - $payerName = Mockery::mock(PayerName::class); - $payer->expects('name')->andReturn($payerName); $payer->expects('to_array')->andReturn(['payer']); $testee->create([$purchaseUnit], ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE, $payer); } From c11635343d1c780d1f1ffe7fc0b7d74a8bdfbec5 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 19 Jul 2022 09:29:12 +0300 Subject: [PATCH 43/86] Remove dcc continuation leftovers --- .../src/Checkout/DisableGateways.php | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php index 0ff7dc8d1..8ecfce40c 100644 --- a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php +++ b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php @@ -79,12 +79,6 @@ class DisableGateways { return $methods; } - if ( $this->is_credit_card() ) { - return array( - CreditCardGateway::ID => $methods[ CreditCardGateway::ID ], - PayPalGateway::ID => $methods[ PayPalGateway::ID ], - ); - } return array( PayPalGateway::ID => $methods[ PayPalGateway::ID ] ); } @@ -112,23 +106,20 @@ class DisableGateways { * @return bool */ private function needs_to_disable_gateways(): bool { - return $this->session_handler->order() !== null && - 'card' !== $this->session_handler->funding_source(); - } - - /** - * Whether the current PayPal session is done via DCC payment. - * - * @return bool - */ - private function is_credit_card(): bool { $order = $this->session_handler->order(); if ( ! $order ) { return false; } - if ( ! $order->payment_source() || ! $order->payment_source()->card() ) { - return false; + + $source = $order->payment_source(); + if ( $source && $source->card() ) { + return false; // DCC. } + + if ( 'card' === $this->session_handler->funding_source() ) { + return false; // Card buttons. + } + return true; } } From 429260ca34997044163a19b0ed5718f77fea14a5 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 19 Jul 2022 11:14:32 +0300 Subject: [PATCH 44/86] Do not disable card funding source if card gateway enabled --- modules/ppcp-button/src/Assets/SmartButton.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index c581a26a8..ae187b2da 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -958,7 +958,10 @@ class SmartButton implements SmartButtonInterface { $is_dcc_enabled = $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ); - if ( is_checkout() && $is_dcc_enabled ) { + $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); + $is_separate_card_enabled = isset( $available_gateways[ CardButtonGateway::ID ] ); + + if ( is_checkout() && ( $is_dcc_enabled || $is_separate_card_enabled ) ) { $key = array_search( 'card', $disable_funding, true ); if ( false !== $key ) { unset( $disable_funding[ $key ] ); From 51001c388e5d45374279bbb42b9b4ee2376bd43e Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 19 Jul 2022 12:56:59 +0200 Subject: [PATCH 45/86] Change button text in oxxo gateway --- .../ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index 2d7f0a385..8aad79ddc 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -94,22 +94,34 @@ class OXXO { 2 ); - add_action('woocommerce_email_before_order_table', - function (WC_Order $order, bool $sent_to_admin) { - if( + add_action( + 'woocommerce_email_before_order_table', + function ( WC_Order $order, bool $sent_to_admin ) { + if ( ! $sent_to_admin && $order->get_payment_method() === OXXOGateway::ID && $order->has_status( 'on-hold' ) ) { $payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' ) ?? ''; - if($payer_action) { - echo '

    OXXO voucher

    '; + if ( $payer_action ) { + echo '

    OXXO voucher

    '; } } }, 10, 2 ); + + add_filter( + 'woocommerce_available_payment_gateways', + function( $available_gateways ) { + if ( array_key_exists( OXXOGateway::ID, $available_gateways ) ) { + $available_gateways[ OXXOGateway::ID ]->order_button_text = __( 'Pay with OXXO', 'woocommerce-paypal-payments' ); + } + + return $available_gateways; + } + ); } /** From dc0bc3ba0ac7cd8b7e70024813ebfee79e4a8f8b Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 19 Jul 2022 14:04:19 +0300 Subject: [PATCH 46/86] Render all buttons separately to allow hiding sources in paypal gateway --- .../resources/js/modules/Renderer/Renderer.js | 46 +++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js index 2ceee9a0a..d40e18feb 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js @@ -6,30 +6,56 @@ class Renderer { this.creditCardRenderer = creditCardRenderer; this.onSmartButtonClick = onSmartButtonClick; this.onSmartButtonsInit = onSmartButtonsInit; + + this.renderedSources = new Set(); } render(contextConfig, settingsOverride = {}) { const settings = merge(this.defaultSettings, settingsOverride); - this.renderButtons(settings.button.wrapper, settings.button.style, contextConfig); + const separateGatewayFundingSources = Object.keys(settings.separate_buttons); + + for (const fundingSource of paypal.getFundingSources() + .filter(s => + !separateGatewayFundingSources.includes(s) || + !document.querySelector(settings.separate_buttons[s].wrapper) // disabled gateway + )) { + let style = settings.button.style; + if (fundingSource !== 'paypal') { + style = { + shape: style.shape, + }; + } + + this.renderButtons( + settings.button.wrapper, + style, + contextConfig, + fundingSource + ); + } + this.creditCardRenderer.render(settings.hosted_fields.wrapper, contextConfig); + for (const [fundingSource, data] of Object.entries(settings.separate_buttons)) { this.renderButtons( data.wrapper, data.style, - { - ...contextConfig, - fundingSource: fundingSource, - } + contextConfig, + fundingSource ); } } - renderButtons(wrapper, style, contextConfig) { - if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper) || 'undefined' === typeof paypal.Buttons ) { + renderButtons(wrapper, style, contextConfig, fundingSource = null) { + if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper, fundingSource) || 'undefined' === typeof paypal.Buttons ) { return; } + if (fundingSource) { + contextConfig.fundingSource = fundingSource; + } + const btn = paypal.Buttons({ style, ...contextConfig, @@ -41,10 +67,12 @@ class Renderer { } btn.render(wrapper); + + this.renderedSources.add(wrapper + fundingSource ?? ''); } - isAlreadyRendered(wrapper) { - return document.querySelector(wrapper).hasChildNodes(); + isAlreadyRendered(wrapper, fundingSource) { + return this.renderedSources.has(wrapper + fundingSource ?? ''); } hideButtons(element) { From 1b1091051df25fcd198c56259ceda37e93f9c146 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 19 Jul 2022 15:56:12 +0300 Subject: [PATCH 47/86] Render separately only if needed to avoid breaking things. e.g. horizontal layout --- .../resources/js/modules/Renderer/Renderer.js | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js index d40e18feb..1bd4ef757 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js @@ -13,35 +13,43 @@ class Renderer { render(contextConfig, settingsOverride = {}) { const settings = merge(this.defaultSettings, settingsOverride); - const separateGatewayFundingSources = Object.keys(settings.separate_buttons); - - for (const fundingSource of paypal.getFundingSources() - .filter(s => - !separateGatewayFundingSources.includes(s) || - !document.querySelector(settings.separate_buttons[s].wrapper) // disabled gateway - )) { - let style = settings.button.style; - if (fundingSource !== 'paypal') { - style = { - shape: style.shape, - }; - } + const enabledSeparateGateways = Object.fromEntries(Object.entries( + settings.separate_buttons).filter(([s, data]) => document.querySelector(data.wrapper) + )); + const hasEnabledSeparateGateways = Object.keys(enabledSeparateGateways).length !== 0; + if (!hasEnabledSeparateGateways) { this.renderButtons( settings.button.wrapper, - style, - contextConfig, - fundingSource + settings.button.style, + contextConfig ); + } else { + // render each button separately + for (const fundingSource of paypal.getFundingSources().filter(s => !(s in enabledSeparateGateways))) { + let style = settings.button.style; + if (fundingSource !== 'paypal') { + style = { + shape: style.shape, + }; + } + + this.renderButtons( + settings.button.wrapper, + style, + contextConfig, + fundingSource + ); + } } this.creditCardRenderer.render(settings.hosted_fields.wrapper, contextConfig); - for (const [fundingSource, data] of Object.entries(settings.separate_buttons)) { + for (const [fundingSource, data] of Object.entries(enabledSeparateGateways)) { this.renderButtons( data.wrapper, data.style, - contextConfig, + contextConfig, fundingSource ); } From e1cd7d5ad18adcbcc10027fe86c8047f4b7df0f8 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 20 Jul 2022 11:37:28 +0200 Subject: [PATCH 48/86] Add update status note for capture denied --- modules/ppcp-wc-gateway/resources/js/oxxo.js | 4 ++-- modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 14 ++++++++------ .../src/Handler/PaymentCaptureReversed.php | 7 ++++++- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-wc-gateway/resources/js/oxxo.js b/modules/ppcp-wc-gateway/resources/js/oxxo.js index 0dcb26f29..a6d976a9c 100644 --- a/modules/ppcp-wc-gateway/resources/js/oxxo.js +++ b/modules/ppcp-wc-gateway/resources/js/oxxo.js @@ -5,8 +5,8 @@ document.addEventListener( if(data.payer_action && data.payer_action !== '') { const width = screen.width / 2; const height = screen.height / 2; - const left = (screen.width / 2) - (width / 2); - const top = (screen.height / 2) - (height / 2); + const left = width - (width / 2); + const top = height - (height / 2); window.open( data.payer_action, '_blank', diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index 8aad79ddc..7fbcd450d 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -113,14 +113,16 @@ class OXXO { ); add_filter( - 'woocommerce_available_payment_gateways', - function( $available_gateways ) { - if ( array_key_exists( OXXOGateway::ID, $available_gateways ) ) { - $available_gateways[ OXXOGateway::ID ]->order_button_text = __( 'Pay with OXXO', 'woocommerce-paypal-payments' ); + 'ppcp_payment_capture_reversed_webhook_update_status_note', + function( $note, $wc_order, $event_type ) { + if ( $wc_order->get_payment_method() === OXXOGateway::ID && $event_type === 'PAYMENT.CAPTURE.DENIED' ) { + $note = __( 'OXXO voucher has expired or the buyer didn\'t complete the payment successfully.', 'woocommerce-paypal-payments' ); } - return $available_gateways; - } + return $note; + }, + 10, + 2 ); } diff --git a/modules/ppcp-webhooks/src/Handler/PaymentCaptureReversed.php b/modules/ppcp-webhooks/src/Handler/PaymentCaptureReversed.php index bef765503..5354e555c 100644 --- a/modules/ppcp-webhooks/src/Handler/PaymentCaptureReversed.php +++ b/modules/ppcp-webhooks/src/Handler/PaymentCaptureReversed.php @@ -112,12 +112,17 @@ class PaymentCaptureReversed implements RequestHandler { return rest_ensure_response( $response ); } + /** + * Allows adding an update status note. + */ + $note = apply_filters( 'ppcp_payment_capture_reversed_webhook_update_status_note', '', $wc_order, $request['event_type'] ); + /** * The WooCommerce order. * * @var \WC_Order $wc_order */ - $response['success'] = (bool) $wc_order->update_status( 'cancelled' ); + $response['success'] = (bool) $wc_order->update_status( 'cancelled', $note ); $message = $response['success'] ? sprintf( // translators: %1$s is the order id. From eb4fc639fcff330d4ab5ca9e3c782e4cfd0c4176 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 20 Jul 2022 12:16:33 +0200 Subject: [PATCH 49/86] Fix psalm --- .../src/Endpoint/ReturnUrlEndpoint.php | 2 +- .../ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 4 +- .../src/Handler/PaymentCapturePending.php | 58 +++++++++---------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php b/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php index 0270dae48..0fdc07954 100644 --- a/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php +++ b/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php @@ -69,7 +69,7 @@ class ReturnUrlEndpoint { } $wc_order = wc_get_order( $wc_order_id ); - if ( ! $wc_order ) { + if ( ! is_a( $wc_order, \WC_Order::class ) ) { exit(); } diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index 7fbcd450d..4d7936286 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -114,7 +114,7 @@ class OXXO { add_filter( 'ppcp_payment_capture_reversed_webhook_update_status_note', - function( $note, $wc_order, $event_type ) { + function( string $note, WC_Order $wc_order, string $event_type ): string { if ( $wc_order->get_payment_method() === OXXOGateway::ID && $event_type === 'PAYMENT.CAPTURE.DENIED' ) { $note = __( 'OXXO voucher has expired or the buyer didn\'t complete the payment successfully.', 'woocommerce-paypal-payments' ); } @@ -122,7 +122,7 @@ class OXXO { return $note; }, 10, - 2 + 3 ); } diff --git a/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php b/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php index 92507c9f3..1467e663f 100644 --- a/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php +++ b/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Webhooks\Handler; use Psr\Log\LoggerInterface; +use WP_REST_Request; use WP_REST_Response; /** @@ -60,12 +61,35 @@ class PaymentCapturePending implements RequestHandler { /** * Responsible for handling the request. * - * @param \WP_REST_Request $request The request. + * @param WP_REST_Request $request The request. * * @return WP_REST_Response */ - public function handle_request( \WP_REST_Request $request ): WP_REST_Response { + public function handle_request( WP_REST_Request $request ): WP_REST_Response { $response = array( 'success' => false ); + $order_id = $request['resource'] !== null && isset( $request['resource']['custom_id'] ) + ? $this->sanitize_custom_id( $request['resource']['custom_id'] ) + : 0; + if ( ! $order_id ) { + $message = sprintf( + // translators: %s is the PayPal webhook Id. + __( + 'No order for webhook event %s was found.', + 'woocommerce-paypal-payments' + ), + $request['id'] !== null && isset( $request['id'] ) ? $request['id'] : '' + ); + $this->logger->log( + 'warning', + $message, + array( + 'request' => $request, + ) + ); + $response['message'] = $message; + return new WP_REST_Response( $response ); + } + $resource = $request['resource']; if ( ! is_array( $resource ) ) { $message = 'Resource data not found in webhook request.'; @@ -74,36 +98,12 @@ class PaymentCapturePending implements RequestHandler { return new WP_REST_Response( $response ); } - $order_id = isset( $request['resource']['custom_id'] ) - ? $this->sanitize_custom_id( $request['resource']['custom_id'] ) - : 0; - - if ( ! $order_id ) { - $message = sprintf( - // translators: %s is the PayPal webhook Id. - __( - 'No order for webhook event %s was found.', - 'woocommerce-paypal-payments' - ), - isset( $request['id'] ) ? $request['id'] : '' - ); - $this->logger->log( - 'warning', - $message, - array( - 'request' => $request, - ) - ); - $response['message'] = $message; - return rest_ensure_response( $response ); - } - $wc_order = wc_get_order( $order_id ); if ( ! is_a( $wc_order, \WC_Order::class ) ) { $message = sprintf( // translators: %s is the PayPal refund Id. __( 'Order for PayPal refund %s not found.', 'woocommerce-paypal-payments' ), - isset( $request['resource']['id'] ) ? $request['resource']['id'] : '' + $request['resource'] !== null && isset( $request['resource']['id'] ) ? $request['resource']['id'] : '' ); $this->logger->log( 'warning', @@ -113,11 +113,11 @@ class PaymentCapturePending implements RequestHandler { ) ); $response['message'] = $message; - return rest_ensure_response( $response ); + return new WP_REST_Response( $response ); } if ( $wc_order->get_status() === 'pending' ) { - $wc_order->update_status('on-hold', __('Payment initiation was successful, and is waiting for the buyer to complete the payment.', 'woocommerce-paypal-payments')); + $wc_order->update_status( 'on-hold', __( 'Payment initiation was successful, and is waiting for the buyer to complete the payment.', 'woocommerce-paypal-payments' ) ); } From 323ec8720cb71e2f018dfa792a9b9fc26cd878f0 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 20 Jul 2022 15:49:56 +0200 Subject: [PATCH 50/86] Add unit test for oxxo gateway process payment --- .../Gateway/OXXO/OXXOGatewayTest.php | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 tests/PHPUnit/WcGateway/Gateway/OXXO/OXXOGatewayTest.php diff --git a/tests/PHPUnit/WcGateway/Gateway/OXXO/OXXOGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/OXXO/OXXOGatewayTest.php new file mode 100644 index 000000000..849353b86 --- /dev/null +++ b/tests/PHPUnit/WcGateway/Gateway/OXXO/OXXOGatewayTest.php @@ -0,0 +1,140 @@ +orderEndpoint = Mockery::mock(OrderEndpoint::class); + $this->purchaseUnitFactory = Mockery::mock(PurchaseUnitFactory::class); + $this->shippingPreferenceFactory = Mockery::mock(ShippingPreferenceFactory::class); + $this->logger = Mockery::mock(LoggerInterface::class); + + $this->wcOrder = Mockery::mock(WC_Order::class); + when('wc_get_order')->justReturn($this->wcOrder); + when('get_option')->justReturn([ + 'title' => 'foo', + 'description' => 'bar', + ]); + + $this->testee = new OXXOGateway( + $this->orderEndpoint, + $this->purchaseUnitFactory, + $this->shippingPreferenceFactory, + $this->logger + ); + } + + public function testProcessPaymentSuccess() + { + $this->wcOrder->shouldReceive('get_billing_first_name')->andReturn('John'); + $this->wcOrder->shouldReceive('get_billing_last_name')->andReturn('Doe'); + $this->wcOrder->shouldReceive('get_billing_email')->andReturn('foo@bar.com'); + $this->wcOrder->shouldReceive('get_billing_country')->andReturn('MX'); + + list($purchaseUnit, $shippingPreference) = $this->setStubs(); + + $linkHref = 'https://sandbox.paypal.com/payment/oxxo?token=ABC123'; + $this->orderEndpoint + ->shouldReceive('confirm_payment_source') + ->with('1', [ + 'oxxo' => [ + 'name' => 'John Doe', + 'email' => 'foo@bar.com', + 'country_code' => 'MX', + ] + ] + )->andReturn((object)[ + 'links' => [ + (object)[ + 'rel' => 'payer-action', + 'href' => $linkHref, + ], + ] + ]); + + $order = Mockery::mock(Order::class); + $order->shouldReceive('id')->andReturn('1'); + + $this->orderEndpoint + ->shouldReceive('create') + ->with([$purchaseUnit], $shippingPreference) + ->andReturn($order); + + $this->wcOrder + ->shouldReceive('add_meta_data') + ->with('ppcp_oxxo_payer_action', $linkHref) + ->andReturn(true); + $this->wcOrder->shouldReceive('save_meta_data'); + + $woocommerce = Mockery::mock(\WooCommerce::class); + $cart = Mockery::mock(\WC_Cart::class); + when('WC')->justReturn($woocommerce); + $woocommerce->cart = $cart; + $cart->shouldReceive('empty_cart'); + + $result = $this->testee->process_payment(1); + $this->assertEquals('success', $result['result']); + } + + public function testProcessPaymentFailure() + { + list($purchaseUnit, $shippingPreference) = $this->setStubs(); + + $this->orderEndpoint + ->shouldReceive('create') + ->with([$purchaseUnit], $shippingPreference) + ->andThrows(RuntimeException::class); + + $this->logger->shouldReceive('error'); + when('wc_add_notice')->justReturn(); + when('wc_get_checkout_url')->justReturn(); + $this->wcOrder->shouldReceive('update_status'); + + $result = $this->testee->process_payment(1); + $this->assertEquals('failure', $result['result']); + + } + + /** + * @return array + */ + private function setStubs(): array + { + $purchaseUnit = Mockery::mock(PurchaseUnit::class); + $this->purchaseUnitFactory + ->shouldReceive('from_wc_order') + ->with($this->wcOrder) + ->andReturn($purchaseUnit); + + $shippingPreference = 'SOME_SHIPPING_PREFERENCE'; + $this->shippingPreferenceFactory + ->shouldReceive('from_state') + ->with($purchaseUnit, 'checkout') + ->andReturn($shippingPreference); + return array($purchaseUnit, $shippingPreference); + } +} From 0c17c6b1de9adb0ca477092e7276cb0380898503 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 20 Jul 2022 17:28:12 +0300 Subject: [PATCH 51/86] Allow to choose one of 3 card billing data handling modes also handle it in one place, no need to mess with payer parsing and JS --- .../ActionHandler/CheckoutActionHandler.js | 6 +- modules/ppcp-button/services.php | 1 + .../ppcp-button/src/Assets/SmartButton.php | 1 - .../src/Endpoint/CreateOrderEndpoint.php | 37 ++++++--- modules/ppcp-wc-gateway/services.php | 79 +++++++++++++++++-- .../ppcp-wc-gateway/src/CardBillingMode.php | 19 +++++ .../Endpoint/CreateOrderEndpointTest.php | 2 + 7 files changed, 121 insertions(+), 24 deletions(-) create mode 100644 modules/ppcp-wc-gateway/src/CardBillingMode.php diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js index cc9c75ae7..8b4dd7501 100644 --- a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js +++ b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js @@ -13,7 +13,7 @@ class CheckoutActionHandler { configuration() { const spinner = this.spinner; const createOrder = (data, actions) => { - let payer = payerData(); + const payer = payerData(); const bnCode = typeof this.config.bn_codes[this.config.context] !== 'undefined' ? this.config.bn_codes[this.config.context] : ''; @@ -29,10 +29,6 @@ class CheckoutActionHandler { const paymentMethod = getCurrentPaymentMethod(); const fundingSource = window.ppcpFundingSource; - if (fundingSource === 'card' && !PayPalCommerceGateway.use_form_billing_data_for_cards) { - payer = null; - } - return fetch(this.config.ajax.create_order.endpoint, { method: 'POST', body: JSON.stringify({ diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php index 3b4b710fd..cd30058cf 100644 --- a/modules/ppcp-button/services.php +++ b/modules/ppcp-button/services.php @@ -133,6 +133,7 @@ return array( $settings, $early_order_handler, $registration_needed, + $container->get( 'wcgateway.settings.card_billing_data_mode' ), $logger ); }, diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index ae187b2da..fd465cfc2 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -891,7 +891,6 @@ class SmartButton implements SmartButtonInterface { 'single_product_buttons_enabled' => $this->settings->has( 'button_product_enabled' ) && $this->settings->get( 'button_product_enabled' ), 'mini_cart_buttons_enabled' => $this->settings->has( 'button_mini-cart_enabled' ) && $this->settings->get( 'button_mini-cart_enabled' ), 'basic_checkout_validation_enabled' => $this->basic_checkout_validation_enabled, - 'use_form_billing_data_for_cards' => $this->settings->has( 'use_form_billing_data_for_cards' ) && $this->settings->get( 'use_form_billing_data_for_cards' ), ); if ( $this->style_for_context( 'layout', 'mini-cart' ) !== 'horizontal' ) { diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index 8ff3c9476..b9a59c490 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -28,6 +28,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; +use WooCommerce\PayPalCommerce\WcGateway\CardBillingMode; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; @@ -120,6 +121,13 @@ class CreateOrderEndpoint implements EndpointInterface { */ private $registration_needed; + /** + * The value of card_billing_data_mode from the settings. + * + * @var string + */ + protected $card_billing_data_mode; + /** * The logger. * @@ -139,6 +147,7 @@ class CreateOrderEndpoint implements EndpointInterface { * @param Settings $settings The Settings object. * @param EarlyOrderHandler $early_order_handler The EarlyOrderHandler object. * @param bool $registration_needed Whether a new user must be registered during checkout. + * @param string $card_billing_data_mode The value of card_billing_data_mode from the settings. * @param LoggerInterface $logger The logger. */ public function __construct( @@ -151,6 +160,7 @@ class CreateOrderEndpoint implements EndpointInterface { Settings $settings, EarlyOrderHandler $early_order_handler, bool $registration_needed, + string $card_billing_data_mode, LoggerInterface $logger ) { @@ -163,6 +173,7 @@ class CreateOrderEndpoint implements EndpointInterface { $this->settings = $settings; $this->early_order_handler = $early_order_handler; $this->registration_needed = $registration_needed; + $this->card_billing_data_mode = $card_billing_data_mode; $this->logger = $logger; } @@ -344,16 +355,21 @@ class CreateOrderEndpoint implements EndpointInterface { ); if ( 'card' === $funding_source ) { - if ( ApplicationContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS === $shipping_preference ) { - if ( $payer ) { - $payer->set_address( null ); - + if ( CardBillingMode::MINIMAL_INPUT === $this->card_billing_data_mode ) { + if ( ApplicationContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS === $shipping_preference ) { + if ( $payer ) { + $payer->set_address( null ); + } + } + if ( ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING === $shipping_preference ) { + if ( $payer ) { + $payer->set_name( null ); + } } } - if ( ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING === $shipping_preference ) { - if ( $payer ) { - $payer->set_name( null ); - } + + if ( CardBillingMode::NO_WC === $this->card_billing_data_mode ) { + $payer = null; } } @@ -423,10 +439,7 @@ class CreateOrderEndpoint implements EndpointInterface { $payer = $this->payer_factory->from_paypal_response( json_decode( wp_json_encode( $data['payer'] ) ) ); } - $use_form_billing_data_for_cards = $this->settings->has( 'use_form_billing_data_for_cards' ) && - (bool) $this->settings->get( 'use_form_billing_data_for_cards' ); - - if ( ! $payer && isset( $data['form'] ) && $use_form_billing_data_for_cards ) { + if ( ! $payer && isset( $data['form'] ) ) { $form_fields = $data['form']; if ( is_array( $form_fields ) && isset( $form_fields['billing_email'] ) && '' !== $form_fields['billing_email'] ) { diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index a4c7b35a6..5f95d3af8 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -869,13 +869,19 @@ return array( 'requirements' => array(), 'gateway' => 'paypal', ), - 'use_form_billing_data_for_cards' => array( - 'title' => __( 'Send billing data for cards', 'woocommerce-paypal-payments' ), - 'type' => 'checkbox', + 'card_billing_data_mode' => array( + 'title' => __( 'Card billing data handling', 'woocommerce-paypal-payments' ), + 'type' => 'select', + 'class' => array(), + 'input_class' => array( 'wc-enhanced-select' ), 'desc_tip' => true, - 'label' => __( 'Send Checkout billing form data to PayPal smart card fields', 'woocommerce-paypal-payments' ), - 'description' => __( 'This increases convenience for the users, but can cause issues if card details do not match the billing data.', 'woocommerce-paypal-payments' ), - 'default' => false, + 'description' => __( 'Using the WC form data increases convenience for the customers, but can cause issues if card details do not match the billing data in the checkout form.', 'woocommerce-paypal-payments' ), + 'default' => $container->get( 'wcgateway.settings.card_billing_data_mode.default' ), + 'options' => array( + CardBillingMode::USE_WC => __( 'Use WC checkout form data (do not show any address fields)', 'woocommerce-paypal-payments' ), + CardBillingMode::MINIMAL_INPUT => __( 'Request only name and postal code', 'woocommerce-paypal-payments' ), + CardBillingMode::NO_WC => __( 'Do not use WC checkout form data (request all address fields)', 'woocommerce-paypal-payments' ), + ), 'screens' => array( State::STATE_START, State::STATE_ONBOARDED, @@ -2293,4 +2299,65 @@ return array( return $pay_later_label; }, + + 'wcgateway.settings.card_billing_data_mode.default' => static function ( ContainerInterface $container ): string { + return in_array( + $container->get( 'api.shop.country' ), + array( + 'AI', + 'AG', + 'AR', + 'AW', + 'BS', + 'BB', + 'BZ', + 'BM', + 'BO', + 'BR', + 'VG', + 'KY', + 'CL', + 'CO', + 'CR', + 'DM', + 'DO', + 'EC', + 'SV', + 'FK', + 'GF', + 'GD', + 'GP', + 'GT', + 'GY', + 'HN', + 'JM', + 'MQ', + 'MX', + 'MS', + 'AN', + 'NI', + 'PA', + 'PY', + 'PE', + 'KN', + 'LC', + 'PM', + 'VC', + 'SR', + 'TT', + 'TC', + 'UY', + 'VE', + ), + true + ) ? CardBillingMode::MINIMAL_INPUT : CardBillingMode::USE_WC; + }, + 'wcgateway.settings.card_billing_data_mode' => static function ( ContainerInterface $container ): string { + $settings = $container->get( 'wcgateway.settings' ); + assert( $settings instanceof ContainerInterface ); + + return $settings->has( 'card_billing_data_mode' ) ? + (string) $settings->get( 'card_billing_data_mode' ) : + $container->get( 'wcgateway.settings.card_billing_data_mode.default' ); + }, ); diff --git a/modules/ppcp-wc-gateway/src/CardBillingMode.php b/modules/ppcp-wc-gateway/src/CardBillingMode.php new file mode 100644 index 000000000..300b186ca --- /dev/null +++ b/modules/ppcp-wc-gateway/src/CardBillingMode.php @@ -0,0 +1,19 @@ + Date: Thu, 21 Jul 2022 10:13:59 +0300 Subject: [PATCH 52/86] Add fields to the basic js validation error message --- modules/ppcp-button/resources/js/button.js | 15 ++++++++++++++- modules/ppcp-button/src/Assets/SmartButton.php | 8 +++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index ed8c400cf..348fde7a5 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -38,9 +38,22 @@ const bootstrap = () => { requiredFields.each((i, input) => { jQuery(input).trigger('validate'); }); - if (jQuery('form.woocommerce-checkout .validate-required.woocommerce-invalid:visible').length) { + const invalidFields = Array.from(jQuery('form.woocommerce-checkout .validate-required.woocommerce-invalid:visible')); + if (invalidFields.length) { + const namesMap = PayPalCommerceGateway.labels.elements; + const labels = invalidFields.map(el => { + const name = el.querySelector('[name]')?.getAttribute('name'); + if (name && name in namesMap) { + return namesMap[name]; + } + return el.querySelector('label').textContent + .replaceAll('*', '') + .trim(); + }).filter(s => s.length > 2); + errorHandler.clear(); errorHandler.message(PayPalCommerceGateway.labels.error.js_validation); + labels.forEach(s => errorHandler.message(s)); // each message() call adds
  • return actions.reject(); } diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 33a2eb1a4..f5ce03dbd 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -852,7 +852,7 @@ class SmartButton implements SmartButtonInterface { ), 'messages' => $this->message_values(), 'labels' => array( - 'error' => array( + 'error' => array( 'generic' => __( 'Something went wrong. Please try again or choose another payment source.', 'woocommerce-paypal-payments' @@ -862,6 +862,12 @@ class SmartButton implements SmartButtonInterface { 'woocommerce-paypal-payments' ), ), + 'elements' => array( // Map
    => text, used for error messages. + 'terms' => __( + 'Terms and conditions', + 'woocommerce-paypal-payments' + ), + ), ), 'order_id' => 'pay-now' === $this->context() ? absint( $wp->query_vars['order-pay'] ) : 0, 'single_product_buttons_enabled' => $this->settings->has( 'button_product_enabled' ) && $this->settings->get( 'button_product_enabled' ), From ea79ec36d5b225f3966a1975bc89711fc68e59e4 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Thu, 21 Jul 2022 10:09:53 +0200 Subject: [PATCH 53/86] Add oxxo payer action link to both admin and customer order --- .../ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 37 +++++++++++++++++++ .../Gateway/PayUponInvoice/PayUponInvoice.php | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index 4d7936286..079a6e2d3 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -124,6 +124,43 @@ class OXXO { 10, 3 ); + + add_action( + 'add_meta_boxes', + function( string $post_type ) { + if ( $post_type === 'shop_order' ) { + $post_id = filter_input( INPUT_GET, 'post', FILTER_SANITIZE_STRING ); + $order = wc_get_order( $post_id ); + if ( is_a( $order, WC_Order::class ) && $order->get_payment_method() === OXXOGateway::ID ) { + $payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' ); + if ( $payer_action ) { + add_meta_box( + 'ppcp_oxxo_payer_action', + __( 'OXXO Voucher/Ticket', 'woocommerce-paypal-payments' ), + function() use ( $payer_action ) { + echo '

    See OXXO voucher

    '; + }, + $post_type, + 'side', + 'high' + ); + } + } + } + } + ); + + add_action( + 'woocommerce_order_details_before_order_table_items', + function( $order ) { + if ( $order->get_payment_method() === OXXOGateway::ID ) { + $payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' ); + if ( $payer_action ) { + echo '

    See OXXO voucher

    '; + } + } + } + ); } /** diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index 38883eb1c..b9502bbe8 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -488,7 +488,7 @@ class PayUponInvoice { if ( $post_type === 'shop_order' ) { $post_id = filter_input( INPUT_GET, 'post', FILTER_SANITIZE_STRING ); $order = wc_get_order( $post_id ); - if ( is_a( $order, WC_Order::class ) && $order->get_payment_method() === 'ppcp-pay-upon-invoice-gateway' ) { + if ( is_a( $order, WC_Order::class ) && $order->get_payment_method() === PayUponInvoiceGateway::ID ) { $instructions = $order->get_meta( 'ppcp_ratepay_payment_instructions_payment_reference' ); if ( $instructions ) { add_meta_box( From ec8ba1509276615ea0ca1fed8641189d7a8ca876 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 21 Jul 2022 11:15:39 +0300 Subject: [PATCH 54/86] Fix dcc gateway --- modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index 95ee8b8b7..4b97120bc 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -462,7 +462,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { $selected_token ); - $this->add_paypal_meta( $wc_order, $order, $this->environment() ); + $this->add_paypal_meta( $wc_order, $order, $this->environment ); if ( ! $order->status()->is( OrderStatus::COMPLETED ) ) { return $this->handle_payment_failure( From de842543cd0df5700a96760dfe7edab7a49b2bd6 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Thu, 21 Jul 2022 10:21:45 +0200 Subject: [PATCH 55/86] Fix psalm --- modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index 079a6e2d3..5d46435d2 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -152,7 +152,7 @@ class OXXO { add_action( 'woocommerce_order_details_before_order_table_items', - function( $order ) { + function( WC_Order $order ) { if ( $order->get_payment_method() === OXXOGateway::ID ) { $payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' ); if ( $payer_action ) { From b7199dbbbf8e34d0b9111b6645fce2048884f71b Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 21 Jul 2022 15:25:30 +0300 Subject: [PATCH 56/86] Fix phpcs warning --- modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php index 69203d885..d230bd4cb 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php @@ -128,7 +128,7 @@ class OXXOGateway extends WC_Payment_Gateway { * @return array */ public function process_payment( $order_id ) { - $wc_order = wc_get_order( $order_id ); + $wc_order = wc_get_order( $order_id ); $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); $payer_action = ''; From 7d7f1f55d540c1110d1231c8caba6423f13ac7ee Mon Sep 17 00:00:00 2001 From: dinamiko Date: Thu, 21 Jul 2022 15:30:30 +0200 Subject: [PATCH 57/86] Make string translatable and return json error when oxxo endpoint fail --- modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 6 +++--- modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php | 3 +++ modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php | 5 ++--- modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index 5d46435d2..a2f1dbc22 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -104,7 +104,7 @@ class OXXO { ) { $payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' ) ?? ''; if ( $payer_action ) { - echo '

    OXXO voucher

    '; + echo '

    ' . esc_html__( 'See OXXO voucher', 'woocommerce-paypal-payments' ) . '

    '; } } }, @@ -138,7 +138,7 @@ class OXXO { 'ppcp_oxxo_payer_action', __( 'OXXO Voucher/Ticket', 'woocommerce-paypal-payments' ), function() use ( $payer_action ) { - echo '

    See OXXO voucher

    '; + echo '

    ' . esc_html__( 'See OXXO voucher', 'woocommerce-paypal-payments' ) . '

    '; }, $post_type, 'side', @@ -156,7 +156,7 @@ class OXXO { if ( $order->get_payment_method() === OXXOGateway::ID ) { $payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' ); if ( $payer_action ) { - echo '

    See OXXO voucher

    '; + echo '

    ' . esc_html__( 'See OXXO voucher', 'woocommerce-paypal-payments' ) . '

    '; } } } diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php index 0d6e4fc3c..64d8e713f 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php @@ -140,6 +140,9 @@ class OXXOEndpoint implements EndpointInterface { $this->logger->error( $error ); wc_add_notice( $error, 'error' ); + + wp_send_json_error( 'Could not get OXXO payer action.' ); + return false; } WC()->session->set( 'ppcp_payer_action', $payer_action ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php index d230bd4cb..a04ea2d91 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php @@ -70,9 +70,8 @@ class OXXOGateway extends WC_Payment_Gateway { $this->method_title = __( 'OXXO', 'woocommerce-paypal-payments' ); $this->method_description = __( 'OXXO is a Mexican chain of convenience stores.', 'woocommerce-paypal-payments' ); - $gateway_settings = get_option( 'woocommerce_ppcp-oxxo-gateway_settings' ); - $this->title = $gateway_settings['title'] ?? $this->method_title; - $this->description = $gateway_settings['description'] ?? __( 'OXXO allows you to pay bills and online purchases in-store with cash.', 'woocommerce-paypal-payments' ); + $this->title = $this->get_option( 'title' ) ?? $this->method_title; + $this->description = $this->get_option( 'description' ) ?? __( 'OXXO allows you to pay bills and online purchases in-store with cash.', 'woocommerce-paypal-payments' ); $this->init_form_fields(); $this->init_settings(); diff --git a/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php b/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php index 1467e663f..d3b711f08 100644 --- a/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php +++ b/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php @@ -101,10 +101,10 @@ class PaymentCapturePending implements RequestHandler { $wc_order = wc_get_order( $order_id ); if ( ! is_a( $wc_order, \WC_Order::class ) ) { $message = sprintf( - // translators: %s is the PayPal refund Id. - __( 'Order for PayPal refund %s not found.', 'woocommerce-paypal-payments' ), + 'Order for PayPal refund %s not found.', $request['resource'] !== null && isset( $request['resource']['id'] ) ? $request['resource']['id'] : '' ); + $this->logger->log( 'warning', $message, From 0ceb3504b4261545dc686e34a9668de966663c38 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Thu, 21 Jul 2022 15:37:07 +0200 Subject: [PATCH 58/86] Fix typo and use log `warning` method --- .../src/Handler/PaymentCapturePending.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php b/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php index d3b711f08..225e70121 100644 --- a/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php +++ b/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php @@ -101,17 +101,12 @@ class PaymentCapturePending implements RequestHandler { $wc_order = wc_get_order( $order_id ); if ( ! is_a( $wc_order, \WC_Order::class ) ) { $message = sprintf( - 'Order for PayPal refund %s not found.', + 'WC order for PayPal ID %s not found.', $request['resource'] !== null && isset( $request['resource']['id'] ) ? $request['resource']['id'] : '' ); - $this->logger->log( - 'warning', - $message, - array( - 'request' => $request, - ) - ); + $this->logger->warning( $message ); + $response['message'] = $message; return new WP_REST_Response( $response ); } From c802d5dea82f05d7ee73295914577249e1003d73 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Thu, 21 Jul 2022 16:31:13 +0200 Subject: [PATCH 59/86] Make string translatable --- modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index a2f1dbc22..0ea0b2065 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -85,7 +85,7 @@ class OXXO { $button = ''; if ( $payer_action ) { - $button = '

    See OXXO Voucher/Ticket

    '; + $button = '

    ' . esc_html__( 'See OXXO voucher', 'woocommerce-paypal-payments' ) . '

    '; } return $message . ' ' . $button; From 5a80c45a8dd5d2e9ed0535d543f8d882a8ffb125 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 22 Jul 2022 10:23:12 +0200 Subject: [PATCH 60/86] Do not allow bith date older than 100 years --- modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php | 6 +++++- tests/PHPUnit/WcGateway/Helper/PayUponInvoiceHelperTest.php | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php index 32391bb0a..7789779d0 100644 --- a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php +++ b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php @@ -22,7 +22,7 @@ use WC_Product_Variation; class PayUponInvoiceHelper { /** - * Ensures date is valid and at least 18 years back. + * Ensures date is valid, at least 18 years back and not older than 100 years. * * @param string $date The date. * @param string $format The date format. @@ -43,6 +43,10 @@ class PayUponInvoiceHelper { return false; } + if ( $date_time < strtotime( '-100 years', time() ) ) { + return false; + } + return true; } diff --git a/tests/PHPUnit/WcGateway/Helper/PayUponInvoiceHelperTest.php b/tests/PHPUnit/WcGateway/Helper/PayUponInvoiceHelperTest.php index 3a1d9bdc0..f470d25ea 100644 --- a/tests/PHPUnit/WcGateway/Helper/PayUponInvoiceHelperTest.php +++ b/tests/PHPUnit/WcGateway/Helper/PayUponInvoiceHelperTest.php @@ -26,6 +26,7 @@ class PayUponInvoiceHelperTest extends TestCase ['1942-02-31', false], ['01-01-1942', false], ['1942-01-01', true], + ['0001-01-01', false], ]; } From 82126059268b1e2165fa8569f88c5eef8bc20c0b Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 22 Jul 2022 11:48:53 +0300 Subject: [PATCH 61/86] Make js validation error message like WC message --- modules/ppcp-button/resources/js/button.js | 30 +++++++++++++----- .../ppcp-button/src/Assets/SmartButton.php | 31 ++++++++++++------- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index 348fde7a5..11bdf15f7 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -40,20 +40,34 @@ const bootstrap = () => { }); const invalidFields = Array.from(jQuery('form.woocommerce-checkout .validate-required.woocommerce-invalid:visible')); if (invalidFields.length) { - const namesMap = PayPalCommerceGateway.labels.elements; - const labels = invalidFields.map(el => { + const billingFieldsContainer = document.querySelector('.woocommerce-billing-fields'); + const shippingFieldsContainer = document.querySelector('.woocommerce-shipping-fields'); + + const nameMessageMap = PayPalCommerceGateway.labels.error.required.elements; + const messages = invalidFields.map(el => { const name = el.querySelector('[name]')?.getAttribute('name'); - if (name && name in namesMap) { - return namesMap[name]; + if (name && name in nameMessageMap) { + return nameMessageMap[name]; } - return el.querySelector('label').textContent + let label = el.querySelector('label').textContent .replaceAll('*', '') .trim(); + if (billingFieldsContainer?.contains(el)) { + label = PayPalCommerceGateway.labels.billing_field.replace('%s', label); + } + if (shippingFieldsContainer?.contains(el)) { + label = PayPalCommerceGateway.labels.shipping_field.replace('%s', label); + } + return PayPalCommerceGateway.labels.error.required.field + .replace('%s', `${label}`) }).filter(s => s.length > 2); errorHandler.clear(); - errorHandler.message(PayPalCommerceGateway.labels.error.js_validation); - labels.forEach(s => errorHandler.message(s)); // each message() call adds
  • + if (messages.length) { + messages.forEach(s => errorHandler.message(s)); + } else { + errorHandler.message(PayPalCommerceGateway.labels.error.required.generic); + } return actions.reject(); } @@ -96,7 +110,7 @@ const bootstrap = () => { PayPalCommerceGateway, renderer, messageRenderer, - ); + );w singleProductBootstrap.init(); } diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index f5ce03dbd..e388c36e0 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -852,22 +852,31 @@ class SmartButton implements SmartButtonInterface { ), 'messages' => $this->message_values(), 'labels' => array( - 'error' => array( - 'generic' => __( + 'error' => array( + 'generic' => __( 'Something went wrong. Please try again or choose another payment source.', 'woocommerce-paypal-payments' ), - 'js_validation' => __( - 'Required form fields are not filled or invalid.', - 'woocommerce-paypal-payments' - ), - ), - 'elements' => array( // Map => text, used for error messages. - 'terms' => __( - 'Terms and conditions', - 'woocommerce-paypal-payments' + 'required' => array( + 'generic' => __( + 'Required form fields are not filled.', + 'woocommerce-paypal-payments' + ), + // phpcs:ignore WordPress.WP.I18n + 'field' => __( '%s is a required field.', 'woocommerce' ), + 'elements' => array( // Map => text for error messages. + 'terms' => __( + 'Please read and accept the terms and conditions to proceed with your order.', + // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch + 'woocommerce' + ), + ), ), ), + // phpcs:ignore WordPress.WP.I18n + 'billing_field' => _x( 'Billing %s', 'checkout-validation', 'woocommerce' ), + // phpcs:ignore WordPress.WP.I18n + 'shipping_field' => _x( 'Shipping %s', 'checkout-validation', 'woocommerce' ), ), 'order_id' => 'pay-now' === $this->context() ? absint( $wp->query_vars['order-pay'] ) : 0, 'single_product_buttons_enabled' => $this->settings->has( 'button_product_enabled' ) && $this->settings->get( 'button_product_enabled' ), From 55b2adf2aa2f449315cb8b27630fa0bc153f6e3e Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 25 Jul 2022 15:47:38 +0300 Subject: [PATCH 62/86] Show billing data mode in card button gateway settings --- modules/ppcp-wc-gateway/services.php | 3 ++- .../src/Gateway/CardButtonGateway.php | 25 ++++++++++++++++++- .../src/Settings/PageMatcherTrait.php | 2 ++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 5f95d3af8..30eeecf3f 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -129,6 +129,7 @@ return array( }, 'wcgateway.card-button-gateway' => static function ( ContainerInterface $container ): CardButtonGateway { return new CardButtonGateway( + $container->get( 'wcgateway.settings.render' ), $container->get( 'wcgateway.order-processor' ), $container->get( 'wcgateway.settings' ), $container->get( 'session.handler' ), @@ -887,7 +888,7 @@ return array( State::STATE_ONBOARDED, ), 'requirements' => array(), - 'gateway' => 'paypal', + 'gateway' => array( 'paypal', CardButtonGateway::ID ), ), // General button styles. diff --git a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php index daae61dd3..3241853e3 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php @@ -24,16 +24,24 @@ use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor; use Psr\Container\ContainerInterface; +use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer; /** * Class CardButtonGateway */ class CardButtonGateway extends \WC_Payment_Gateway { - use ProcessPaymentTrait, FreeTrialHandlerTrait; + use ProcessPaymentTrait, FreeTrialHandlerTrait, GatewaySettingsRendererTrait; const ID = 'ppcp-card-button-gateway'; + /** + * The Settings Renderer. + * + * @var SettingsRenderer + */ + protected $settings_renderer; + /** * The processor for orders. * @@ -114,6 +122,7 @@ class CardButtonGateway extends \WC_Payment_Gateway { /** * CardButtonGateway constructor. * + * @param SettingsRenderer $settings_renderer The Settings Renderer. * @param OrderProcessor $order_processor The Order Processor. * @param ContainerInterface $config The settings. * @param SessionHandler $session_handler The Session Handler. @@ -126,6 +135,7 @@ class CardButtonGateway extends \WC_Payment_Gateway { * @param LoggerInterface $logger The logger. */ public function __construct( + SettingsRenderer $settings_renderer, OrderProcessor $order_processor, ContainerInterface $config, SessionHandler $session_handler, @@ -138,6 +148,7 @@ class CardButtonGateway extends \WC_Payment_Gateway { LoggerInterface $logger ) { $this->id = self::ID; + $this->settings_renderer = $settings_renderer; $this->order_processor = $order_processor; $this->config = $config; $this->session_handler = $session_handler; @@ -228,6 +239,9 @@ class CardButtonGateway extends \WC_Payment_Gateway { 'desc_tip' => true, 'description' => __( 'This controls the description which the user sees during checkout.', 'woocommerce-paypal-payments' ), ), + 'ppcp' => array( + 'type' => 'ppcp', + ), ); } @@ -328,4 +342,13 @@ class CardButtonGateway extends \WC_Payment_Gateway { return parent::get_transaction_url( $order ); } + + /** + * Returns the settings renderer. + * + * @return SettingsRenderer + */ + protected function settings_renderer(): SettingsRenderer { + return $this->settings_renderer; + } } diff --git a/modules/ppcp-wc-gateway/src/Settings/PageMatcherTrait.php b/modules/ppcp-wc-gateway/src/Settings/PageMatcherTrait.php index f3b41fdfb..ea301362b 100644 --- a/modules/ppcp-wc-gateway/src/Settings/PageMatcherTrait.php +++ b/modules/ppcp-wc-gateway/src/Settings/PageMatcherTrait.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\WcGateway\Settings; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage; @@ -34,6 +35,7 @@ trait PageMatcherTrait { $gateway_page_id_map = array( PayPalGateway::ID => 'paypal', CreditCardGateway::ID => 'dcc', // TODO: consider using just the gateway ID for PayPal and DCC too. + CardButtonGateway::ID => CardButtonGateway::ID, WebhooksStatusPage::ID => WebhooksStatusPage::ID, ); return array_key_exists( $current_page_id, $gateway_page_id_map ) From 1ca704f2220873639e740e5144da2c8bea2f79a3 Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Mon, 25 Jul 2022 11:01:53 -0300 Subject: [PATCH 63/86] Update release date for 1.9.1 --- changelog.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/changelog.txt b/changelog.txt index 6879bff12..1dea43cea 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,14 +1,14 @@ *** Changelog *** -= 1.9.1 - TBD = += 1.9.1 - 2022-07-25 = * Fix - ITEM_TOTAL_MISMATCH error when checking out with multiple products #721 * Fix - Unable to purchase a product with Credit card button in pay for order page #718 * Fix - Pay Later messaging only displayed when smart button is active on the same page #283 * Fix - Pay Later messaging displayed for out of stock variable products or with no variation selected #667 * Fix - Placeholders and card type detection not working for PayPal Card Processing (260) #685 -* Fix - PUI gateway is displayed with unsupported store currency #711 +* Fix - PUI gateway is displayed with unsupported store currency #711 * Fix - Wrong PUI locale sent causing error PAYMENT_SOURCE_CANNOT_BE_USED #741 -* Enhancement - Missing PayPal fee in WC order details for PUI purchase #714 +* Enhancement - Missing PayPal fee in WC order details for PUI purchase #714 * Enhancement - Skip loading of PUI js file on all pages where PUI gateway is not displayed #723 * Enhancement - PUI feature capitalization not consistent #724 From ab69f40220c7fe8a3cde5f307b70f5b6db9db05f Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 26 Jul 2022 10:31:53 +0300 Subject: [PATCH 64/86] Make Enable checkbox text more consistent --- modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php index 3241853e3..efd3d6d9c 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php @@ -220,7 +220,7 @@ class CardButtonGateway extends \WC_Payment_Gateway { 'enabled' => array( 'title' => __( 'Enable/Disable', 'woocommerce-paypal-payments' ), 'type' => 'checkbox', - 'label' => __( 'PayPal Card Button', 'woocommerce-paypal-payments' ), + 'label' => __( 'Enable PayPal Card Button', 'woocommerce-paypal-payments' ), 'default' => 'no', 'desc_tip' => true, 'description' => __( 'Enable/Disable the separate payment gateway with the card button.', 'woocommerce-paypal-payments' ), From c2d0a3aa692de8b60ad76830016b42b5b7112d6e Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 26 Jul 2022 10:58:21 +0300 Subject: [PATCH 65/86] Add an option to hide card button gateway if not needed --- modules/ppcp-api-client/services.php | 54 ++++++ modules/ppcp-wc-gateway/services.php | 176 ++++++++---------- .../src/Gateway/CardButtonGateway.php | 12 +- .../ppcp-wc-gateway/src/WCGatewayModule.php | 4 +- 4 files changed, 145 insertions(+), 101 deletions(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index f34ac93da..ef1b40e3d 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -400,6 +400,60 @@ return array( ); }, + + 'api.shop.is-latin-america' => static function ( ContainerInterface $container ): bool { + return in_array( + $container->get( 'api.shop.country' ), + array( + 'AI', + 'AG', + 'AR', + 'AW', + 'BS', + 'BB', + 'BZ', + 'BM', + 'BO', + 'BR', + 'VG', + 'KY', + 'CL', + 'CO', + 'CR', + 'DM', + 'DO', + 'EC', + 'SV', + 'FK', + 'GF', + 'GD', + 'GP', + 'GT', + 'GY', + 'HN', + 'JM', + 'MQ', + 'MX', + 'MS', + 'AN', + 'NI', + 'PA', + 'PY', + 'PE', + 'KN', + 'LC', + 'PM', + 'VC', + 'SR', + 'TT', + 'TC', + 'UY', + 'VE', + ), + true + ); + }, + /** * Currencies supported by PayPal. * diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 30eeecf3f..9cf68f451 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -56,7 +56,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer; use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage; return array( - 'wcgateway.paypal-gateway' => static function ( ContainerInterface $container ): PayPalGateway { + 'wcgateway.paypal-gateway' => static function ( ContainerInterface $container ): PayPalGateway { $order_processor = $container->get( 'wcgateway.order-processor' ); $settings_renderer = $container->get( 'wcgateway.settings.render' ); $funding_source_renderer = $container->get( 'wcgateway.funding-source.renderer' ); @@ -88,7 +88,7 @@ return array( $api_shop_country ); }, - 'wcgateway.credit-card-gateway' => static function ( ContainerInterface $container ): CreditCardGateway { + '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' ); @@ -127,7 +127,7 @@ return array( $payments_endpoint ); }, - 'wcgateway.card-button-gateway' => static function ( ContainerInterface $container ): CardButtonGateway { + 'wcgateway.card-button-gateway' => static function ( ContainerInterface $container ): CardButtonGateway { return new CardButtonGateway( $container->get( 'wcgateway.settings.render' ), $container->get( 'wcgateway.order-processor' ), @@ -137,23 +137,24 @@ return array( $container->get( 'onboarding.state' ), $container->get( 'wcgateway.transaction-url-provider' ), $container->get( 'subscription.helper' ), + $container->get( 'wcgateway.settings.allow_card_button_gateway.default' ), $container->get( 'onboarding.environment' ), $container->get( 'vaulting.repository.payment-token' ), $container->get( 'woocommerce.logger.woocommerce' ) ); }, - 'wcgateway.disabler' => static function ( ContainerInterface $container ): DisableGateways { + 'wcgateway.disabler' => static function ( ContainerInterface $container ): DisableGateways { $session_handler = $container->get( 'session.handler' ); $settings = $container->get( 'wcgateway.settings' ); return new DisableGateways( $session_handler, $settings ); }, - 'wcgateway.is-wc-payments-page' => static function ( ContainerInterface $container ): bool { + 'wcgateway.is-wc-payments-page' => static function ( ContainerInterface $container ): bool { $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; $tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : ''; return 'wc-settings' === $page && 'checkout' === $tab; }, - 'wcgateway.is-ppcp-settings-page' => static function ( ContainerInterface $container ): bool { + 'wcgateway.is-ppcp-settings-page' => static function ( ContainerInterface $container ): bool { if ( ! $container->get( 'wcgateway.is-wc-payments-page' ) ) { return false; } @@ -162,7 +163,7 @@ return array( return in_array( $section, array( PayPalGateway::ID, CreditCardGateway::ID, WebhooksStatusPage::ID, PayUponInvoiceGateway::ID, CardButtonGateway::ID ), true ); }, - 'wcgateway.current-ppcp-settings-page-id' => static function ( ContainerInterface $container ): string { + 'wcgateway.current-ppcp-settings-page-id' => static function ( ContainerInterface $container ): string { if ( ! $container->get( 'wcgateway.is-ppcp-settings-page' ) ) { return ''; } @@ -173,36 +174,36 @@ return array( return $ppcp_tab ? $ppcp_tab : $section; }, - 'wcgateway.settings' => static function ( ContainerInterface $container ): Settings { + 'wcgateway.settings' => static function ( ContainerInterface $container ): Settings { return new Settings(); }, - 'wcgateway.notice.connect' => static function ( ContainerInterface $container ): ConnectAdminNotice { + 'wcgateway.notice.connect' => static function ( ContainerInterface $container ): ConnectAdminNotice { $state = $container->get( 'onboarding.state' ); $settings = $container->get( 'wcgateway.settings' ); return new ConnectAdminNotice( $state, $settings ); }, - 'wcgateway.notice.dcc-without-paypal' => static function ( ContainerInterface $container ): DccWithoutPayPalAdminNotice { + 'wcgateway.notice.dcc-without-paypal' => static function ( ContainerInterface $container ): DccWithoutPayPalAdminNotice { $state = $container->get( 'onboarding.state' ); $settings = $container->get( 'wcgateway.settings' ); $is_payments_page = $container->get( 'wcgateway.is-wc-payments-page' ); $is_ppcp_settings_page = $container->get( 'wcgateway.is-ppcp-settings-page' ); return new DccWithoutPayPalAdminNotice( $state, $settings, $is_payments_page, $is_ppcp_settings_page ); }, - 'wcgateway.notice.authorize-order-action' => + 'wcgateway.notice.authorize-order-action' => static function ( ContainerInterface $container ): AuthorizeOrderActionNotice { return new AuthorizeOrderActionNotice(); }, - 'wcgateway.settings.sections-renderer' => static function ( ContainerInterface $container ): SectionsRenderer { + 'wcgateway.settings.sections-renderer' => static function ( ContainerInterface $container ): SectionsRenderer { return new SectionsRenderer( $container->get( 'wcgateway.current-ppcp-settings-page-id' ), $container->get( 'api.shop.country' ) ); }, - 'wcgateway.settings.status' => static function ( ContainerInterface $container ): SettingsStatus { + 'wcgateway.settings.status' => static function ( ContainerInterface $container ): SettingsStatus { $settings = $container->get( 'wcgateway.settings' ); return new SettingsStatus( $settings ); }, - 'wcgateway.settings.render' => static function ( ContainerInterface $container ): SettingsRenderer { + 'wcgateway.settings.render' => static function ( ContainerInterface $container ): SettingsRenderer { $settings = $container->get( 'wcgateway.settings' ); $state = $container->get( 'onboarding.state' ); $fields = $container->get( 'wcgateway.settings.fields' ); @@ -222,7 +223,7 @@ return array( $page_id ); }, - 'wcgateway.settings.listener' => static function ( ContainerInterface $container ): SettingsListener { + 'wcgateway.settings.listener' => static function ( ContainerInterface $container ): SettingsListener { $settings = $container->get( 'wcgateway.settings' ); $fields = $container->get( 'wcgateway.settings.fields' ); $webhook_registrar = $container->get( 'webhook.registrar' ); @@ -244,7 +245,7 @@ return array( $signup_link_ids ); }, - 'wcgateway.order-processor' => static function ( ContainerInterface $container ): OrderProcessor { + 'wcgateway.order-processor' => static function ( ContainerInterface $container ): OrderProcessor { $session_handler = $container->get( 'session.handler' ); $order_endpoint = $container->get( 'api.endpoint.order' ); @@ -269,13 +270,13 @@ return array( $order_helper ); }, - 'wcgateway.processor.refunds' => static function ( ContainerInterface $container ): RefundProcessor { + 'wcgateway.processor.refunds' => static function ( ContainerInterface $container ): RefundProcessor { $order_endpoint = $container->get( 'api.endpoint.order' ); $payments_endpoint = $container->get( 'api.endpoint.payments' ); $logger = $container->get( 'woocommerce.logger.woocommerce' ); return new RefundProcessor( $order_endpoint, $payments_endpoint, $logger ); }, - 'wcgateway.processor.authorized-payments' => static function ( ContainerInterface $container ): AuthorizedPaymentsProcessor { + 'wcgateway.processor.authorized-payments' => static function ( ContainerInterface $container ): AuthorizedPaymentsProcessor { $order_endpoint = $container->get( 'api.endpoint.order' ); $payments_endpoint = $container->get( 'api.endpoint.payments' ); $logger = $container->get( 'woocommerce.logger.woocommerce' ); @@ -291,23 +292,23 @@ return array( $subscription_helper ); }, - 'wcgateway.admin.render-authorize-action' => static function ( ContainerInterface $container ): RenderAuthorizeAction { + 'wcgateway.admin.render-authorize-action' => static function ( ContainerInterface $container ): RenderAuthorizeAction { $column = $container->get( 'wcgateway.admin.orders-payment-status-column' ); return new RenderAuthorizeAction( $column ); }, - 'wcgateway.admin.order-payment-status' => static function ( ContainerInterface $container ): PaymentStatusOrderDetail { + 'wcgateway.admin.order-payment-status' => static function ( ContainerInterface $container ): PaymentStatusOrderDetail { $column = $container->get( 'wcgateway.admin.orders-payment-status-column' ); return new PaymentStatusOrderDetail( $column ); }, - 'wcgateway.admin.orders-payment-status-column' => static function ( ContainerInterface $container ): OrderTablePaymentStatusColumn { + 'wcgateway.admin.orders-payment-status-column' => static function ( ContainerInterface $container ): OrderTablePaymentStatusColumn { $settings = $container->get( 'wcgateway.settings' ); return new OrderTablePaymentStatusColumn( $settings ); }, - 'wcgateway.admin.fees-renderer' => static function ( ContainerInterface $container ): FeesRenderer { + 'wcgateway.admin.fees-renderer' => static function ( ContainerInterface $container ): FeesRenderer { return new FeesRenderer(); }, - 'wcgateway.settings.fields' => static function ( ContainerInterface $container ): array { + 'wcgateway.settings.fields' => static function ( ContainerInterface $container ): array { $state = $container->get( 'onboarding.state' ); assert( $state instanceof State ); @@ -890,6 +891,20 @@ return array( 'requirements' => array(), 'gateway' => array( 'paypal', CardButtonGateway::ID ), ), + 'allow_card_button_gateway' => array( + 'title' => __( 'Separate Card Button from PayPal gateway', 'woocommerce-paypal-payments' ), + 'type' => 'checkbox', + 'desc_tip' => true, + 'label' => __( 'Enable a separate payment gateway for the branded PayPal Debit or Credit Card button.', 'woocommerce-paypal-payments' ), + 'description' => __( 'By default, the Debit or Credit Card button is displayed in the PayPal Checkout payment gateway. This setting creates a second gateway for the Card button.', 'woocommerce-paypal-payments' ), + 'default' => $container->get( 'wcgateway.settings.allow_card_button_gateway.default' ), + 'screens' => array( + State::STATE_START, + State::STATE_ONBOARDED, + ), + 'requirements' => array(), + 'gateway' => 'paypal', + ), // General button styles. 'button_style_heading' => array( @@ -2103,7 +2118,7 @@ return array( return $fields; }, - 'wcgateway.all-funding-sources' => static function( ContainerInterface $container ): array { + 'wcgateway.all-funding-sources' => static function( ContainerInterface $container ): array { return array( 'card' => _x( 'Credit or debit cards', 'Name of payment method', 'woocommerce-paypal-payments' ), 'credit' => _x( 'Pay Later', 'Name of payment method', 'woocommerce-paypal-payments' ), @@ -2121,28 +2136,28 @@ return array( ); }, - 'wcgateway.checkout.address-preset' => static function( ContainerInterface $container ): CheckoutPayPalAddressPreset { + 'wcgateway.checkout.address-preset' => static function( ContainerInterface $container ): CheckoutPayPalAddressPreset { return new CheckoutPayPalAddressPreset( $container->get( 'session.handler' ) ); }, - 'wcgateway.url' => static function ( ContainerInterface $container ): string { + 'wcgateway.url' => static function ( ContainerInterface $container ): string { return plugins_url( $container->get( 'wcgateway.relative-path' ), dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' ); }, - 'wcgateway.relative-path' => static function( ContainerInterface $container ): string { + 'wcgateway.relative-path' => static function( ContainerInterface $container ): string { return 'modules/ppcp-wc-gateway/'; }, - 'wcgateway.absolute-path' => static function( ContainerInterface $container ): string { + 'wcgateway.absolute-path' => static function( ContainerInterface $container ): string { return plugin_dir_path( dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' ) . $container->get( 'wcgateway.relative-path' ); }, - 'wcgateway.endpoint.return-url' => static function ( ContainerInterface $container ) : ReturnUrlEndpoint { + 'wcgateway.endpoint.return-url' => static function ( ContainerInterface $container ) : ReturnUrlEndpoint { $gateway = $container->get( 'wcgateway.paypal-gateway' ); $endpoint = $container->get( 'api.endpoint.order' ); $prefix = $container->get( 'api.prefix' ); @@ -2153,40 +2168,40 @@ return array( ); }, - 'wcgateway.transaction-url-sandbox' => static function ( ContainerInterface $container ): string { + 'wcgateway.transaction-url-sandbox' => static function ( ContainerInterface $container ): string { return 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=%s'; }, - 'wcgateway.transaction-url-live' => static function ( ContainerInterface $container ): string { + 'wcgateway.transaction-url-live' => static function ( ContainerInterface $container ): string { return 'https://www.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=%s'; }, - 'wcgateway.transaction-url-provider' => static function ( ContainerInterface $container ): TransactionUrlProvider { + 'wcgateway.transaction-url-provider' => static function ( ContainerInterface $container ): TransactionUrlProvider { $sandbox_url_base = $container->get( 'wcgateway.transaction-url-sandbox' ); $live_url_base = $container->get( 'wcgateway.transaction-url-live' ); return new TransactionUrlProvider( $sandbox_url_base, $live_url_base ); }, - 'wcgateway.helper.dcc-product-status' => static function ( ContainerInterface $container ) : DCCProductStatus { + 'wcgateway.helper.dcc-product-status' => static function ( ContainerInterface $container ) : DCCProductStatus { $settings = $container->get( 'wcgateway.settings' ); $partner_endpoint = $container->get( 'api.endpoint.partners' ); return new DCCProductStatus( $settings, $partner_endpoint ); }, - 'button.helper.messages-disclaimers' => static function ( ContainerInterface $container ): MessagesDisclaimers { + 'button.helper.messages-disclaimers' => static function ( ContainerInterface $container ): MessagesDisclaimers { return new MessagesDisclaimers( $container->get( 'api.shop.country' ) ); }, - 'wcgateway.funding-source.renderer' => function ( ContainerInterface $container ) : FundingSourceRenderer { + 'wcgateway.funding-source.renderer' => function ( ContainerInterface $container ) : FundingSourceRenderer { return new FundingSourceRenderer( $container->get( 'wcgateway.settings' ) ); }, - 'wcgateway.pay-upon-invoice-order-endpoint' => static function ( ContainerInterface $container ): PayUponInvoiceOrderEndpoint { + 'wcgateway.pay-upon-invoice-order-endpoint' => static function ( ContainerInterface $container ): PayUponInvoiceOrderEndpoint { return new PayUponInvoiceOrderEndpoint( $container->get( 'api.host' ), $container->get( 'api.bearer' ), @@ -2195,10 +2210,10 @@ return array( $container->get( 'woocommerce.logger.woocommerce' ) ); }, - 'wcgateway.pay-upon-invoice-payment-source-factory' => static function ( ContainerInterface $container ): PaymentSourceFactory { + 'wcgateway.pay-upon-invoice-payment-source-factory' => static function ( ContainerInterface $container ): PaymentSourceFactory { return new PaymentSourceFactory(); }, - 'wcgateway.pay-upon-invoice-gateway' => static function ( ContainerInterface $container ): PayUponInvoiceGateway { + 'wcgateway.pay-upon-invoice-gateway' => static function ( ContainerInterface $container ): PayUponInvoiceGateway { return new PayUponInvoiceGateway( $container->get( 'wcgateway.pay-upon-invoice-order-endpoint' ), $container->get( 'api.factory.purchase-unit' ), @@ -2209,13 +2224,13 @@ return array( $container->get( 'wcgateway.pay-upon-invoice-helper' ) ); }, - 'wcgateway.pay-upon-invoice-fraudnet-session-id' => static function ( ContainerInterface $container ): FraudNetSessionId { + 'wcgateway.pay-upon-invoice-fraudnet-session-id' => static function ( ContainerInterface $container ): FraudNetSessionId { return new FraudNetSessionId(); }, 'wcgateway.pay-upon-invoice-fraudnet-source-website-id' => static function ( ContainerInterface $container ): FraudNetSourceWebsiteId { return new FraudNetSourceWebsiteId( $container->get( 'api.merchant_id' ) ); }, - 'wcgateway.pay-upon-invoice-fraudnet' => static function ( ContainerInterface $container ): FraudNet { + 'wcgateway.pay-upon-invoice-fraudnet' => static function ( ContainerInterface $container ): FraudNet { $session_id = $container->get( 'wcgateway.pay-upon-invoice-fraudnet-session-id' ); $source_website_id = $container->get( 'wcgateway.pay-upon-invoice-fraudnet-source-website-id' ); return new FraudNet( @@ -2223,16 +2238,16 @@ return array( (string) $source_website_id() ); }, - 'wcgateway.pay-upon-invoice-helper' => static function( ContainerInterface $container ): PayUponInvoiceHelper { + 'wcgateway.pay-upon-invoice-helper' => static function( ContainerInterface $container ): PayUponInvoiceHelper { return new PayUponInvoiceHelper(); }, - 'wcgateway.pay-upon-invoice-product-status' => static function( ContainerInterface $container ): PayUponInvoiceProductStatus { + 'wcgateway.pay-upon-invoice-product-status' => static function( ContainerInterface $container ): PayUponInvoiceProductStatus { return new PayUponInvoiceProductStatus( $container->get( 'wcgateway.settings' ), $container->get( 'api.endpoint.partners' ) ); }, - 'wcgateway.pay-upon-invoice' => static function ( ContainerInterface $container ): PayUponInvoice { + 'wcgateway.pay-upon-invoice' => static function ( ContainerInterface $container ): PayUponInvoice { return new PayUponInvoice( $container->get( 'wcgateway.url' ), $container->get( 'wcgateway.pay-upon-invoice-fraudnet' ), @@ -2249,7 +2264,7 @@ return array( $container->get( 'api.factory.capture' ) ); }, - 'wcgateway.logging.is-enabled' => function ( ContainerInterface $container ) : bool { + 'wcgateway.logging.is-enabled' => function ( ContainerInterface $container ) : bool { $settings = $container->get( 'wcgateway.settings' ); /** @@ -2261,7 +2276,7 @@ return array( ); }, - 'wcgateway.helper.vaulting-scope' => static function ( ContainerInterface $container ): bool { + 'wcgateway.helper.vaulting-scope' => static function ( ContainerInterface $container ): bool { try { $token = $container->get( 'api.bearer' )->bearer(); return $token->vaulting_available(); @@ -2270,7 +2285,7 @@ return array( } }, - 'button.helper.vaulting-label' => static function ( ContainerInterface $container ): string { + 'button.helper.vaulting-label' => static function ( ContainerInterface $container ): string { $vaulting_label = __( 'Enable saved cards and subscription features on your store.', 'woocommerce-paypal-payments' ); if ( ! $container->get( 'wcgateway.helper.vaulting-scope' ) ) { @@ -2292,7 +2307,7 @@ return array( return $vaulting_label; }, - 'wcgateway.settings.fields.pay-later-label' => static function ( ContainerInterface $container ): string { + 'wcgateway.settings.fields.pay-later-label' => static function ( ContainerInterface $container ): string { $pay_later_label = '%s'; $pay_later_label .= ''; $pay_later_label .= __( "You have PayPal vaulting enabled, that's why Pay Later Messaging options are unavailable now. You cannot use both features at the same time.", 'woocommerce-paypal-payments' ); @@ -2301,59 +2316,10 @@ return array( return $pay_later_label; }, - 'wcgateway.settings.card_billing_data_mode.default' => static function ( ContainerInterface $container ): string { - return in_array( - $container->get( 'api.shop.country' ), - array( - 'AI', - 'AG', - 'AR', - 'AW', - 'BS', - 'BB', - 'BZ', - 'BM', - 'BO', - 'BR', - 'VG', - 'KY', - 'CL', - 'CO', - 'CR', - 'DM', - 'DO', - 'EC', - 'SV', - 'FK', - 'GF', - 'GD', - 'GP', - 'GT', - 'GY', - 'HN', - 'JM', - 'MQ', - 'MX', - 'MS', - 'AN', - 'NI', - 'PA', - 'PY', - 'PE', - 'KN', - 'LC', - 'PM', - 'VC', - 'SR', - 'TT', - 'TC', - 'UY', - 'VE', - ), - true - ) ? CardBillingMode::MINIMAL_INPUT : CardBillingMode::USE_WC; + 'wcgateway.settings.card_billing_data_mode.default' => static function ( ContainerInterface $container ): string { + return $container->get( 'api.shop.is-latin-america' ) ? CardBillingMode::MINIMAL_INPUT : CardBillingMode::USE_WC; }, - 'wcgateway.settings.card_billing_data_mode' => static function ( ContainerInterface $container ): string { + 'wcgateway.settings.card_billing_data_mode' => static function ( ContainerInterface $container ): string { $settings = $container->get( 'wcgateway.settings' ); assert( $settings instanceof ContainerInterface ); @@ -2361,4 +2327,16 @@ return array( (string) $settings->get( 'card_billing_data_mode' ) : $container->get( 'wcgateway.settings.card_billing_data_mode.default' ); }, + + 'wcgateway.settings.allow_card_button_gateway.default' => static function ( ContainerInterface $container ): bool { + return $container->get( 'api.shop.is-latin-america' ); + }, + 'wcgateway.settings.allow_card_button_gateway' => static function ( ContainerInterface $container ): bool { + $settings = $container->get( 'wcgateway.settings' ); + assert( $settings instanceof ContainerInterface ); + + return $settings->has( 'allow_card_button_gateway' ) ? + (bool) $settings->get( 'allow_card_button_gateway' ) : + $container->get( 'wcgateway.settings.allow_card_button_gateway.default' ); + }, ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php index efd3d6d9c..45aff4ce6 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php @@ -105,6 +105,13 @@ class CardButtonGateway extends \WC_Payment_Gateway { */ private $onboarded; + /** + * Whether the gateway should be enabled by default. + * + * @var bool + */ + private $default_enabled; + /** * The environment. * @@ -130,6 +137,7 @@ class CardButtonGateway extends \WC_Payment_Gateway { * @param State $state The state. * @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order. * @param SubscriptionHelper $subscription_helper The subscription helper. + * @param bool $default_enabled Whether the gateway should be enabled by default. * @param Environment $environment The environment. * @param PaymentTokenRepository $payment_token_repository The payment token repository. * @param LoggerInterface $logger The logger. @@ -143,6 +151,7 @@ class CardButtonGateway extends \WC_Payment_Gateway { State $state, TransactionUrlProvider $transaction_url_provider, SubscriptionHelper $subscription_helper, + bool $default_enabled, Environment $environment, PaymentTokenRepository $payment_token_repository, LoggerInterface $logger @@ -156,6 +165,7 @@ class CardButtonGateway extends \WC_Payment_Gateway { $this->state = $state; $this->transaction_url_provider = $transaction_url_provider; $this->subscription_helper = $subscription_helper; + $this->default_enabled = $default_enabled; $this->environment = $environment; $this->onboarded = $state->current_state() === State::STATE_ONBOARDED; $this->payment_token_repository = $payment_token_repository; @@ -221,7 +231,7 @@ class CardButtonGateway extends \WC_Payment_Gateway { 'title' => __( 'Enable/Disable', 'woocommerce-paypal-payments' ), 'type' => 'checkbox', 'label' => __( 'Enable PayPal Card Button', 'woocommerce-paypal-payments' ), - 'default' => 'no', + 'default' => $this->default_enabled ? 'yes' : 'no', 'desc_tip' => true, 'description' => __( 'Enable/Disable the separate payment gateway with the card button.', 'woocommerce-paypal-payments' ), ), diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index a828e7684..f0aa2ffb5 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -284,7 +284,9 @@ class WCGatewayModule implements ModuleInterface { $methods[] = $container->get( 'wcgateway.credit-card-gateway' ); } - $methods[] = $container->get( 'wcgateway.card-button-gateway' ); + if ( $container->get( 'wcgateway.settings.allow_card_button_gateway' ) ) { + $methods[] = $container->get( 'wcgateway.card-button-gateway' ); + } if ( 'DE' === $container->get( 'api.shop.country' ) && 'EUR' === $container->get( 'api.shop.currency' ) ) { $methods[] = $container->get( 'wcgateway.pay-upon-invoice-gateway' ); From 9215ac24a4ba45b44052eeb3b7bb2c1bf4310aed Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 26 Jul 2022 15:28:15 +0300 Subject: [PATCH 66/86] Show notice if card button gateway without paypal gateway --- modules/ppcp-wc-gateway/services.php | 25 ++++++--- ...hp => GatewayWithoutPayPalAdminNotice.php} | 56 ++++++++++++++++--- .../ppcp-wc-gateway/src/WCGatewayModule.php | 16 ++++-- 3 files changed, 75 insertions(+), 22 deletions(-) rename modules/ppcp-wc-gateway/src/Notice/{DccWithoutPayPalAdminNotice.php => GatewayWithoutPayPalAdminNotice.php} (57%) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 9cf68f451..2906ba9d7 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -45,7 +45,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus; use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; use WooCommerce\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice; use WooCommerce\PayPalCommerce\WcGateway\Notice\ConnectAdminNotice; -use WooCommerce\PayPalCommerce\WcGateway\Notice\DccWithoutPayPalAdminNotice; +use WooCommerce\PayPalCommerce\WcGateway\Notice\GatewayWithoutPayPalAdminNotice; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor; @@ -182,12 +182,23 @@ return array( $settings = $container->get( 'wcgateway.settings' ); return new ConnectAdminNotice( $state, $settings ); }, - 'wcgateway.notice.dcc-without-paypal' => static function ( ContainerInterface $container ): DccWithoutPayPalAdminNotice { - $state = $container->get( 'onboarding.state' ); - $settings = $container->get( 'wcgateway.settings' ); - $is_payments_page = $container->get( 'wcgateway.is-wc-payments-page' ); - $is_ppcp_settings_page = $container->get( 'wcgateway.is-ppcp-settings-page' ); - return new DccWithoutPayPalAdminNotice( $state, $settings, $is_payments_page, $is_ppcp_settings_page ); + 'wcgateway.notice.dcc-without-paypal' => static function ( ContainerInterface $container ): GatewayWithoutPayPalAdminNotice { + return new GatewayWithoutPayPalAdminNotice( + CreditCardGateway::ID, + $container->get( 'onboarding.state' ), + $container->get( 'wcgateway.settings' ), + $container->get( 'wcgateway.is-wc-payments-page' ), + $container->get( 'wcgateway.is-ppcp-settings-page' ) + ); + }, + 'wcgateway.notice.card-button-without-paypal' => static function ( ContainerInterface $container ): GatewayWithoutPayPalAdminNotice { + return new GatewayWithoutPayPalAdminNotice( + CardButtonGateway::ID, + $container->get( 'onboarding.state' ), + $container->get( 'wcgateway.settings' ), + $container->get( 'wcgateway.is-wc-payments-page' ), + $container->get( 'wcgateway.is-ppcp-settings-page' ) + ); }, 'wcgateway.notice.authorize-order-action' => static function ( ContainerInterface $container ): AuthorizeOrderActionNotice { diff --git a/modules/ppcp-wc-gateway/src/Notice/DccWithoutPayPalAdminNotice.php b/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php similarity index 57% rename from modules/ppcp-wc-gateway/src/Notice/DccWithoutPayPalAdminNotice.php rename to modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php index 2343c9e00..171f50280 100644 --- a/modules/ppcp-wc-gateway/src/Notice/DccWithoutPayPalAdminNotice.php +++ b/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php @@ -1,6 +1,6 @@ id = $id; $this->state = $state; $this->settings = $settings; $this->is_payments_page = $is_payments_page; @@ -76,12 +86,20 @@ class DccWithoutPayPalAdminNotice { return null; } + $gateway = $this->get_gateway(); + if ( ! $gateway ) { + return null; + } + + $name = $gateway->get_method_title(); + $message = sprintf( - /* translators: %1$s the gateway name. */ + /* translators: %1$s the gateway name, %2$s URL. */ __( - 'PayPal Card Processing cannot be used without the PayPal gateway. Enable the PayPal Gateway.', + '%1$s cannot be used without the PayPal gateway. Enable the PayPal gateway.', 'woocommerce-paypal-payments' ), + $name, admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway' ) ); return new Message( $message, 'warning' ); @@ -93,9 +111,29 @@ class DccWithoutPayPalAdminNotice { * @return bool */ protected function should_display(): bool { - return State::STATE_ONBOARDED === $this->state->current_state() - && ( $this->is_payments_page || $this->is_ppcp_settings_page ) - && ( $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) ) - && ( ! $this->settings->has( 'enabled' ) || ! $this->settings->get( 'enabled' ) ); + if ( State::STATE_ONBOARDED !== $this->state->current_state() || + ( ! $this->is_payments_page && ! $this->is_ppcp_settings_page ) ) { + return false; + } + if ( $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ) ) { + return false; + } + + $gateway = $this->get_gateway(); + + return $gateway && wc_string_to_bool( $gateway->get_option( 'enabled' ) ); + } + + /** + * Returns the gateway object or null. + * + * @return WC_Payment_Gateway|null + */ + protected function get_gateway(): ?WC_Payment_Gateway { + $gateways = WC()->payment_gateways->payment_gateways(); + if ( ! isset( $gateways[ $this->id ] ) ) { + return null; + } + return $gateways[ $this->id ]; } } diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index f0aa2ffb5..a4628b035 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -29,7 +29,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Notice\ConnectAdminNotice; -use WooCommerce\PayPalCommerce\WcGateway\Notice\DccWithoutPayPalAdminNotice; +use WooCommerce\PayPalCommerce\WcGateway\Notice\GatewayWithoutPayPalAdminNotice; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; use WooCommerce\PayPalCommerce\WcGateway\Settings\SectionsRenderer; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -164,11 +164,15 @@ class WCGatewayModule implements ModuleInterface { $notices[] = $connect_message; } - $dcc_without_paypal_notice = $c->get( 'wcgateway.notice.dcc-without-paypal' ); - assert( $dcc_without_paypal_notice instanceof DccWithoutPayPalAdminNotice ); - $dcc_without_paypal_message = $dcc_without_paypal_notice->message(); - if ( $dcc_without_paypal_message ) { - $notices[] = $dcc_without_paypal_message; + foreach ( array( + $c->get( 'wcgateway.notice.dcc-without-paypal' ), + $c->get( 'wcgateway.notice.card-button-without-paypal' ), + ) as $gateway_without_paypal_notice ) { + assert( $gateway_without_paypal_notice instanceof GatewayWithoutPayPalAdminNotice ); + $message = $gateway_without_paypal_notice->message(); + if ( $message ) { + $notices[] = $message; + } } $authorize_order_action = $c->get( 'wcgateway.notice.authorize-order-action' ); From f88c182737a4802b679e765a04c7e43afae09b85 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 26 Jul 2022 17:00:32 +0300 Subject: [PATCH 67/86] Fix oxxo default title/description --- modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php index a04ea2d91..f514db1f5 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php @@ -70,8 +70,8 @@ class OXXOGateway extends WC_Payment_Gateway { $this->method_title = __( 'OXXO', 'woocommerce-paypal-payments' ); $this->method_description = __( 'OXXO is a Mexican chain of convenience stores.', 'woocommerce-paypal-payments' ); - $this->title = $this->get_option( 'title' ) ?? $this->method_title; - $this->description = $this->get_option( 'description' ) ?? __( 'OXXO allows you to pay bills and online purchases in-store with cash.', 'woocommerce-paypal-payments' ); + $this->title = $this->get_option( 'title', $this->method_title ); + $this->description = $this->get_option( 'description', __( 'OXXO allows you to pay bills and online purchases in-store with cash.', 'woocommerce-paypal-payments' ) ); $this->init_form_fields(); $this->init_settings(); From 07e1fec6fb51acecc13fc5cc16837d9be1dbab85 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 26 Jul 2022 17:05:03 +0300 Subject: [PATCH 68/86] Show OXXO settings tab --- modules/ppcp-wc-gateway/services.php | 2 +- modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 0e2ba2fd8..c589c068d 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -164,7 +164,7 @@ return array( } $section = isset( $_GET['section'] ) ? sanitize_text_field( wp_unslash( $_GET['section'] ) ) : ''; - return in_array( $section, array( PayPalGateway::ID, CreditCardGateway::ID, WebhooksStatusPage::ID, PayUponInvoiceGateway::ID, CardButtonGateway::ID ), true ); + return in_array( $section, array( PayPalGateway::ID, CreditCardGateway::ID, WebhooksStatusPage::ID, PayUponInvoiceGateway::ID, CardButtonGateway::ID, OXXOGateway::ID ), true ); }, 'wcgateway.current-ppcp-settings-page-id' => static function ( ContainerInterface $container ): string { diff --git a/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php b/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php index 631a078c7..2fd880e01 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php +++ b/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php @@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Settings; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway; use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage; @@ -68,6 +69,7 @@ class SectionsRenderer { PayPalGateway::ID => __( 'PayPal Checkout', 'woocommerce-paypal-payments' ), CreditCardGateway::ID => __( 'PayPal Card Processing', 'woocommerce-paypal-payments' ), CardButtonGateway::ID => __( 'PayPal Card Button', 'woocommerce-paypal-payments' ), + OXXOGateway::ID => __( 'OXXO', 'woocommerce-paypal-payments' ), PayUponInvoiceGateway::ID => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ), WebhooksStatusPage::ID => __( 'Webhooks Status', 'woocommerce-paypal-payments' ), ); @@ -82,7 +84,7 @@ class SectionsRenderer { foreach ( $sections as $id => $label ) { $url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway&' . self::KEY . '=' . $id ); - if ( in_array( $id, array( PayUponInvoiceGateway::ID, CardButtonGateway::ID ), true ) ) { + if ( in_array( $id, array( PayUponInvoiceGateway::ID, CardButtonGateway::ID, OXXOGateway::ID ), true ) ) { $url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=' . $id ); } echo '
  • ' . esc_html( $label ) . ' ' . ( end( $array_keys ) === $id ? '' : '|' ) . '
  • '; From 65caa42872c3e06ae56e7b1d3166bf7ad8d9124e Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Tue, 26 Jul 2022 19:11:07 +0400 Subject: [PATCH 69/86] Fix PHPCS problems --- .../ppcp-button/src/Assets/SmartButton.php | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index eb80fdada..ae3b319f0 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -434,7 +434,7 @@ class SmartButton implements SmartButtonInterface { return; } - $this->button_renderer( PayPalGateway::ID ); + $this->button_renderer( PayPalGateway::ID ); }, 31 ); @@ -469,20 +469,20 @@ class SmartButton implements SmartButtonInterface { $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); if ( isset( $available_gateways['ppcp-gateway'] ) ) { - add_action( - $this->pay_order_renderer_hook(), - function (): void { - $this->button_renderer(PayPalGateway::ID); - $this->button_renderer(CardButtonGateway::ID); - } - ); - add_action( - $this->checkout_button_renderer_hook(), - function (): void { - $this->button_renderer(PayPalGateway::ID); - $this->button_renderer(CardButtonGateway::ID); - } - ); + add_action( + $this->pay_order_renderer_hook(), + function (): void { + $this->button_renderer( PayPalGateway::ID ); + $this->button_renderer( CardButtonGateway::ID ); + } + ); + add_action( + $this->checkout_button_renderer_hook(), + function (): void { + $this->button_renderer( PayPalGateway::ID ); + $this->button_renderer( CardButtonGateway::ID ); + } + ); $not_enabled_on_cart = $this->settings->has( 'button_cart_enabled' ) && ! $this->settings->get( 'button_cart_enabled' ); @@ -493,11 +493,11 @@ class SmartButton implements SmartButtonInterface { return; } - $this->button_renderer(PayPalGateway::ID); - }, - 20 - ); - } + $this->button_renderer( PayPalGateway::ID ); + }, + 20 + ); + } return true; } From 0a9c7bfaf9ac11ba4f455c5f52b732774c86701d Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 27 Jul 2022 16:00:08 +0300 Subject: [PATCH 70/86] Add oxxo icon --- modules/ppcp-wc-gateway/assets/images/oxxo.svg | 18 ++++++++++++++++++ modules/ppcp-wc-gateway/services.php | 1 + .../src/Gateway/OXXO/OXXOGateway.php | 12 ++++++++++++ .../WcGateway/Gateway/OXXO/OXXOGatewayTest.php | 1 + 4 files changed, 32 insertions(+) create mode 100644 modules/ppcp-wc-gateway/assets/images/oxxo.svg diff --git a/modules/ppcp-wc-gateway/assets/images/oxxo.svg b/modules/ppcp-wc-gateway/assets/images/oxxo.svg new file mode 100644 index 000000000..c67d3c53d --- /dev/null +++ b/modules/ppcp-wc-gateway/assets/images/oxxo.svg @@ -0,0 +1,18 @@ + + + + logo OXXO + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index c589c068d..b356090b6 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -2298,6 +2298,7 @@ return array( $container->get( 'api.endpoint.order' ), $container->get( 'api.factory.purchase-unit' ), $container->get( 'api.factory.shipping-preference' ), + $container->get( 'wcgateway.url' ), $container->get( 'woocommerce.logger.woocommerce' ) ); }, diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php index f514db1f5..1711b8664 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOGateway.php @@ -44,6 +44,13 @@ class OXXOGateway extends WC_Payment_Gateway { */ protected $shipping_preference_factory; + /** + * The URL to the module. + * + * @var string + */ + private $module_url; + /** * The logger. * @@ -57,12 +64,14 @@ class OXXOGateway extends WC_Payment_Gateway { * @param OrderEndpoint $order_endpoint The order endpoint. * @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory. * @param ShippingPreferenceFactory $shipping_preference_factory The shipping preference factory. + * @param string $module_url The URL to the module. * @param LoggerInterface $logger The logger. */ public function __construct( OrderEndpoint $order_endpoint, PurchaseUnitFactory $purchase_unit_factory, ShippingPreferenceFactory $shipping_preference_factory, + string $module_url, LoggerInterface $logger ) { $this->id = self::ID; @@ -87,7 +96,10 @@ class OXXOGateway extends WC_Payment_Gateway { $this->order_endpoint = $order_endpoint; $this->purchase_unit_factory = $purchase_unit_factory; $this->shipping_preference_factory = $shipping_preference_factory; + $this->module_url = $module_url; $this->logger = $logger; + + $this->icon = esc_url( $this->module_url ) . 'assets/images/oxxo.svg'; } /** diff --git a/tests/PHPUnit/WcGateway/Gateway/OXXO/OXXOGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/OXXO/OXXOGatewayTest.php index 849353b86..5f55ff8e8 100644 --- a/tests/PHPUnit/WcGateway/Gateway/OXXO/OXXOGatewayTest.php +++ b/tests/PHPUnit/WcGateway/Gateway/OXXO/OXXOGatewayTest.php @@ -44,6 +44,7 @@ private $testee; $this->orderEndpoint, $this->purchaseUnitFactory, $this->shippingPreferenceFactory, + 'oxxo.svg', $this->logger ); } From 615e96c409e704bdeebf00e98ec4df0611c17bdb Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 27 Jul 2022 16:26:27 +0300 Subject: [PATCH 71/86] Do not remove card funding source for free trials if card button enabled --- modules/ppcp-button/src/Assets/SmartButton.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index ab6a30ffd..ecf7fca20 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -984,7 +984,7 @@ class SmartButton implements SmartButtonInterface { if ( $this->is_free_trial_cart() ) { $all_sources = array_keys( $this->all_funding_sources ); - if ( $is_dcc_enabled ) { + if ( $is_dcc_enabled || $is_separate_card_enabled ) { $all_sources = array_diff( $all_sources, array( 'card' ) ); } $disable_funding = $all_sources; From 63c207354954c397c313a170f4b70541dd9a744d Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 28 Jul 2022 10:07:55 +0300 Subject: [PATCH 72/86] Refactor sections renderer, hide unavailable sections --- modules/ppcp-wc-gateway/services.php | 25 +++++++++- .../src/Settings/SectionsRenderer.php | 46 +++++++------------ 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index c589c068d..d280c493b 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -211,9 +211,32 @@ return array( 'wcgateway.settings.sections-renderer' => static function ( ContainerInterface $container ): SectionsRenderer { return new SectionsRenderer( $container->get( 'wcgateway.current-ppcp-settings-page-id' ), - $container->get( 'api.shop.country' ) + $container->get( 'wcgateway.settings.sections' ) ); }, + 'wcgateway.settings.sections' => static function ( ContainerInterface $container ): array { + $sections = array( + PayPalGateway::ID => __( 'PayPal Checkout', 'woocommerce-paypal-payments' ), + CreditCardGateway::ID => __( 'PayPal Card Processing', 'woocommerce-paypal-payments' ), + CardButtonGateway::ID => __( 'PayPal Card Button', 'woocommerce-paypal-payments' ), + OXXOGateway::ID => __( 'OXXO', 'woocommerce-paypal-payments' ), + PayUponInvoiceGateway::ID => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ), + WebhooksStatusPage::ID => __( 'Webhooks Status', 'woocommerce-paypal-payments' ), + ); + + // Remove for all not registered in WC gateways that cannot render anything in this case. + $gateways = WC()->payment_gateways->payment_gateways(); + foreach ( array_diff( + array_keys( $sections ), + array( PayPalGateway::ID, CreditCardGateway::ID, WebhooksStatusPage::ID ) + ) as $id ) { + if ( ! isset( $gateways[ $id ] ) ) { + unset( $sections[ $id ] ); + } + } + + return $sections; + }, 'wcgateway.settings.status' => static function ( ContainerInterface $container ): SettingsStatus { $settings = $container->get( 'wcgateway.settings' ); return new SettingsStatus( $settings ); diff --git a/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php b/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php index 2fd880e01..275574e97 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php +++ b/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php @@ -9,11 +9,7 @@ declare( strict_types=1 ); namespace WooCommerce\PayPalCommerce\WcGateway\Settings; -use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; -use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway; -use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; -use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway; use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage; /** @@ -31,21 +27,21 @@ class SectionsRenderer { protected $page_id; /** - * The api shop country. + * Key - page/gateway ID, value - displayed text. * - * @var string + * @var array */ - protected $api_shop_country; + protected $sections; /** * SectionsRenderer constructor. * - * @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page. - * @param string $api_shop_country The api shop country. + * @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page. + * @param array $sections Key - page/gateway ID, value - displayed text. */ - public function __construct( string $page_id, string $api_shop_country ) { - $this->page_id = $page_id; - $this->api_shop_country = $api_shop_country; + public function __construct( string $page_id, array $sections ) { + $this->page_id = $page_id; + $this->sections = $sections; } /** @@ -65,27 +61,17 @@ class SectionsRenderer { return; } - $sections = array( - PayPalGateway::ID => __( 'PayPal Checkout', 'woocommerce-paypal-payments' ), - CreditCardGateway::ID => __( 'PayPal Card Processing', 'woocommerce-paypal-payments' ), - CardButtonGateway::ID => __( 'PayPal Card Button', 'woocommerce-paypal-payments' ), - OXXOGateway::ID => __( 'OXXO', 'woocommerce-paypal-payments' ), - PayUponInvoiceGateway::ID => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ), - WebhooksStatusPage::ID => __( 'Webhooks Status', 'woocommerce-paypal-payments' ), - ); - - if ( 'DE' !== $this->api_shop_country ) { - unset( $sections[ PayUponInvoiceGateway::ID ] ); - } - echo '
      '; - $array_keys = array_keys( $sections ); + $array_keys = array_keys( $this->sections ); - foreach ( $sections as $id => $label ) { - $url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway&' . self::KEY . '=' . $id ); - if ( in_array( $id, array( PayUponInvoiceGateway::ID, CardButtonGateway::ID, OXXOGateway::ID ), true ) ) { - $url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=' . $id ); + foreach ( $this->sections as $id => $label ) { + $url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=' . $id ); + if ( in_array( $id, array( CreditCardGateway::ID, WebhooksStatusPage::ID ), true ) ) { + // We need section=ppcp-gateway for the webhooks page because it is not a gateway, + // and for DCC because otherwise it will not render the page if gateway is not available (country/currency). + // Other gateways render fields differently, and their pages are not expected to work when gateway is not available. + $url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway&' . self::KEY . '=' . $id ); } echo '
    • ' . esc_html( $label ) . ' ' . ( end( $array_keys ) === $id ? '' : '|' ) . '
    • '; } From de218667126d7f7d8b08630d23441b6ff9d474fb Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 28 Jul 2022 15:05:35 +0400 Subject: [PATCH 73/86] Fix the script loading check. --- modules/ppcp-button/src/Assets/SmartButton.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index ab6a30ffd..e6dacecbe 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1092,9 +1092,9 @@ class SmartButton implements SmartButtonInterface { } if ( $this->context() === 'product' - && $this->settings->has( 'button_product_enabled' ) + && ( $this->settings->has( 'button_product_enabled' ) && $this->settings->get( 'button_product_enabled' ) - || $this->settings->has( 'message_product_enabled' ) + || $this->settings->has( 'message_product_enabled' ) ) ) { $load_buttons = true; } @@ -1106,9 +1106,9 @@ class SmartButton implements SmartButtonInterface { } if ( $this->context() === 'cart' - && $this->settings->has( 'button_cart_enabled' ) + && ( $this->settings->has( 'button_cart_enabled' ) && $this->settings->get( 'button_cart_enabled' ) - || $this->settings->has( 'message_product_enabled' ) + || $this->settings->has( 'message_product_enabled' ) ) ) { $load_buttons = true; } From a1454dbfd7e60928b6e1926c94c7f269b621f383 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 28 Jul 2022 15:51:00 +0400 Subject: [PATCH 74/86] Fix the script loading check. --- modules/ppcp-button/src/Assets/SmartButton.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index e6dacecbe..1a03e982e 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1092,9 +1092,10 @@ class SmartButton implements SmartButtonInterface { } if ( $this->context() === 'product' - && ( $this->settings->has( 'button_product_enabled' ) - && $this->settings->get( 'button_product_enabled' ) - || $this->settings->has( 'message_product_enabled' ) ) + && ( + ( $this->settings->has( 'button_product_enabled' ) && $this->settings->get( 'button_product_enabled' ) ) || + ( $this->settings->has( 'message_product_enabled' ) && $this->settings->get( 'message_product_enabled' ) ) + ) ) { $load_buttons = true; } @@ -1104,14 +1105,17 @@ class SmartButton implements SmartButtonInterface { ) { $load_buttons = true; } + if ( $this->context() === 'cart' - && ( $this->settings->has( 'button_cart_enabled' ) - && $this->settings->get( 'button_cart_enabled' ) - || $this->settings->has( 'message_product_enabled' ) ) + && ( + ( $this->settings->has( 'button_cart_enabled' ) && $this->settings->get( 'button_cart_enabled' ) ) || + ( $this->settings->has( 'message_cart_enabled' ) && $this->settings->get( 'message_cart_enabled' ) ) + ) ) { $load_buttons = true; } + if ( $this->context() === 'pay-now' ) { $load_buttons = true; } From 6b8014e5b1600a834809b6e2dca3ce1b5f035c98 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 28 Jul 2022 16:33:24 +0400 Subject: [PATCH 75/86] Fix the JS file --- modules/ppcp-button/resources/js/button.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index 4ec22c7d7..03a38a3f7 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -112,7 +112,7 @@ const bootstrap = () => { PayPalCommerceGateway, renderer, messageRenderer, - );w + ); singleProductBootstrap.init(); } From 706ff246da4842342de735569489515160deddee Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 29 Jul 2022 15:51:05 +0300 Subject: [PATCH 76/86] Add GH Action for building package --- .editorconfig | 3 +++ .github/workflows/package.yml | 41 +++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 .github/workflows/package.yml diff --git a/.editorconfig b/.editorconfig index 922986d27..9920ff350 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,3 +14,6 @@ trim_trailing_whitespace = false [*.{js,json,yml}] indent_style = space + +[*.yml] +indent_size = 2 diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml new file mode 100644 index 000000000..6d4d11f21 --- /dev/null +++ b/.github/workflows/package.yml @@ -0,0 +1,41 @@ +name: Build package + +on: + workflow_dispatch: + inputs: + packageVersion: + description: 'Package Version' + required: false + type: string + +jobs: + package: + runs-on: ubuntu-latest + + name: Build package + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 7.1 + tools: composer:v1 + + - name: Set plugin version header + env: + PACKAGE_VERSION: ${{ github.event.inputs.packageVersion }} + run: 'sed -Ei "s/Version: .*/Version: ${PACKAGE_VERSION}/g" woocommerce-paypal-payments.php' + if: github.event.inputs.packageVersion + + - name: Build + run: yarn build + + - name: Unzip # GH currently always zips, so if we upload a zip we get a zip inside a zip + run: unzip woocommerce-paypal-payments.zip -d dist + + - name: Upload + uses: actions/upload-artifact@v3 + with: + name: woocommerce-paypal-payments + path: dist/ From 4f0771156ab7250bafab6c1ffa59162c709fe9fb Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 1 Aug 2022 14:11:18 +0200 Subject: [PATCH 77/86] Do not disable gateways when updating order review --- modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php index 33fbcef6e..1c940557a 100644 --- a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php +++ b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php @@ -110,6 +110,11 @@ class DisableGateways { * @return bool */ private function needs_to_disable_gateways(): bool { + $wc_ajax = filter_input( INPUT_GET, 'wc-ajax', FILTER_SANITIZE_STRING ) ?? ''; + if ( $wc_ajax === 'update_order_review' ) { + return false; + } + return $this->session_handler->order() !== null; } From 0aa16b4f4ab53da9252c244834f5ae08376ef51f Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 1 Aug 2022 10:38:02 +0300 Subject: [PATCH 78/86] Handle PAYER_ACTION_REQUIRED --- .../src/Gateway/PayPalGateway.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index 5cc391370..1adbbbaf6 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -458,10 +458,22 @@ class PayPalGateway extends \WC_Payment_Gateway { return $this->handle_payment_success( $wc_order ); } catch ( PayPalApiException $error ) { - if ( $error->has_detail( 'INSTRUMENT_DECLINED' ) ) { + $retry_keys_messages = array( + 'INSTRUMENT_DECLINED' => __( 'Instrument declined.', 'woocommerce-paypal-payments' ), + 'PAYER_ACTION_REQUIRED' => __( 'Payer action required, possibly overcharge.', 'woocommerce-paypal-payments' ), + ); + $retry_errors = array_filter( + array_keys( $retry_keys_messages ), + function ( string $key ) use ( $error ): bool { + return $error->has_detail( $key ); + } + ); + if ( $retry_errors ) { + $retry_error_key = $retry_errors[0]; + $wc_order->update_status( 'failed', - __( 'Instrument declined. ', 'woocommerce-paypal-payments' ) . $error->details()[0]->description ?? '' + $retry_keys_messages[ $retry_error_key ] . ' ' . $error->details()[0]->description ?? '' ); $this->session_handler->increment_insufficient_funding_tries(); From 11c96608f172155f5fe4e13e745314e4a414070d Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 1 Aug 2022 16:05:28 +0200 Subject: [PATCH 79/86] Fix merge conflict --- modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php index 8ecfce40c..dffecca37 100644 --- a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php +++ b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php @@ -106,6 +106,11 @@ class DisableGateways { * @return bool */ private function needs_to_disable_gateways(): bool { + $wc_ajax = filter_input( INPUT_GET, 'wc-ajax', FILTER_SANITIZE_STRING ) ?? ''; + if ( $wc_ajax === 'update_order_review' ) { + return false; + } + $order = $this->session_handler->order(); if ( ! $order ) { return false; From d5e2d8585c3db80917f803dd69815cd6c551a519 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 2 Aug 2022 12:11:18 +0200 Subject: [PATCH 80/86] Disable oxxo feature via constant --- modules/ppcp-wc-gateway/src/WCGatewayModule.php | 12 +++++++++++- woocommerce-paypal-payments.php | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index cb69b3fb7..356a07ca8 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -232,6 +232,10 @@ class WCGatewayModule implements ModuleInterface { add_action( 'init', function () use ( $c ) { + if ( defined( 'PPCP_FLAG_OXXO' ) && PPCP_FLAG_OXXO === false ) { + return; + } + if ( 'DE' === $c->get( 'api.shop.country' ) && 'EUR' === $c->get( 'api.shop.currency' ) ) { ( $c->get( 'wcgateway.pay-upon-invoice' ) )->init(); } @@ -270,6 +274,10 @@ class WCGatewayModule implements ModuleInterface { add_action( 'wc_ajax_ppc-oxxo', static function () use ( $c ) { + if ( defined( 'PPCP_FLAG_OXXO' ) && PPCP_FLAG_OXXO === false ) { + return; + } + $endpoint = $c->get( 'wcgateway.endpoint.oxxo' ); $endpoint->handle_request(); } @@ -306,7 +314,9 @@ class WCGatewayModule implements ModuleInterface { $methods[] = $container->get( 'wcgateway.pay-upon-invoice-gateway' ); } - $methods[] = $container->get( 'wcgateway.oxxo-gateway' ); + if ( defined( 'PPCP_FLAG_OXXO' ) && PPCP_FLAG_OXXO === true ) { + $methods[] = $container->get( 'wcgateway.oxxo-gateway' ); + } return (array) $methods; } diff --git a/woocommerce-paypal-payments.php b/woocommerce-paypal-payments.php index 38817fd03..e0cc1b643 100644 --- a/woocommerce-paypal-payments.php +++ b/woocommerce-paypal-payments.php @@ -24,6 +24,8 @@ define( 'PAYPAL_SANDBOX_API_URL', 'https://api.sandbox.paypal.com' ); define( 'PAYPAL_INTEGRATION_DATE', '2022-04-13' ); define( 'PPCP_FLAG_SUBSCRIPTION', true ); +define( 'PPCP_FLAG_OXXO', apply_filters( 'woocommerce_paypal_payments_enable_oxxo', false ) ); +define( 'PPCP_FLAG_SEPARATE_APM_BUTTONS', apply_filters( 'woocommerce_paypal_payments_enable_separate_apm_buttons', false ) ); ! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' ); ! defined( 'CONNECT_WOO_SANDBOX_CLIENT_ID' ) && define( 'CONNECT_WOO_SANDBOX_CLIENT_ID', 'AYmOHbt1VHg-OZ_oihPdzKEVbU3qg0qXonBcAztuzniQRaKE0w1Hr762cSFwd4n8wxOl-TCWohEa0XM_' ); From e14fc1014157b56e21d193f084543b9933b48b19 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 2 Aug 2022 12:24:22 +0200 Subject: [PATCH 81/86] Fix wrong disabling oxxo --- modules/ppcp-wc-gateway/src/WCGatewayModule.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 356a07ca8..d01287f67 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -232,15 +232,13 @@ class WCGatewayModule implements ModuleInterface { add_action( 'init', function () use ( $c ) { - if ( defined( 'PPCP_FLAG_OXXO' ) && PPCP_FLAG_OXXO === false ) { - return; - } - if ( 'DE' === $c->get( 'api.shop.country' ) && 'EUR' === $c->get( 'api.shop.currency' ) ) { ( $c->get( 'wcgateway.pay-upon-invoice' ) )->init(); } - ( $c->get( 'wcgateway.oxxo' ) )->init(); + if ( defined( 'PPCP_FLAG_OXXO' ) && PPCP_FLAG_OXXO === true ) { + ( $c->get( 'wcgateway.oxxo' ) )->init(); + } } ); From 926e47a57f8cef60ad239fb9f306a6efebf09f96 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 2 Aug 2022 13:59:48 +0300 Subject: [PATCH 82/86] Disable card button feature via constant --- modules/ppcp-wc-gateway/services.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 157d3de31..63948c96f 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -2153,6 +2153,10 @@ return array( $fields['disable_cards']['options'] = $card_options; $fields['card_icons']['options'] = array_merge( $dark_versions, $card_options ); + if ( defined( 'PPCP_FLAG_SEPARATE_APM_BUTTONS' ) && PPCP_FLAG_SEPARATE_APM_BUTTONS === false ) { + unset( $fields['allow_card_button_gateway'] ); + } + return $fields; }, @@ -2402,6 +2406,10 @@ return array( return $container->get( 'api.shop.is-latin-america' ); }, 'wcgateway.settings.allow_card_button_gateway' => static function ( ContainerInterface $container ): bool { + if ( defined( 'PPCP_FLAG_SEPARATE_APM_BUTTONS' ) && PPCP_FLAG_SEPARATE_APM_BUTTONS === false ) { + return false; + } + $settings = $container->get( 'wcgateway.settings' ); assert( $settings instanceof ContainerInterface ); From af3b5d2a1cd57a6720441ee58120c6fde27cc088 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 2 Aug 2022 14:02:23 +0300 Subject: [PATCH 83/86] Improve feature flag filters names --- woocommerce-paypal-payments.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/woocommerce-paypal-payments.php b/woocommerce-paypal-payments.php index e0cc1b643..66e84b2bd 100644 --- a/woocommerce-paypal-payments.php +++ b/woocommerce-paypal-payments.php @@ -24,8 +24,8 @@ define( 'PAYPAL_SANDBOX_API_URL', 'https://api.sandbox.paypal.com' ); define( 'PAYPAL_INTEGRATION_DATE', '2022-04-13' ); define( 'PPCP_FLAG_SUBSCRIPTION', true ); -define( 'PPCP_FLAG_OXXO', apply_filters( 'woocommerce_paypal_payments_enable_oxxo', false ) ); -define( 'PPCP_FLAG_SEPARATE_APM_BUTTONS', apply_filters( 'woocommerce_paypal_payments_enable_separate_apm_buttons', false ) ); +define( 'PPCP_FLAG_OXXO', apply_filters( 'woocommerce_paypal_payments_enable_oxxo_feature', false ) ); +define( 'PPCP_FLAG_SEPARATE_APM_BUTTONS', apply_filters( 'woocommerce_paypal_payments_enable_separate_apm_buttons_feature', false ) ); ! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' ); ! defined( 'CONNECT_WOO_SANDBOX_CLIENT_ID' ) && define( 'CONNECT_WOO_SANDBOX_CLIENT_ID', 'AYmOHbt1VHg-OZ_oihPdzKEVbU3qg0qXonBcAztuzniQRaKE0w1Hr762cSFwd4n8wxOl-TCWohEa0XM_' ); From f95889e6da98d9a61858676637d29cf1ed60370f Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 2 Aug 2022 14:25:23 +0200 Subject: [PATCH 84/86] Bump 1.9.2 version --- changelog.txt | 12 ++++++++++++ package.json | 2 +- readme.txt | 14 +++++++++++++- woocommerce-paypal-payments.php | 4 ++-- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index 1dea43cea..9eccfa066 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,17 @@ *** Changelog *** += 1.9.2 - TBD = +* Fix - PayPal scripts loading on on pages without active smart buttons or Pay Later messaging #750 +* Fix - Capture Virtual-Only Orders setting does not auto-capture subscription renewal payments #626 +* Fix - Voiding authorization at PayPal does not update the status/order notes #712 +* Fix - The smart buttons are not loaded on single product page while subscription product exists in the Cart #703 +* Fix - DCC causes other gateways to disappear after checkout validation error #757 +* Enhancement - PAYER_ACTION_REQUIRED error #759 +* Enhancement - Store the customer id for vaulted payment method in usermeta #698 +* Enhancement - No minimum and maximum birth date range for PUI #743 +* Enhancement - Checkout Field Validation Message #739 +* Enhancement - Empty settings when Card Button, PUI gateways not available #753 + = 1.9.1 - 2022-07-25 = * Fix - ITEM_TOTAL_MISMATCH error when checking out with multiple products #721 * Fix - Unable to purchase a product with Credit card button in pay for order page #718 diff --git a/package.json b/package.json index f9b601973..edabc03b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "woocommerce-paypal-payments", - "version": "1.9.1", + "version": "1.9.2", "description": "WooCommerce PayPal Payments", "repository": "https://github.com/woocommerce/woocommerce-paypal-payments", "license": "GPL-2.0", diff --git a/readme.txt b/readme.txt index dec5134eb..e97ac3b24 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: woocommerce, paypal, payments, ecommerce, e-commerce, store, sales, sell, Requires at least: 5.3 Tested up to: 6.0 Requires PHP: 7.1 -Stable tag: 1.9.1 +Stable tag: 1.9.2 License: GPLv2 License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -81,6 +81,18 @@ Follow the steps below to connect the plugin to your PayPal account: == Changelog == += 1.9.2 = +* Fix - PayPal scripts loading on on pages without active smart buttons or Pay Later messaging #750 +* Fix - Capture Virtual-Only Orders setting does not auto-capture subscription renewal payments #626 +* Fix - Voiding authorization at PayPal does not update the status/order notes #712 +* Fix - The smart buttons are not loaded on single product page while subscription product exists in the Cart #703 +* Fix - DCC causes other gateways to disappear after checkout validation error #757 +* Enhancement - PAYER_ACTION_REQUIRED error #759 +* Enhancement - Store the customer id for vaulted payment method in usermeta #698 +* Enhancement - No minimum and maximum birth date range for PUI #743 +* Enhancement - Checkout Field Validation Message #739 +* Enhancement - Empty settings when Card Button, PUI gateways not available #753 + = 1.9.1 = * Fix - ITEM_TOTAL_MISMATCH error when checking out with multiple products #721 * Fix - Unable to purchase a product with Credit card button in pay for order page #718 diff --git a/woocommerce-paypal-payments.php b/woocommerce-paypal-payments.php index 66e84b2bd..6dc5feb05 100644 --- a/woocommerce-paypal-payments.php +++ b/woocommerce-paypal-payments.php @@ -3,13 +3,13 @@ * Plugin Name: WooCommerce PayPal Payments * Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/ * Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage. - * Version: 1.9.1 + * Version: 1.9.2 * Author: WooCommerce * Author URI: https://woocommerce.com/ * License: GPL-2.0 * Requires PHP: 7.1 * WC requires at least: 3.9 - * WC tested up to: 6.6 + * WC tested up to: 6.7 * Text Domain: woocommerce-paypal-payments * * @package WooCommerce\PayPalCommerce From 2fc0c609f3f379edebf08ed3c2d29b8c76c3ff68 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 2 Aug 2022 14:46:50 +0200 Subject: [PATCH 85/86] Update changelog --- changelog.txt | 21 +++++++++++---------- readme.txt | 21 +++++++++++---------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/changelog.txt b/changelog.txt index 9eccfa066..57df81252 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,16 +1,17 @@ *** Changelog *** = 1.9.2 - TBD = -* Fix - PayPal scripts loading on on pages without active smart buttons or Pay Later messaging #750 -* Fix - Capture Virtual-Only Orders setting does not auto-capture subscription renewal payments #626 -* Fix - Voiding authorization at PayPal does not update the status/order notes #712 -* Fix - The smart buttons are not loaded on single product page while subscription product exists in the Cart #703 -* Fix - DCC causes other gateways to disappear after checkout validation error #757 -* Enhancement - PAYER_ACTION_REQUIRED error #759 -* Enhancement - Store the customer id for vaulted payment method in usermeta #698 -* Enhancement - No minimum and maximum birth date range for PUI #743 -* Enhancement - Checkout Field Validation Message #739 -* Enhancement - Empty settings when Card Button, PUI gateways not available #753 +* Fix - Do not allow birth date older than 100 years for PUI. #743 +* Fix - Store the customer id for vaulted payment method in usermeta to not lose vaulted methods after the invoice prefix change. #698 +* Fix - Do not allow birth date older than 100 years for PUI. #743 +* Fix - Capture Virtual-Only Orders setting did not auto-capture subscription renewal payments. #626 +* Fix - Voiding authorization at PayPal did not update the status/order notes. #712 +* Fix - PayPal scripts were loading on pages without smart buttons or Pay Later messaging. #750 +* Fix - Do not show links for unavailable gateways settings pages. #753 +* Fix - The smart buttons were not loaded on single product page if a subscription product exists in the cart. #703 +* Fix - DCC was causing other gateways to disappear after checkout validation error #757 +* Enhancement - Improve Checkout Field Validation Message. #739 +* Enhancement - Handle PAYER_ACTION_REQUIRED error. #759 = 1.9.1 - 2022-07-25 = * Fix - ITEM_TOTAL_MISMATCH error when checking out with multiple products #721 diff --git a/readme.txt b/readme.txt index e97ac3b24..fb621a804 100644 --- a/readme.txt +++ b/readme.txt @@ -82,16 +82,17 @@ Follow the steps below to connect the plugin to your PayPal account: == Changelog == = 1.9.2 = -* Fix - PayPal scripts loading on on pages without active smart buttons or Pay Later messaging #750 -* Fix - Capture Virtual-Only Orders setting does not auto-capture subscription renewal payments #626 -* Fix - Voiding authorization at PayPal does not update the status/order notes #712 -* Fix - The smart buttons are not loaded on single product page while subscription product exists in the Cart #703 -* Fix - DCC causes other gateways to disappear after checkout validation error #757 -* Enhancement - PAYER_ACTION_REQUIRED error #759 -* Enhancement - Store the customer id for vaulted payment method in usermeta #698 -* Enhancement - No minimum and maximum birth date range for PUI #743 -* Enhancement - Checkout Field Validation Message #739 -* Enhancement - Empty settings when Card Button, PUI gateways not available #753 +* Fix - Do not allow birth date older than 100 years for PUI. #743 +* Fix - Store the customer id for vaulted payment method in usermeta to not lose vaulted methods after the invoice prefix change. #698 +* Fix - Do not allow birth date older than 100 years for PUI. #743 +* Fix - Capture Virtual-Only Orders setting did not auto-capture subscription renewal payments. #626 +* Fix - Voiding authorization at PayPal did not update the status/order notes. #712 +* Fix - PayPal scripts were loading on pages without smart buttons or Pay Later messaging. #750 +* Fix - Do not show links for unavailable gateways settings pages. #753 +* Fix - The smart buttons were not loaded on single product page if a subscription product exists in the cart. #703 +* Fix - DCC was causing other gateways to disappear after checkout validation error #757 +* Enhancement - Improve Checkout Field Validation Message. #739 +* Enhancement - Handle PAYER_ACTION_REQUIRED error. #759 = 1.9.1 = * Fix - ITEM_TOTAL_MISMATCH error when checking out with multiple products #721 From 5868055eebb76ad014083cba9dee9d5d3991cbf2 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 2 Aug 2022 14:57:57 +0200 Subject: [PATCH 86/86] Remove duplicated changelog entry --- changelog.txt | 1 - readme.txt | 1 - 2 files changed, 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index 57df81252..c1b80a430 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,7 +3,6 @@ = 1.9.2 - TBD = * Fix - Do not allow birth date older than 100 years for PUI. #743 * Fix - Store the customer id for vaulted payment method in usermeta to not lose vaulted methods after the invoice prefix change. #698 -* Fix - Do not allow birth date older than 100 years for PUI. #743 * Fix - Capture Virtual-Only Orders setting did not auto-capture subscription renewal payments. #626 * Fix - Voiding authorization at PayPal did not update the status/order notes. #712 * Fix - PayPal scripts were loading on pages without smart buttons or Pay Later messaging. #750 diff --git a/readme.txt b/readme.txt index fb621a804..55846e0bd 100644 --- a/readme.txt +++ b/readme.txt @@ -84,7 +84,6 @@ Follow the steps below to connect the plugin to your PayPal account: = 1.9.2 = * Fix - Do not allow birth date older than 100 years for PUI. #743 * Fix - Store the customer id for vaulted payment method in usermeta to not lose vaulted methods after the invoice prefix change. #698 -* Fix - Do not allow birth date older than 100 years for PUI. #743 * Fix - Capture Virtual-Only Orders setting did not auto-capture subscription renewal payments. #626 * Fix - Voiding authorization at PayPal did not update the status/order notes. #712 * Fix - PayPal scripts were loading on pages without smart buttons or Pay Later messaging. #750