From 4b6e2ab2d5f9942935e0fad31789d522f5996849 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 23 Jun 2022 19:05:55 +0400 Subject: [PATCH 01/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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/96] 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 From a893a1de8eaf802457633921ec49ba80f2de21c4 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 4 Aug 2022 09:55:27 +0300 Subject: [PATCH 87/96] Fallback to child nodes check in isAlreadyRendered when possible Otherwise it breaks at least in cart when updating shipping. --- .../resources/js/modules/Renderer/Renderer.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js index 1bd4ef757..65e96c0c8 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js @@ -22,7 +22,8 @@ class Renderer { this.renderButtons( settings.button.wrapper, settings.button.style, - contextConfig + contextConfig, + hasEnabledSeparateGateways ); } else { // render each button separately @@ -38,6 +39,7 @@ class Renderer { settings.button.wrapper, style, contextConfig, + hasEnabledSeparateGateways, fundingSource ); } @@ -50,13 +52,14 @@ class Renderer { data.wrapper, data.style, contextConfig, + hasEnabledSeparateGateways, fundingSource ); } } - renderButtons(wrapper, style, contextConfig, fundingSource = null) { - if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper, fundingSource) || 'undefined' === typeof paypal.Buttons ) { + renderButtons(wrapper, style, contextConfig, hasEnabledSeparateGateways, fundingSource = null) { + if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) || 'undefined' === typeof paypal.Buttons ) { return; } @@ -79,7 +82,14 @@ class Renderer { this.renderedSources.add(wrapper + fundingSource ?? ''); } - isAlreadyRendered(wrapper, fundingSource) { + isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) { + // Simply check that has child nodes when we do not need to render buttons separately, + // this will reduce the risk of breaking with different themes/plugins + // and on the cart page (where we also do not need to render separately), which may fully reload this part of the page. + // Ideally we should also find a way to detect such full reloads and remove the corresponding keys from the set. + if (!hasEnabledSeparateGateways) { + return document.querySelector(wrapper).hasChildNodes(); + } return this.renderedSources.has(wrapper + fundingSource ?? ''); } From f11f151e6510ad06202e24530a268af57fbbae71 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Thu, 4 Aug 2022 11:25:12 +0200 Subject: [PATCH 88/96] Disable gateways only for non PayPal funding sources --- modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php index dffecca37..b9fb934eb 100644 --- a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php +++ b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php @@ -107,7 +107,7 @@ class DisableGateways { */ private function needs_to_disable_gateways(): bool { $wc_ajax = filter_input( INPUT_GET, 'wc-ajax', FILTER_SANITIZE_STRING ) ?? ''; - if ( $wc_ajax === 'update_order_review' ) { + if ( $wc_ajax === 'update_order_review' && $this->session_handler->funding_source() !== 'paypal' ) { return false; } From aa18a0982caca681cd93af43c3dc69705dbf384b Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 5 Aug 2022 09:06:15 +0200 Subject: [PATCH 89/96] Do not check ajax for showing gateways --- modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php index b9fb934eb..8ecfce40c 100644 --- a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php +++ b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php @@ -106,11 +106,6 @@ 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' && $this->session_handler->funding_source() !== 'paypal' ) { - return false; - } - $order = $this->session_handler->order(); if ( ! $order ) { return false; From 65b435135b10ccd855dc2ed585ce565eb7f66760 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 5 Aug 2022 17:12:51 +0300 Subject: [PATCH 90/96] Revert removal of gateway status check Looks like it was accidentally removed after #704 merge conflict. Without it we always render the hidden area for separate card gateway, so the check in JS fails and we always render all standard buttons separately in checkout, which may result in wrong style and other issues. --- modules/ppcp-button/src/Assets/SmartButton.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index d7dd4c168..364189417 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -559,6 +559,12 @@ class SmartButton implements SmartButtonInterface { return; } + $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); + + if ( ! isset( $available_gateways[ $gateway_id ] ) ) { + return; + } + // The wrapper is needed for the loading spinner, // otherwise jQuery block() prevents buttons rendering. echo '
      '; From 0771b4082c2d1ed5ee6c6ed7e2b5860477c1c08e Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 8 Aug 2022 09:14:21 +0200 Subject: [PATCH 91/96] Update changelog --- changelog.txt | 2 ++ readme.txt | 2 ++ 2 files changed, 4 insertions(+) diff --git a/changelog.txt b/changelog.txt index c1b80a430..0b2602079 100644 --- a/changelog.txt +++ b/changelog.txt @@ -9,6 +9,8 @@ * 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 +* Fix - Fallback to child nodes check in isAlreadyRendered when possible #768 +* Fix - Revert removal of gateway status check #770 * Enhancement - Improve Checkout Field Validation Message. #739 * Enhancement - Handle PAYER_ACTION_REQUIRED error. #759 diff --git a/readme.txt b/readme.txt index 55846e0bd..146a51e82 100644 --- a/readme.txt +++ b/readme.txt @@ -90,6 +90,8 @@ Follow the steps below to connect the plugin to your PayPal account: * 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 +* Fix - Fallback to child nodes check in isAlreadyRendered when possible #768 +* Fix - Revert removal of gateway status check #770 * Enhancement - Improve Checkout Field Validation Message. #739 * Enhancement - Handle PAYER_ACTION_REQUIRED error. #759 From 1d6a6651d81be250095984fe5f703321bac4a84f Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 8 Aug 2022 09:20:18 +0200 Subject: [PATCH 92/96] Remove changelog entries --- changelog.txt | 2 -- readme.txt | 2 -- 2 files changed, 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index 0b2602079..c1b80a430 100644 --- a/changelog.txt +++ b/changelog.txt @@ -9,8 +9,6 @@ * 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 -* Fix - Fallback to child nodes check in isAlreadyRendered when possible #768 -* Fix - Revert removal of gateway status check #770 * Enhancement - Improve Checkout Field Validation Message. #739 * Enhancement - Handle PAYER_ACTION_REQUIRED error. #759 diff --git a/readme.txt b/readme.txt index 146a51e82..55846e0bd 100644 --- a/readme.txt +++ b/readme.txt @@ -90,8 +90,6 @@ Follow the steps below to connect the plugin to your PayPal account: * 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 -* Fix - Fallback to child nodes check in isAlreadyRendered when possible #768 -* Fix - Revert removal of gateway status check #770 * Enhancement - Improve Checkout Field Validation Message. #739 * Enhancement - Handle PAYER_ACTION_REQUIRED error. #759 From 012204d393eb19c834e84004be527c3f7019f844 Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Mon, 8 Aug 2022 15:33:55 -0300 Subject: [PATCH 93/96] Update release date for 1.9.2 --- changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index c1b80a430..bbb4ce86b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,6 @@ *** Changelog *** -= 1.9.2 - TBD = += 1.9.2 - 2022-08-08 = * 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 - Capture Virtual-Only Orders setting did not auto-capture subscription renewal payments. #626 From 611fc63aeb0896c2ebcf9a52229154455a3cbdaa Mon Sep 17 00:00:00 2001 From: Danae Millan <41606954+a-danae@users.noreply.github.com> Date: Mon, 8 Aug 2022 16:50:20 -0300 Subject: [PATCH 94/96] Revert "Update release date for 1.9.2" --- changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index bbb4ce86b..c1b80a430 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,6 @@ *** Changelog *** -= 1.9.2 - 2022-08-08 = += 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 - Capture Virtual-Only Orders setting did not auto-capture subscription renewal payments. #626 From 3b33ffe90360e9f664857ed4e8d03da1f5125c22 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 9 Aug 2022 09:44:26 +0300 Subject: [PATCH 95/96] Fix style merging --- modules/ppcp-button/src/Assets/SmartButton.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 364189417..b69417a03 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -912,10 +912,10 @@ class SmartButton implements SmartButtonInterface { ); if ( $this->style_for_context( 'layout', 'mini-cart' ) !== 'horizontal' ) { - unset( $localize['button']['mini_cart_style']['tagline'] ); + $localize['button']['mini_cart_style']['tagline'] = false; } if ( $this->style_for_context( 'layout', $this->context() ) !== 'horizontal' ) { - unset( $localize['button']['style']['tagline'] ); + $localize['button']['style']['tagline'] = false; } $this->request_data->dequeue_nonce_fix(); From 4d37d53f6aa07620cc069ccb2c778035fbc42c53 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 9 Aug 2022 11:01:41 +0200 Subject: [PATCH 96/96] Update changelog --- changelog.txt | 3 ++- readme.txt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index c1b80a430..423d52ab8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -8,7 +8,8 @@ * 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 +* Fix - DCC was causing other gateways to disappear after checkout validation error. #757 +* Fix - Buttons not loading on single product page with default settings when product is in cart. #777 * Enhancement - Improve Checkout Field Validation Message. #739 * Enhancement - Handle PAYER_ACTION_REQUIRED error. #759 diff --git a/readme.txt b/readme.txt index 55846e0bd..e3bf1233a 100644 --- a/readme.txt +++ b/readme.txt @@ -89,7 +89,8 @@ Follow the steps below to connect the plugin to your PayPal account: * 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 +* Fix - DCC was causing other gateways to disappear after checkout validation error. #757 +* Fix - Buttons not loading on single product page with default settings when product is in cart. #777 * Enhancement - Improve Checkout Field Validation Message. #739 * Enhancement - Handle PAYER_ACTION_REQUIRED error. #759