From 4639a385d6ff609559cac05b61b6d8622a27537b Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 17 Jun 2022 17:19:03 +0400 Subject: [PATCH 001/145] Fix credit card detection. --- .../js/modules/Helper/DccInputFactory.js | 7 ++++-- .../js/modules/Renderer/CreditCardRenderer.js | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/Helper/DccInputFactory.js b/modules/ppcp-button/resources/js/modules/Helper/DccInputFactory.js index c01969269..c5a2034fc 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/DccInputFactory.js +++ b/modules/ppcp-button/resources/js/modules/Helper/DccInputFactory.js @@ -1,9 +1,12 @@ const dccInputFactory = (original) => { const styles = window.getComputedStyle(original); const newElement = document.createElement('span'); + newElement.setAttribute('id', original.id); + newElement.setAttribute('class', original.className); + Object.values(styles).forEach( (prop) => { - if (! styles[prop] || ! isNaN(prop) ) { + if (! styles[prop] || ! isNaN(prop) || prop === 'background-image' ) { return; } newElement.style.setProperty(prop,'' + styles[prop]); @@ -11,4 +14,4 @@ const dccInputFactory = (original) => { return newElement; } -export default dccInputFactory; \ No newline at end of file +export default dccInputFactory; diff --git a/modules/ppcp-button/resources/js/modules/Renderer/CreditCardRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/CreditCardRenderer.js index e2c08a483..7e0b5a235 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/CreditCardRenderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/CreditCardRenderer.js @@ -1,5 +1,6 @@ import dccInputFactory from "../Helper/DccInputFactory"; import {show} from "../Helper/Hiding"; +import Product from "../Entity/Product"; class CreditCardRenderer { @@ -117,11 +118,23 @@ class CreditCardRenderer { } const validCards = this.defaultConfig.hosted_fields.valid_cards; this.cardValid = validCards.indexOf(event.cards[0].type) !== -1; + + const className = this._cardNumberFiledCLassNameByCardType(event.cards[0].type); + this._recreateElementClassAttribute(cardNumber, cardNumberField.className); + if (event.fields.number.isValid) { + cardNumber.classList.add(className); + } }) hostedFields.on('validityChange', (event) => { const formValid = Object.keys(event.fields).every(function (key) { return event.fields[key].isValid; }); + + const className = this._cardNumberFiledCLassNameByCardType(event.cards[0].type); + event.fields.number.isValid + ? cardNumber.classList.add(className) + : this._recreateElementClassAttribute(cardNumber, cardNumberField.className); + this.formValid = formValid; }); @@ -230,5 +243,14 @@ class CreditCardRenderer { this.errorHandler.message(message); } } + + _cardNumberFiledCLassNameByCardType(cardType) { + return cardType === 'american-express' ? 'amex' : cardType.replace('-', ''); + } + + _recreateElementClassAttribute(element, newClassName) { + element.removeAttribute('class') + element.setAttribute('class', newClassName); + } } export default CreditCardRenderer; From 9da3f112bc9708719e8b835e6f2a337c10ebc4c4 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 20 Jun 2022 15:28:07 +0400 Subject: [PATCH 002/145] Add the hiding messages callback to single product handler --- .../ContextBootstrap/SingleProductBootstap.js | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js index 927b36d84..301b03cd9 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js @@ -41,7 +41,7 @@ class SingleProductBootstap { } - priceAmountIsZero() { + priceAmount() { let priceText = "0"; if (document.querySelector('form.cart ins .woocommerce-Price-amount')) { @@ -53,9 +53,12 @@ class SingleProductBootstap { else if (document.querySelector('.product .woocommerce-Price-amount')) { priceText = document.querySelector('.product .woocommerce-Price-amount').innerText; } - const amount = parseFloat(priceText.replace(/([^\d,\.\s]*)/g, '')); - return amount === 0; + return parseFloat(priceText.replace(/([^\d,\.\s]*)/g, '')); + } + + priceAmountIsZero() { + return this.priceAmount() === 0; } render() { @@ -68,19 +71,12 @@ class SingleProductBootstap { () => { this.renderer.showButtons(this.gateway.button.wrapper); this.renderer.showButtons(this.gateway.hosted_fields.wrapper); - let priceText = "0"; - if (document.querySelector('form.cart ins .woocommerce-Price-amount')) { - priceText = document.querySelector('form.cart ins .woocommerce-Price-amount').innerText; - } - else if (document.querySelector('form.cart .woocommerce-Price-amount')) { - priceText = document.querySelector('form.cart .woocommerce-Price-amount').innerText; - } - const amount = parseInt(priceText.replace(/([^\d,\.\s]*)/g, '')); - this.messages.renderWithAmount(amount) + this.messages.renderWithAmount(this.priceAmount()) }, () => { this.renderer.hideButtons(this.gateway.button.wrapper); this.renderer.hideButtons(this.gateway.hosted_fields.wrapper); + this.messages.hideMessages(); }, document.querySelector('form.cart'), new ErrorHandler(this.gateway.labels.error.generic), From 20c3c6e34556e6c49ccab8b5193ba3d579f5c2a1 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 20 Jun 2022 17:12:58 +0400 Subject: [PATCH 003/145] Fix messaging display when the buttons are disabled. --- modules/ppcp-button/src/Assets/SmartButton.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index bdb6522e6..20af30ef4 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -840,7 +840,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' ), @@ -1037,6 +1037,7 @@ class SmartButton implements SmartButtonInterface { $this->context() === 'product' && $this->settings->has( 'button_product_enabled' ) && $this->settings->get( 'button_product_enabled' ) + || $this->settings->has( 'message_product_enabled' ) ) { $load_buttons = true; } @@ -1050,6 +1051,7 @@ class SmartButton implements SmartButtonInterface { $this->context() === 'cart' && $this->settings->has( 'button_cart_enabled' ) && $this->settings->get( 'button_cart_enabled' ) + || $this->settings->has( 'message_product_enabled' ) ) { $load_buttons = true; } From 4b6e2ab2d5f9942935e0fad31789d522f5996849 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 23 Jun 2022 19:05:55 +0400 Subject: [PATCH 004/145] 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 005/145] 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 006/145] 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 007/145] 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 b71cff1d57bc82fab0423ad64cab6b5a8f32463a Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 1 Jul 2022 14:57:59 +0200 Subject: [PATCH 008/145] Check pui currency in payment gateways --- modules/ppcp-wc-gateway/src/WCGatewayModule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 5d85638f1..acd2833fe 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -284,7 +284,7 @@ class WCGatewayModule implements ModuleInterface { $methods[] = $container->get( 'wcgateway.credit-card-gateway' ); } - if ( 'DE' === $container->get( 'api.shop.country' ) ) { + if ( 'DE' === $container->get( 'api.shop.country' ) && 'EUR' === get_woocommerce_currency() ) { $methods[] = $container->get( 'wcgateway.pay-upon-invoice-gateway' ); } From 86f85eff635817e701f24936de61f1d7782e5288 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 1 Jul 2022 15:50:14 +0200 Subject: [PATCH 009/145] Use shop currency service --- modules/ppcp-wc-gateway/src/WCGatewayModule.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index acd2833fe..8bb469a9d 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -228,7 +228,7 @@ class WCGatewayModule implements ModuleInterface { add_action( 'init', function () use ( $c ) { - if ( 'DE' === $c->get( 'api.shop.country' ) && 'EUR' === get_woocommerce_currency() ) { + if ( 'DE' === $c->get( 'api.shop.country' ) && 'EUR' === $c->get( 'api.shop.currency' ) ) { ( $c->get( 'wcgateway.pay-upon-invoice' ) )->init(); } } @@ -284,7 +284,7 @@ class WCGatewayModule implements ModuleInterface { $methods[] = $container->get( 'wcgateway.credit-card-gateway' ); } - if ( 'DE' === $container->get( 'api.shop.country' ) && 'EUR' === get_woocommerce_currency() ) { + if ( 'DE' === $container->get( 'api.shop.country' ) && 'EUR' === $container->get( 'api.shop.currency' ) ) { $methods[] = $container->get( 'wcgateway.pay-upon-invoice-gateway' ); } From 3472a1709f3002673f1800e1a402bd3e60d7cdd1 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 1 Jul 2022 19:01:21 +0400 Subject: [PATCH 010/145] 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 f013d190470fb884a83ad00b30a36e51ade2315b Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 4 Jul 2022 12:20:34 +0200 Subject: [PATCH 011/145] Add PayPal fee to pui order --- .../Endpoint/PayUponInvoiceOrderEndpoint.php | 12 ++++----- modules/ppcp-wc-gateway/services.php | 3 ++- .../Gateway/PayUponInvoice/PayUponInvoice.php | 27 +++++++++++++++++-- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/PayUponInvoiceOrderEndpoint.php b/modules/ppcp-api-client/src/Endpoint/PayUponInvoiceOrderEndpoint.php index 91d1ba79a..41e5563ec 100644 --- a/modules/ppcp-api-client/src/Endpoint/PayUponInvoiceOrderEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/PayUponInvoiceOrderEndpoint.php @@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Endpoint; use Psr\Log\LoggerInterface; use RuntimeException; +use stdClass; use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\RequestTrait; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; @@ -162,14 +163,14 @@ class PayUponInvoiceOrderEndpoint { } /** - * Get Ratepay payment instructions from PayPal order. + * Get PayPal order as object. * * @param string $id The PayPal order ID. - * @return array + * @return stdClass * @throws RuntimeException When there is a problem getting the order. * @throws PayPalApiException When there is a problem getting the order. */ - public function order_payment_instructions( string $id ): array { + public function order( string $id ): stdClass { $bearer = $this->bearer->bearer(); $url = trailingslashit( $this->host ) . 'v2/checkout/orders/' . $id; $args = array( @@ -191,10 +192,7 @@ class PayUponInvoiceOrderEndpoint { throw new PayPalApiException( $json, $status_code ); } - return array( - $json->payment_source->pay_upon_invoice->payment_reference, - $json->payment_source->pay_upon_invoice->deposit_bank_details, - ); + return $json; } /** diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 63a5a4814..e2c2b7053 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -2214,7 +2214,8 @@ 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( 'api.factory.capture' ) ); }, 'wcgateway.logging.is-enabled' => function ( ContainerInterface $container ) : bool { diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index 0ebec8c17..5b684ea6f 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -17,8 +17,10 @@ use WC_Product; use WC_Product_Variable; use WC_Product_Variation; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PayUponInvoiceOrderEndpoint; +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\PayUponInvoiceHelper; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus; @@ -118,6 +120,13 @@ class PayUponInvoice { */ protected $pui_product_status; + /** + * The capture factory. + * + * @var CaptureFactory + */ + protected $capture_factory; + /** * PayUponInvoice constructor. * @@ -133,6 +142,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. */ public function __construct( string $module_url, @@ -146,7 +156,8 @@ class PayUponInvoice { bool $is_ppcp_settings_page, string $current_ppcp_settings_page_id, PayUponInvoiceProductStatus $pui_product_status, - PayUponInvoiceHelper $pui_helper + PayUponInvoiceHelper $pui_helper, + CaptureFactory $capture_factory ) { $this->module_url = $module_url; $this->fraud_net = $fraud_net; @@ -160,6 +171,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; } /** @@ -210,7 +222,12 @@ class PayUponInvoice { 'ppcp_payment_capture_completed_webhook_handler', function ( WC_Order $wc_order, string $order_id ) { try { - $payment_instructions = $this->pui_order_endpoint->order_payment_instructions( $order_id ); + $order = $this->pui_order_endpoint->order( $order_id ); + + $payment_instructions = array( + $order->payment_source->pay_upon_invoice->payment_reference, + $order->payment_source->pay_upon_invoice->deposit_bank_details, + ); $wc_order->update_meta_data( 'ppcp_ratepay_payment_instructions_payment_reference', $payment_instructions @@ -218,6 +235,12 @@ class PayUponInvoice { $wc_order->save_meta_data(); $this->logger->info( "Ratepay payment instructions added to order #{$wc_order->get_id()}." ); + $capture = $this->capture_factory->from_paypal_response( $order ); + $breakdown = $capture->seller_receivable_breakdown(); + if ( $breakdown ) { + $wc_order->update_meta_data( PayPalGateway::FEES_META_KEY, $breakdown->to_array() ); + $wc_order->save_meta_data(); + } } catch ( RuntimeException $exception ) { $this->logger->error( $exception->getMessage() ); } From 7aca62a1a30fbcef72e5e8e7edb96b5fcad45c1f Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 4 Jul 2022 14:34:09 +0200 Subject: [PATCH 012/145] Create capture entity from order response capture --- .../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 5b684ea6f..628ae89e6 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -235,7 +235,7 @@ class PayUponInvoice { $wc_order->save_meta_data(); $this->logger->info( "Ratepay payment instructions added to order #{$wc_order->get_id()}." ); - $capture = $this->capture_factory->from_paypal_response( $order ); + $capture = $this->capture_factory->from_paypal_response( $order->purchase_units[0]->payments->captures[0] ); $breakdown = $capture->seller_receivable_breakdown(); if ( $breakdown ) { $wc_order->update_meta_data( PayPalGateway::FEES_META_KEY, $breakdown->to_array() ); From b15c03d4181b6af88e3376e5dd93e808996d7271 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 4 Jul 2022 17:30:29 +0400 Subject: [PATCH 013/145] 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 014/145] 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 015/145] 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 fff25701249bb4b1df79ad08cab132e3403f9a70 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 5 Jul 2022 14:25:55 +0300 Subject: [PATCH 016/145] Refactor shipping_preference Extract its determination to a separate class, also remove useless CartRepository and use single PU to avoid confusion. --- modules/ppcp-api-client/services.php | 9 +- .../src/Endpoint/OrderEndpoint.php | 56 +--------- .../src/Factory/PurchaseUnitFactory.php | 8 +- .../src/Factory/ShippingPreferenceFactory.php | 54 ++++++++++ .../src/Repository/CartRepository.php | 45 -------- .../PurchaseUnitRepositoryInterface.php | 26 ----- modules/ppcp-button/services.php | 7 +- .../src/Endpoint/ChangeCartEndpoint.php | 42 ++++---- .../src/Endpoint/CreateOrderEndpoint.php | 101 +++++++++--------- modules/ppcp-subscription/services.php | 1 + .../ppcp-subscription/src/RenewalHandler.php | 44 +++++--- modules/ppcp-wc-gateway/services.php | 2 + .../src/Gateway/CreditCardGateway.php | 31 ++++-- .../src/Gateway/PayPalGateway.php | 11 ++ .../src/Gateway/ProcessPaymentTrait.php | 7 ++ .../ApiClient/Endpoint/OrderEndpointTest.php | 8 +- .../Endpoint/ChangeCartEndpointTest.php | 37 +++---- .../Endpoint/CreateOrderEndpointTest.php | 6 +- .../Subscription/RenewalHandlerTest.php | 10 +- .../WcGateway/Gateway/WcGatewayTest.php | 4 + 20 files changed, 245 insertions(+), 264 deletions(-) create mode 100644 modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php delete mode 100644 modules/ppcp-api-client/src/Repository/CartRepository.php delete mode 100644 modules/ppcp-api-client/src/Repository/PurchaseUnitRepositoryInterface.php diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 6be5355be..f34ac93da 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -43,13 +43,13 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\SellerReceivableBreakdownFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\SellerStatusFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingFactory; +use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookEventFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookFactory; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderHelper; use WooCommerce\PayPalCommerce\ApiClient\Repository\ApplicationContextRepository; -use WooCommerce\PayPalCommerce\ApiClient\Repository\CartRepository; use WooCommerce\PayPalCommerce\ApiClient\Repository\CustomerRepository; use WooCommerce\PayPalCommerce\ApiClient\Repository\OrderRepository; use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData; @@ -221,10 +221,6 @@ return array( $dcc_applies = $container->get( 'api.helpers.dccapplies' ); return new PartnerReferralsData( $dcc_applies ); }, - 'api.repository.cart' => static function ( ContainerInterface $container ): CartRepository { - $factory = $container->get( 'api.factory.purchase-unit' ); - return new CartRepository( $factory ); - }, 'api.repository.payee' => static function ( ContainerInterface $container ): PayeeRepository { $merchant_email = $container->get( 'api.merchant_email' ); $merchant_id = $container->get( 'api.merchant_id' ); @@ -298,6 +294,9 @@ return array( $address_factory = $container->get( 'api.factory.address' ); return new ShippingFactory( $address_factory ); }, + 'api.factory.shipping-preference' => static function ( ContainerInterface $container ): ShippingPreferenceFactory { + return new ShippingPreferenceFactory(); + }, 'api.factory.amount' => static function ( ContainerInterface $container ): AmountFactory { $item_factory = $container->get( 'api.factory.item' ); return new AmountFactory( diff --git a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php index b12d1a025..41448a542 100644 --- a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php @@ -163,57 +163,23 @@ class OrderEndpoint { * Creates an order. * * @param PurchaseUnit[] $items The purchase unit items for the order. + * @param string $shipping_preference One of ApplicationContext::SHIPPING_PREFERENCE_ values. * @param Payer|null $payer The payer off the order. * @param PaymentToken|null $payment_token The payment token. * @param PaymentMethod|null $payment_method The payment method. * @param string $paypal_request_id The paypal request id. - * @param bool $shipping_address_is_fixed Whether the shipping address is changeable or not. * * @return Order * @throws RuntimeException If the request fails. */ public function create( array $items, + string $shipping_preference, Payer $payer = null, PaymentToken $payment_token = null, PaymentMethod $payment_method = null, - string $paypal_request_id = '', - bool $shipping_address_is_fixed = false + string $paypal_request_id = '' ): Order { - - $contains_physical_goods = false; - $items = array_filter( - $items, - static function ( $item ) use ( &$contains_physical_goods ): bool { - $is_purchase_unit = is_a( $item, PurchaseUnit::class ); - /** - * A purchase unit. - * - * @var PurchaseUnit $item - */ - if ( $is_purchase_unit && $item->contains_physical_goods() ) { - $contains_physical_goods = true; - } - - return $is_purchase_unit; - } - ); - - $shipping_preference = ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING; - if ( $contains_physical_goods ) { - if ( $shipping_address_is_fixed ) { - // Checkout + no address given? Probably something weird happened, like no form validation? - // Also note that $items currently always seems to be an array with one item. - if ( $this->has_items_without_shipping( $items ) ) { - $shipping_preference = ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING; - } else { - $shipping_preference = ApplicationContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS; - } - } else { - $shipping_preference = ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE; - } - } - $bearer = $this->bearer->bearer(); $data = array( 'intent' => ( $this->subscription_helper->cart_contains_subscription() || $this->subscription_helper->current_product_is_subscription() ) ? 'AUTHORIZE' : $this->intent, @@ -598,20 +564,4 @@ class OrderEndpoint { $new_order = $this->order( $order_to_update->id() ); return $new_order; } - - /** - * Checks if there is at least one item without shipping. - * - * @param PurchaseUnit[] $items The items. - * @return bool Whether items contains shipping or not. - */ - private function has_items_without_shipping( array $items ): bool { - foreach ( $items as $item ) { - if ( ! $item->shipping() ) { - return true; - } - } - - return false; - } } diff --git a/modules/ppcp-api-client/src/Factory/PurchaseUnitFactory.php b/modules/ppcp-api-client/src/Factory/PurchaseUnitFactory.php index 303b7bc6a..c2667fc92 100644 --- a/modules/ppcp-api-client/src/Factory/PurchaseUnitFactory.php +++ b/modules/ppcp-api-client/src/Factory/PurchaseUnitFactory.php @@ -152,11 +152,15 @@ class PurchaseUnitFactory { /** * Creates a PurchaseUnit based off a WooCommerce cart. * - * @param \WC_Cart $cart The cart. + * @param \WC_Cart|null $cart The cart. * * @return PurchaseUnit */ - public function from_wc_cart( \WC_Cart $cart ): PurchaseUnit { + public function from_wc_cart( ?\WC_Cart $cart = null ): PurchaseUnit { + if ( ! $cart ) { + $cart = WC()->cart ?? new \WC_Cart(); + } + $amount = $this->amount_factory->from_wc_cart( $cart ); $items = array_filter( $this->item_factory->from_wc_cart( $cart ), diff --git a/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php b/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php new file mode 100644 index 000000000..322d598fd --- /dev/null +++ b/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php @@ -0,0 +1,54 @@ +contains_physical_goods(); + if ( ! $contains_physical_goods ) { + return ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING; + } + + $has_shipping = null !== $purchase_unit->shipping(); + $needs_shipping = $cart && $cart->needs_shipping(); + $shipping_address_is_fixed = $needs_shipping && 'checkout' === $context; + + if ( $shipping_address_is_fixed ) { + // Checkout + no address given? Probably something weird happened, like no form validation? + if ( ! $has_shipping ) { + return ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING; + } + + return ApplicationContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS; + } + + return ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE; + } +} diff --git a/modules/ppcp-api-client/src/Repository/CartRepository.php b/modules/ppcp-api-client/src/Repository/CartRepository.php deleted file mode 100644 index 226f50d91..000000000 --- a/modules/ppcp-api-client/src/Repository/CartRepository.php +++ /dev/null @@ -1,45 +0,0 @@ -factory = $factory; - } - - /** - * Returns all Pur of the WooCommerce cart. - * - * @return PurchaseUnit[] - */ - public function all(): array { - $cart = WC()->cart ?? new \WC_Cart(); - return array( $this->factory->from_wc_cart( $cart ) ); - } -} diff --git a/modules/ppcp-api-client/src/Repository/PurchaseUnitRepositoryInterface.php b/modules/ppcp-api-client/src/Repository/PurchaseUnitRepositoryInterface.php deleted file mode 100644 index 6edbb1bbf..000000000 --- a/modules/ppcp-api-client/src/Repository/PurchaseUnitRepositoryInterface.php +++ /dev/null @@ -1,26 +0,0 @@ -cart; $shipping = WC()->shipping(); $request_data = $container->get( 'button.request-data' ); - $repository = $container->get( 'api.repository.cart' ); + $purchase_unit_factory = $container->get( 'api.factory.purchase-unit' ); $data_store = \WC_Data_Store::load( 'product' ); $logger = $container->get( 'woocommerce.logger.woocommerce' ); - return new ChangeCartEndpoint( $cart, $shipping, $request_data, $repository, $data_store, $logger ); + return new ChangeCartEndpoint( $cart, $shipping, $request_data, $purchase_unit_factory, $data_store, $logger ); }, 'button.endpoint.create-order' => static function ( ContainerInterface $container ): CreateOrderEndpoint { $request_data = $container->get( 'button.request-data' ); - $cart_repository = $container->get( 'api.repository.cart' ); $purchase_unit_factory = $container->get( 'api.factory.purchase-unit' ); $order_endpoint = $container->get( 'api.endpoint.order' ); $payer_factory = $container->get( 'api.factory.payer' ); @@ -126,8 +125,8 @@ return array( $logger = $container->get( 'woocommerce.logger.woocommerce' ); return new CreateOrderEndpoint( $request_data, - $cart_repository, $purchase_unit_factory, + $container->get( 'api.factory.shipping-preference' ), $order_endpoint, $payer_factory, $session_handler, diff --git a/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php b/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php index 1ad4b451d..1c64ddb0d 100644 --- a/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php @@ -13,7 +13,7 @@ use Exception; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; -use WooCommerce\PayPalCommerce\ApiClient\Repository\CartRepository; +use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException; /** @@ -46,11 +46,11 @@ class ChangeCartEndpoint implements EndpointInterface { private $request_data; /** - * Contains purchase units based off the current WC cart. + * The PurchaseUnit factory. * - * @var CartRepository + * @var PurchaseUnitFactory */ - private $repository; + private $purchase_unit_factory; /** * The product data store. @@ -69,28 +69,28 @@ class ChangeCartEndpoint implements EndpointInterface { /** * ChangeCartEndpoint constructor. * - * @param \WC_Cart $cart The current WC cart object. - * @param \WC_Shipping $shipping The current WC shipping object. - * @param RequestData $request_data The request data helper. - * @param CartRepository $repository The repository for the current purchase items. - * @param \WC_Data_Store $product_data_store The data store for products. - * @param LoggerInterface $logger The logger. + * @param \WC_Cart $cart The current WC cart object. + * @param \WC_Shipping $shipping The current WC shipping object. + * @param RequestData $request_data The request data helper. + * @param PurchaseUnitFactory $purchase_unit_factory The PurchaseUnit factory. + * @param \WC_Data_Store $product_data_store The data store for products. + * @param LoggerInterface $logger The logger. */ public function __construct( \WC_Cart $cart, \WC_Shipping $shipping, RequestData $request_data, - CartRepository $repository, + PurchaseUnitFactory $purchase_unit_factory, \WC_Data_Store $product_data_store, LoggerInterface $logger ) { - $this->cart = $cart; - $this->shipping = $shipping; - $this->request_data = $request_data; - $this->repository = $repository; - $this->product_data_store = $product_data_store; - $this->logger = $logger; + $this->cart = $cart; + $this->shipping = $shipping; + $this->request_data = $request_data; + $this->purchase_unit_factory = $purchase_unit_factory; + $this->product_data_store = $product_data_store; + $this->logger = $logger; } /** @@ -292,11 +292,7 @@ class ChangeCartEndpoint implements EndpointInterface { * @return array */ private function generate_purchase_units(): array { - return array_map( - static function ( PurchaseUnit $line_item ): array { - return $line_item->to_array(); - }, - $this->repository->all() - ); + $pu = $this->purchase_unit_factory->from_wc_cart(); + return array( $pu->to_array() ); } } diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index 81344bb64..9d3ed2d4e 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -23,7 +23,7 @@ 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\Repository\CartRepository; +use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; @@ -48,13 +48,6 @@ class CreateOrderEndpoint implements EndpointInterface { */ private $request_data; - /** - * The cart repository. - * - * @var CartRepository - */ - private $cart_repository; - /** * The PurchaseUnit factory. * @@ -62,6 +55,13 @@ class CreateOrderEndpoint implements EndpointInterface { */ private $purchase_unit_factory; + /** + * The shipping_preference factory. + * + * @var ShippingPreferenceFactory + */ + private $shipping_preference_factory; + /** * The order endpoint. * @@ -105,11 +105,11 @@ class CreateOrderEndpoint implements EndpointInterface { private $parsed_request_data; /** - * The array of purchase units for order. + * The purchase unit for order. * - * @var PurchaseUnit[] + * @var PurchaseUnit|null */ - private $purchase_units; + private $purchase_unit; /** * Whether a new user must be registered during checkout. @@ -128,21 +128,21 @@ class CreateOrderEndpoint implements EndpointInterface { /** * CreateOrderEndpoint constructor. * - * @param RequestData $request_data The RequestData object. - * @param CartRepository $cart_repository The CartRepository object. - * @param PurchaseUnitFactory $purchase_unit_factory The Purchaseunit factory. - * @param OrderEndpoint $order_endpoint The OrderEndpoint object. - * @param PayerFactory $payer_factory The PayerFactory object. - * @param SessionHandler $session_handler The SessionHandler object. - * @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 LoggerInterface $logger The logger. + * @param RequestData $request_data The RequestData object. + * @param PurchaseUnitFactory $purchase_unit_factory The PurchaseUnit factory. + * @param ShippingPreferenceFactory $shipping_preference_factory The shipping_preference factory. + * @param OrderEndpoint $order_endpoint The OrderEndpoint object. + * @param PayerFactory $payer_factory The PayerFactory object. + * @param SessionHandler $session_handler The SessionHandler object. + * @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 LoggerInterface $logger The logger. */ public function __construct( RequestData $request_data, - CartRepository $cart_repository, PurchaseUnitFactory $purchase_unit_factory, + ShippingPreferenceFactory $shipping_preference_factory, OrderEndpoint $order_endpoint, PayerFactory $payer_factory, SessionHandler $session_handler, @@ -152,16 +152,16 @@ class CreateOrderEndpoint implements EndpointInterface { LoggerInterface $logger ) { - $this->request_data = $request_data; - $this->cart_repository = $cart_repository; - $this->purchase_unit_factory = $purchase_unit_factory; - $this->api_endpoint = $order_endpoint; - $this->payer_factory = $payer_factory; - $this->session_handler = $session_handler; - $this->settings = $settings; - $this->early_order_handler = $early_order_handler; - $this->registration_needed = $registration_needed; - $this->logger = $logger; + $this->request_data = $request_data; + $this->purchase_unit_factory = $purchase_unit_factory; + $this->shipping_preference_factory = $shipping_preference_factory; + $this->api_endpoint = $order_endpoint; + $this->payer_factory = $payer_factory; + $this->session_handler = $session_handler; + $this->settings = $settings; + $this->early_order_handler = $early_order_handler; + $this->registration_needed = $registration_needed; + $this->logger = $logger; } /** @@ -198,9 +198,9 @@ class CreateOrderEndpoint implements EndpointInterface { ) ); } - $this->purchase_units = array( $this->purchase_unit_factory->from_wc_order( $wc_order ) ); + $this->purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); } else { - $this->purchase_units = $this->cart_repository->all(); + $this->purchase_unit = $this->purchase_unit_factory->from_wc_cart(); // The cart does not have any info about payment method, so we must handle free trial here. if ( ( @@ -209,10 +209,10 @@ class CreateOrderEndpoint implements EndpointInterface { ) && $this->is_free_trial_cart() ) { - $this->purchase_units[0]->set_amount( + $this->purchase_unit->set_amount( new Amount( - new Money( 1.0, $this->purchase_units[0]->amount()->currency_code() ), - $this->purchase_units[0]->amount()->breakdown() + new Money( 1.0, $this->purchase_unit->amount()->currency_code() ), + $this->purchase_unit->amount()->breakdown() ) ); } @@ -329,17 +329,21 @@ class CreateOrderEndpoint implements EndpointInterface { * phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber */ private function create_paypal_order( \WC_Order $wc_order = null ): Order { - $needs_shipping = WC()->cart instanceof \WC_Cart && WC()->cart->needs_shipping(); - $shipping_address_is_fix = $needs_shipping && 'checkout' === $this->parsed_request_data['context']; + assert( $this->purchase_unit instanceof PurchaseUnit ); + + $shipping_preference = $this->shipping_preference_factory->from_state( + $this->purchase_unit, + $this->parsed_request_data['context'], + WC()->cart + ); try { return $this->api_endpoint->create( - $this->purchase_units, + array( $this->purchase_unit ), + $shipping_preference, $this->payer( $this->parsed_request_data, $wc_order ), null, - $this->payment_method(), - '', - $shipping_address_is_fix + $this->payment_method() ); } catch ( PayPalApiException $exception ) { // Looks like currently there is no proper way to validate the shipping address for PayPal, @@ -354,17 +358,14 @@ class CreateOrderEndpoint implements EndpointInterface { ) ) { $this->logger->info( 'Invalid shipping address for order creation, retrying without it.' ); - foreach ( $this->purchase_units as $purchase_unit ) { - $purchase_unit->set_shipping( null ); - } + $this->purchase_unit->set_shipping( null ); return $this->api_endpoint->create( - $this->purchase_units, + array( $this->purchase_unit ), + $shipping_preference, $this->payer( $this->parsed_request_data, $wc_order ), null, - $this->payment_method(), - '', - $shipping_address_is_fix + $this->payment_method() ); } diff --git a/modules/ppcp-subscription/services.php b/modules/ppcp-subscription/services.php index b4f25c52d..026d26965 100644 --- a/modules/ppcp-subscription/services.php +++ b/modules/ppcp-subscription/services.php @@ -29,6 +29,7 @@ return array( $repository, $endpoint, $purchase_unit_factory, + $container->get( 'api.factory.shipping-preference' ), $payer_factory, $environment ); diff --git a/modules/ppcp-subscription/src/RenewalHandler.php b/modules/ppcp-subscription/src/RenewalHandler.php index 2103e443d..a7b458d0d 100644 --- a/modules/ppcp-subscription/src/RenewalHandler.php +++ b/modules/ppcp-subscription/src/RenewalHandler.php @@ -13,6 +13,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; 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\Vaulting\PaymentTokenRepository; use Psr\Log\LoggerInterface; @@ -58,6 +59,13 @@ class RenewalHandler { */ private $purchase_unit_factory; + /** + * The shipping_preference factory. + * + * @var ShippingPreferenceFactory + */ + private $shipping_preference_factory; + /** * The payer factory. * @@ -75,28 +83,31 @@ 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 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. */ public function __construct( LoggerInterface $logger, PaymentTokenRepository $repository, OrderEndpoint $order_endpoint, PurchaseUnitFactory $purchase_unit_factory, + ShippingPreferenceFactory $shipping_preference_factory, PayerFactory $payer_factory, 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->logger = $logger; + $this->repository = $repository; + $this->order_endpoint = $order_endpoint; + $this->purchase_unit_factory = $purchase_unit_factory; + $this->shipping_preference_factory = $shipping_preference_factory; + $this->payer_factory = $payer_factory; + $this->environment = $environment; } /** @@ -141,11 +152,16 @@ class RenewalHandler { if ( ! $token ) { return; } - $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); - $payer = $this->payer_factory->from_customer( $customer ); + $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, + 'renewal' + ); $order = $this->order_endpoint->create( array( $purchase_unit ), + $shipping_preference, $payer, $token ); diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 63a5a4814..4c590aeb0 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -87,6 +87,7 @@ return array( $page_id, $environment, $payment_token_repository, + $container->get( 'api.factory.shipping-preference' ), $logger, $payments_endpoint, $order_endpoint, @@ -123,6 +124,7 @@ return array( $transaction_url_provider, $payment_token_repository, $purchase_unit_factory, + $container->get( 'api.factory.shipping-preference' ), $payer_factory, $order_endpoint, $subscription_helper, diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index c39d03745..b11116878 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -14,6 +14,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint; 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; @@ -111,6 +112,13 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { */ private $purchase_unit_factory; + /** + * The shipping_preference factory. + * + * @var ShippingPreferenceFactory + */ + private $shipping_preference_factory; + /** * The payer factory. * @@ -167,6 +175,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { * @param TransactionUrlProvider $transaction_url_provider Service able to provide view transaction url base. * @param PaymentTokenRepository $payment_token_repository The payment token repository. * @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory. + * @param ShippingPreferenceFactory $shipping_preference_factory The shipping_preference factory. * @param PayerFactory $payer_factory The payer factory. * @param OrderEndpoint $order_endpoint The order endpoint. * @param SubscriptionHelper $subscription_helper The subscription helper. @@ -186,6 +195,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { TransactionUrlProvider $transaction_url_provider, PaymentTokenRepository $payment_token_repository, PurchaseUnitFactory $purchase_unit_factory, + ShippingPreferenceFactory $shipping_preference_factory, PayerFactory $payer_factory, OrderEndpoint $order_endpoint, SubscriptionHelper $subscription_helper, @@ -252,16 +262,17 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { ) ); - $this->module_url = $module_url; - $this->payment_token_repository = $payment_token_repository; - $this->purchase_unit_factory = $purchase_unit_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; + $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; } /** diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index 4af143dee..3fcd30456 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Gateway; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint; +use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Session\SessionHandler; @@ -118,6 +119,13 @@ class PayPalGateway extends \WC_Payment_Gateway { */ protected $payment_token_repository; + /** + * The shipping_preference factory. + * + * @var ShippingPreferenceFactory + */ + private $shipping_preference_factory; + /** * The payments endpoint * @@ -183,6 +191,7 @@ class PayPalGateway extends \WC_Payment_Gateway { * @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. @@ -202,6 +211,7 @@ 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, @@ -223,6 +233,7 @@ class PayPalGateway extends \WC_Payment_Gateway { $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; diff --git a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php index fa425c6f3..10e0e1313 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php +++ b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php @@ -82,9 +82,16 @@ trait ProcessPaymentTrait { $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 ); diff --git a/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php b/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php index d24087925..642d9c4b6 100644 --- a/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php +++ b/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php @@ -952,7 +952,7 @@ class OrderEndpointTest extends TestCase ->expects('email_address') ->andReturn(''); - $result = $testee->create([$purchaseUnit], $payer); + $result = $testee->create([$purchaseUnit], ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING, $payer); $this->assertEquals($expectedOrder, $result); } @@ -1049,7 +1049,7 @@ class OrderEndpointTest extends TestCase $payerName = Mockery::mock(PayerName::class); $payer->expects('name')->andReturn($payerName); $payer->expects('to_array')->andReturn(['payer']); - $result = $testee->create([$purchaseUnit], $payer); + $result = $testee->create([$purchaseUnit], ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE, $payer); $this->assertEquals($expectedOrder, $result); } @@ -1141,7 +1141,7 @@ class OrderEndpointTest extends TestCase $payerName = Mockery::mock(PayerName::class); $payer->expects('name')->andReturn($payerName); $payer->expects('to_array')->andReturn(['payer']); - $testee->create([$purchaseUnit], $payer); + $testee->create([$purchaseUnit], ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING, $payer); } public function testCreateForPurchaseUnitsIsNot201() @@ -1232,6 +1232,6 @@ class OrderEndpointTest extends TestCase $payerName = Mockery::mock(PayerName::class); $payer->expects('name')->andReturn($payerName); $payer->expects('to_array')->andReturn(['payer']); - $testee->create([$purchaseUnit], $payer); + $testee->create([$purchaseUnit], ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE, $payer); } } diff --git a/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php b/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php index 7b1132c40..9eb6c654b 100644 --- a/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php +++ b/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php @@ -5,7 +5,7 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit; -use WooCommerce\PayPalCommerce\ApiClient\Repository\CartRepository; +use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\TestCase; use Mockery; use WooCommerce\WooCommerce\Logging\Logger\NullLogger; @@ -17,7 +17,7 @@ class ChangeCartEndpointTest extends TestCase /** * @dataProvider dataForTestProducts */ - public function testProducts($data, $products, $lineItems, $responseExpectation) { + public function testProducts($data, $products, $responseExpectation) { $dataStore = Mockery::mock(\WC_Data_Store::class); $cart = Mockery::mock(\WC_Cart::class); @@ -59,16 +59,21 @@ class ChangeCartEndpointTest extends TestCase ->expects('read_request') ->with(ChangeCartEndpoint::nonce()) ->andReturn($data); - $cartRepository = Mockery::mock(CartRepository::class); - $cartRepository - ->expects('all') - ->andReturn($lineItems); + + $pu = Mockery::mock(PurchaseUnit::class); + $pu + ->shouldReceive('to_array') + ->andReturn($responseExpectation[0]); + $purchase_unit_factory = Mockery::mock(PurchaseUnitFactory::class); + $purchase_unit_factory + ->expects('from_wc_cart') + ->andReturn($pu); $testee = new ChangeCartEndpoint( $cart, $shipping, $requestData, - $cartRepository, + $purchase_unit_factory, $dataStore, new NullLogger() ); @@ -97,15 +102,6 @@ class ChangeCartEndpointTest extends TestCase ->with('variable') ->andReturn(true); - $defaultLineItem = Mockery::mock(PurchaseUnit::class); - $defaultLineItem - ->shouldReceive('to_array') - ->andReturn([1]); - $variationLineItem = Mockery::mock(PurchaseUnit::class); - $variationLineItem - ->shouldReceive('to_array') - ->andReturn([2]); - $testData = [ 'default' => [ [ @@ -120,9 +116,6 @@ class ChangeCartEndpointTest extends TestCase [ $defaultProduct, ], - [ - $defaultLineItem, - ], [ [1], ] @@ -162,11 +155,7 @@ class ChangeCartEndpointTest extends TestCase $variationProduct, ], [ - $defaultLineItem, - $variationLineItem, - ], - [ - [1],[2] + [1, 2] ] ] ]; diff --git a/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php b/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php index a6e0bbfb7..00d863a9e 100644 --- a/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php +++ b/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php @@ -10,7 +10,7 @@ use ReflectionClass; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; -use WooCommerce\PayPalCommerce\ApiClient\Repository\CartRepository; +use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\TestCase; @@ -145,7 +145,7 @@ class CreateOrderEndpointTest extends TestCase protected function mockTestee() { $request_data = Mockery::mock(RequestData::class); - $cart_repository = Mockery::mock(CartRepository::class); + $shippingPreferenceFactory = Mockery::mock(ShippingPreferenceFactory::class); $purchase_unit_factory = Mockery::mock(PurchaseUnitFactory::class); $order_endpoint = Mockery::mock(OrderEndpoint::class); $payer_factory = Mockery::mock(PayerFactory::class); @@ -155,8 +155,8 @@ class CreateOrderEndpointTest extends TestCase $testee = new CreateOrderEndpoint( $request_data, - $cart_repository, $purchase_unit_factory, + $shippingPreferenceFactory, $order_endpoint, $payer_factory, $session_handler, diff --git a/tests/PHPUnit/Subscription/RenewalHandlerTest.php b/tests/PHPUnit/Subscription/RenewalHandlerTest.php index 036e8fd85..de3249fd6 100644 --- a/tests/PHPUnit/Subscription/RenewalHandlerTest.php +++ b/tests/PHPUnit/Subscription/RenewalHandlerTest.php @@ -16,6 +16,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; +use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\TestCase; use Mockery; @@ -31,6 +32,7 @@ class RenewalHandlerTest extends TestCase private $repository; private $orderEndpoint; private $purchaseUnitFactory; + private $shippingPreferenceFactory; private $payerFactory; private $environment; private $sut; @@ -43,6 +45,7 @@ class RenewalHandlerTest extends TestCase $this->repository = Mockery::mock(PaymentTokenRepository::class); $this->orderEndpoint = Mockery::mock(OrderEndpoint::class); $this->purchaseUnitFactory = Mockery::mock(PurchaseUnitFactory::class); + $this->shippingPreferenceFactory = Mockery::mock(ShippingPreferenceFactory::class); $this->payerFactory = Mockery::mock(PayerFactory::class); $this->environment = new Environment(new Dictionary([])); @@ -56,6 +59,7 @@ class RenewalHandlerTest extends TestCase $this->repository, $this->orderEndpoint, $this->purchaseUnitFactory, + $this->shippingPreferenceFactory, $this->payerFactory, $this->environment ); @@ -133,8 +137,12 @@ class RenewalHandlerTest extends TestCase $this->payerFactory->shouldReceive('from_customer') ->andReturn($payer); + $this->shippingPreferenceFactory->shouldReceive('from_state') + ->with($purchaseUnit, 'renewal') + ->andReturn('no_shipping'); + $this->orderEndpoint->shouldReceive('create') - ->with([$purchaseUnit], $payer, $token) + ->with([$purchaseUnit], 'no_shipping', $payer, $token) ->andReturn($order); $wcOrder->shouldReceive('update_status'); diff --git a/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php index f410e0b16..b9b2a87cb 100644 --- a/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php +++ b/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php @@ -10,6 +10,7 @@ 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; @@ -44,6 +45,7 @@ class WcGatewayTest extends TestCase private $subscriptionHelper; private $environment; private $paymentTokenRepository; + private $shipping_preference_factory; private $logger; private $paymentsEndpoint; private $orderEndpoint; @@ -67,6 +69,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); @@ -102,6 +105,7 @@ class WcGatewayTest extends TestCase PayPalGateway::ID, $this->environment, $this->paymentTokenRepository, + $this->shipping_preference_factory, $this->logger, $this->paymentsEndpoint, $this->orderEndpoint, From 93778b15398e80958567ab883df1bdd8f03daabb Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 5 Jul 2022 15:49:59 +0200 Subject: [PATCH 017/145] 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 018/145] 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 9ab091b945a0d6c8f3b45d936d6c3a285b52b43a Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 5 Jul 2022 15:05:57 +0300 Subject: [PATCH 019/145] Fix shipping preference for vaulted card button --- .../src/Factory/ShippingPreferenceFactory.php | 12 +- .../src/Endpoint/CreateOrderEndpoint.php | 3 +- .../Factory/ShippingPreferenceFactoryTest.php | 118 ++++++++++++++++++ 3 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 tests/PHPUnit/ApiClient/Factory/ShippingPreferenceFactoryTest.php diff --git a/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php b/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php index 322d598fd..0b433d6c4 100644 --- a/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php +++ b/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php @@ -24,12 +24,14 @@ class ShippingPreferenceFactory { * @param PurchaseUnit $purchase_unit Thw PurchaseUnit. * @param string $context The operation context like 'checkout', 'cart'. * @param WC_Cart|null $cart The current cart if relevant. + * @param string $funding_source The funding source (PayPal button) like 'paypal', 'venmo', 'card'. * @return string */ public function from_state( PurchaseUnit $purchase_unit, string $context, - ?WC_Cart $cart = null + ?WC_Cart $cart = null, + string $funding_source = '' ): string { $contains_physical_goods = $purchase_unit->contains_physical_goods(); if ( ! $contains_physical_goods ) { @@ -49,6 +51,14 @@ class ShippingPreferenceFactory { return ApplicationContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS; } + if ( 'card' === $funding_source ) { + if ( ! $has_shipping ) { + return ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING; + } + // Looks like GET_FROM_FILE does not work for the vaulted card button. + return ApplicationContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS; + } + return ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE; } } diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index 9d3ed2d4e..c0045c8c4 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -334,7 +334,8 @@ class CreateOrderEndpoint implements EndpointInterface { $shipping_preference = $this->shipping_preference_factory->from_state( $this->purchase_unit, $this->parsed_request_data['context'], - WC()->cart + WC()->cart, + $this->parsed_request_data['funding_source'] ?? '' ); try { diff --git a/tests/PHPUnit/ApiClient/Factory/ShippingPreferenceFactoryTest.php b/tests/PHPUnit/ApiClient/Factory/ShippingPreferenceFactoryTest.php new file mode 100644 index 000000000..4da631f06 --- /dev/null +++ b/tests/PHPUnit/ApiClient/Factory/ShippingPreferenceFactoryTest.php @@ -0,0 +1,118 @@ +testee = new ShippingPreferenceFactory(); + } + + /** + * @dataProvider forStateData + */ + public function testFromState( + PurchaseUnit $purchase_unit, + string $context, + ?WC_Cart $cart, + string $funding_source, + string $expected_result + ) { + $result = $this->testee->from_state($purchase_unit, $context, $cart, $funding_source); + + self::assertEquals($expected_result, $result); + } + + public function forStateData() + { + yield [ + $this->createPurchaseUnit(true, Mockery::mock(Shipping::class)), + 'checkout', + $this->createCart(true), + '', + ApplicationContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS, + ]; + yield [ + $this->createPurchaseUnit(false, Mockery::mock(Shipping::class)), + 'checkout', + $this->createCart(false), + '', + ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING, + ]; + yield [ + $this->createPurchaseUnit(true, null), + 'checkout', + $this->createCart(true), + '', + ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING, + ]; + yield [ + $this->createPurchaseUnit(true, Mockery::mock(Shipping::class)), + 'checkout', + $this->createCart(true), + 'card', + ApplicationContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS, + ]; + yield [ + $this->createPurchaseUnit(true, null), + 'product', + null, + '', + ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE, + ]; + yield [ + $this->createPurchaseUnit(true, null), + 'pay-now', + null, + 'venmo', + ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE, + ]; + yield [ + $this->createPurchaseUnit(true, Mockery::mock(Shipping::class)), + 'pay-now', + null, + 'venmo', + ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE, + ]; + yield [ + $this->createPurchaseUnit(true, Mockery::mock(Shipping::class)), + 'pay-now', + null, + 'card', + ApplicationContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS, + ]; + yield [ + $this->createPurchaseUnit(true, null), + 'pay-now', + null, + 'card', + ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING, + ]; + } + + private function createPurchaseUnit(bool $containsPhysicalGoods, ?Shipping $shipping): PurchaseUnit { + $pu = Mockery::mock(PurchaseUnit::class); + $pu->shouldReceive('contains_physical_goods')->andReturn($containsPhysicalGoods); + $pu->shouldReceive('shipping')->andReturn($shipping); + return $pu; + } + + private function createCart(bool $needsShipping): WC_Cart { + $cart = Mockery::mock(WC_Cart::class); + $cart->shouldReceive('needs_shipping')->andReturn($needsShipping); + return $cart; + } +} From faa406fbd89aedd666d7498a8f1843ef5594ffa9 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 6 Jul 2022 10:35:53 +0200 Subject: [PATCH 020/145] 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 021/145] 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 82adf1933f9611e15ba350f777e9470d0dc42f90 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 7 Jul 2022 12:34:21 +0300 Subject: [PATCH 022/145] Refactor money value formatting Do not duplicate code and send string value for JPY --- modules/ppcp-api-client/src/Entity/Amount.php | 16 +++++---- modules/ppcp-api-client/src/Entity/Money.php | 23 ++++++++---- .../src/Helper/MoneyFormatter.php | 36 +++++++++++++++++++ tests/PHPUnit/ApiClient/Entity/AmountTest.php | 8 ++--- tests/e2e/PHPUnit/Order/PurchaseUnitTest.php | 19 ++++++++++ 5 files changed, 84 insertions(+), 18 deletions(-) create mode 100644 modules/ppcp-api-client/src/Helper/MoneyFormatter.php diff --git a/modules/ppcp-api-client/src/Entity/Amount.php b/modules/ppcp-api-client/src/Entity/Amount.php index 27ec10581..0b7c022c6 100644 --- a/modules/ppcp-api-client/src/Entity/Amount.php +++ b/modules/ppcp-api-client/src/Entity/Amount.php @@ -64,6 +64,15 @@ class Amount { return $this->money->value(); } + /** + * The value formatted as string for API requests. + * + * @return string + */ + public function value_str(): string { + return $this->money->value_str(); + } + /** * Returns the breakdown. * @@ -79,12 +88,7 @@ class Amount { * @return array */ public function to_array(): array { - $amount = array( - 'currency_code' => $this->currency_code(), - 'value' => in_array( $this->currency_code(), $this->currencies_without_decimals, true ) - ? round( $this->value(), 0 ) - : number_format( $this->value(), 2, '.', '' ), - ); + $amount = $this->money->to_array(); if ( $this->breakdown() && count( $this->breakdown()->to_array() ) ) { $amount['breakdown'] = $this->breakdown()->to_array(); } diff --git a/modules/ppcp-api-client/src/Entity/Money.php b/modules/ppcp-api-client/src/Entity/Money.php index f1f3f848f..5ba49dc43 100644 --- a/modules/ppcp-api-client/src/Entity/Money.php +++ b/modules/ppcp-api-client/src/Entity/Money.php @@ -9,6 +9,8 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient\Entity; +use WooCommerce\PayPalCommerce\ApiClient\Helper\MoneyFormatter; + /** * Class Money */ @@ -29,11 +31,11 @@ class Money { private $value; /** - * Currencies that does not support decimals. + * The MoneyFormatter. * - * @var array + * @var MoneyFormatter */ - private $currencies_without_decimals = array( 'HUF', 'JPY', 'TWD' ); + private $money_formatter; /** * Money constructor. @@ -44,6 +46,8 @@ class Money { public function __construct( float $value, string $currency_code ) { $this->value = $value; $this->currency_code = $currency_code; + + $this->money_formatter = new MoneyFormatter(); } /** @@ -55,6 +59,15 @@ class Money { return $this->value; } + /** + * The value formatted as string for API requests. + * + * @return string + */ + public function value_str(): string { + return $this->money_formatter->format( $this->value, $this->currency_code ); + } + /** * The currency code. * @@ -72,9 +85,7 @@ class Money { public function to_array(): array { return array( 'currency_code' => $this->currency_code(), - 'value' => in_array( $this->currency_code(), $this->currencies_without_decimals, true ) - ? round( $this->value(), 0 ) - : number_format( $this->value(), 2, '.', '' ), + 'value' => $this->value_str(), ); } } diff --git a/modules/ppcp-api-client/src/Helper/MoneyFormatter.php b/modules/ppcp-api-client/src/Helper/MoneyFormatter.php new file mode 100644 index 000000000..447ba0a0e --- /dev/null +++ b/modules/ppcp-api-client/src/Helper/MoneyFormatter.php @@ -0,0 +1,36 @@ +currencies_without_decimals, true ) + ? (string) round( $value, 0 ) + : number_format( $value, 2, '.', '' ); + } +} diff --git a/tests/PHPUnit/ApiClient/Entity/AmountTest.php b/tests/PHPUnit/ApiClient/Entity/AmountTest.php index 24d4152c9..96b7f0a1c 100644 --- a/tests/PHPUnit/ApiClient/Entity/AmountTest.php +++ b/tests/PHPUnit/ApiClient/Entity/AmountTest.php @@ -22,9 +22,7 @@ class AmountTest extends TestCase public function testBreakdownIsNull() { - $money = Mockery::mock(Money::class); - $money->shouldReceive('currency_code')->andReturn('currencyCode'); - $money->shouldReceive('value')->andReturn(1.10); + $money = new Money(1.10, 'currencyCode'); $testee = new Amount($money); $this->assertNull($testee->breakdown()); @@ -38,9 +36,7 @@ class AmountTest extends TestCase public function testBreakdown() { - $money = Mockery::mock(Money::class); - $money->shouldReceive('currency_code')->andReturn('currencyCode'); - $money->shouldReceive('value')->andReturn(1.10); + $money = new Money(1.10, 'currencyCode'); $breakdown = Mockery::mock(AmountBreakdown::class); $breakdown->shouldReceive('to_array')->andReturn([1]); $testee = new Amount($money, $breakdown); diff --git a/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php b/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php index 835088cbb..0dbf9485c 100644 --- a/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php +++ b/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php @@ -334,6 +334,25 @@ class PurchaseUnitTest extends TestCase ], ]), ]; + + yield 'no decimals currency' => [ + [ + 'currency' => 'JPY', + 'items' => [ + ['price' => 18.0, 'quantity' => 2], + ], + 'shipping' => ['total' => 5.0], + 'billing' => ['city' => 'city2'], + ], + self::adaptAmountFormat([ + 'value' => 66, + 'breakdown' => [ + 'item_total' => 36, + 'tax_total' => 25, // 24.60 + 'shipping' => 5, + ], + ], 'JPY'), + ]; } public function cartData() { From 2ca64739dba2ad30025bea36cb0bfb6941fe308c Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 7 Jul 2022 12:36:14 +0300 Subject: [PATCH 023/145] Use str value when checking mismatch Otherwise the check may fail depending on the rounding used when creating the Money objects --- .../src/Entity/PurchaseUnit.php | 26 ++++++++--------- .../ApiClient/Entity/PurchaseUnitTest.php | 11 +++----- tests/e2e/PHPUnit/Order/PurchaseUnitTest.php | 28 ++++++++++++++++--- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/modules/ppcp-api-client/src/Entity/PurchaseUnit.php b/modules/ppcp-api-client/src/Entity/PurchaseUnit.php index 4f4c8b785..fd7973738 100644 --- a/modules/ppcp-api-client/src/Entity/PurchaseUnit.php +++ b/modules/ppcp-api-client/src/Entity/PurchaseUnit.php @@ -331,9 +331,9 @@ class PurchaseUnit { $remaining_item_total = array_reduce( $items, function ( float $total, Item $item ): float { - return $total - $item->unit_amount()->value() * (float) $item->quantity(); + return $total - (float) $item->unit_amount()->value_str() * (float) $item->quantity(); }, - $item_total->value() + (float) $item_total->value_str() ); $remaining_item_total = round( $remaining_item_total, 2 ); @@ -356,11 +356,11 @@ class PurchaseUnit { function ( float $total, Item $item ): float { $tax = $item->tax(); if ( $tax ) { - $total -= $tax->value() * (float) $item->quantity(); + $total -= (float) $tax->value_str() * (float) $item->quantity(); } return $total; }, - $tax_total->value() + (float) $tax_total->value_str() ); $remaining_tax_total = round( $remaining_tax_total, 2 ); @@ -378,29 +378,29 @@ class PurchaseUnit { $amount_total = 0.0; if ( $shipping ) { - $amount_total += $shipping->value(); + $amount_total += (float) $shipping->value_str(); } if ( $item_total ) { - $amount_total += $item_total->value(); + $amount_total += (float) $item_total->value_str(); } if ( $discount ) { - $amount_total -= $discount->value(); + $amount_total -= (float) $discount->value_str(); } if ( $tax_total ) { - $amount_total += $tax_total->value(); + $amount_total += (float) $tax_total->value_str(); } if ( $shipping_discount ) { - $amount_total -= $shipping_discount->value(); + $amount_total -= (float) $shipping_discount->value_str(); } if ( $handling ) { - $amount_total += $handling->value(); + $amount_total += (float) $handling->value_str(); } if ( $insurance ) { - $amount_total += $insurance->value(); + $amount_total += (float) $insurance->value_str(); } - $amount_str = (string) $amount->to_array()['value']; - $amount_total_str = (string) ( new Money( $amount_total, $amount->currency_code() ) )->to_array()['value']; + $amount_str = $amount->value_str(); + $amount_total_str = ( new Money( $amount_total, $amount->currency_code() ) )->value_str(); $needs_to_ditch = $amount_str !== $amount_total_str; return $needs_to_ditch; } diff --git a/tests/PHPUnit/ApiClient/Entity/PurchaseUnitTest.php b/tests/PHPUnit/ApiClient/Entity/PurchaseUnitTest.php index 0aadf968a..dbb18c4de 100644 --- a/tests/PHPUnit/ApiClient/Entity/PurchaseUnitTest.php +++ b/tests/PHPUnit/ApiClient/Entity/PurchaseUnitTest.php @@ -412,10 +412,8 @@ class PurchaseUnitTest extends TestCase foreach ($data as $testKey => $test) { $items = []; foreach ($test['items'] as $key => $item) { - $unitAmount = Mockery::mock(Money::class); - $unitAmount->shouldReceive('value')->andReturn($item['value']); - $tax = Mockery::mock(Money::class); - $tax->shouldReceive('value')->andReturn($item['tax']); + $unitAmount = new Money($item['value'], 'EUR'); + $tax = new Money($item['tax'], 'EUR'); $items[$key] = Mockery::mock( Item::class, [ @@ -436,15 +434,14 @@ class PurchaseUnitTest extends TestCase return null; } - $money = Mockery::mock(Money::class); - $money->shouldReceive('value')->andReturn($value); + $money = new Money($value, 'EUR'); return $money; }); } } $amount = Mockery::mock(Amount::class); $amount->shouldReceive('to_array')->andReturn(['value' => number_format( $test['amount'], 2, '.', '' ), 'breakdown' => []]); - $amount->shouldReceive('value')->andReturn($test['amount']); + $amount->shouldReceive('value_str')->andReturn(number_format( $test['amount'], 2, '.', '' )); $amount->shouldReceive('currency_code')->andReturn('EUR'); $amount->shouldReceive('breakdown')->andReturn($breakdown); diff --git a/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php b/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php index 0dbf9485c..655830693 100644 --- a/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php +++ b/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php @@ -68,8 +68,6 @@ class PurchaseUnitTest extends TestCase $pu = $this->puFactory->from_wc_order($wcOrder); $puData = $pu->to_array(); - self::assertTrue(isset($puData['amount']['breakdown'])); - self::assertEquals($expectedAmount, $puData['amount']); } @@ -83,8 +81,6 @@ class PurchaseUnitTest extends TestCase $pu = $this->puFactory->from_wc_cart($this->cart); $puData = $pu->to_array(); - self::assertTrue(isset($puData['amount']['breakdown'])); - self::assertEquals($expectedAmount, $puData['amount']); } @@ -353,6 +349,18 @@ class PurchaseUnitTest extends TestCase ], ], 'JPY'), ]; + + yield [ + [ + 'items' => [ + ['price' => 5.345, 'quantity' => 2], + ], + 'billing' => ['city' => 'city0'], + ], + self::adaptAmountFormat([ + 'value' => 10.69, + ]), + ]; } public function cartData() { @@ -409,6 +417,18 @@ class PurchaseUnitTest extends TestCase ], ], get_woocommerce_currency()), ]; + + yield [ + [ + 'products' => [ + ['price' => 5.345, 'quantity' => 2], + ], + 'billing' => ['city' => 'city0'], + ], + self::adaptAmountFormat([ + 'value' => 10.69, + ], get_woocommerce_currency()), + ]; } private static function adaptAmountFormat(array $data, string $currency = null): array { From e7a4522f8d34ed0a069d47bc6abb832f952a7aa6 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 8 Jul 2022 10:53:35 +0200 Subject: [PATCH 024/145] Bump 1.9.1 version --- changelog.txt | 9 +++++++++ package.json | 2 +- readme.txt | 11 ++++++++++- woocommerce-paypal-payments.php | 2 +- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/changelog.txt b/changelog.txt index 70909097d..72fd09378 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,14 @@ *** Changelog *** += 1.9.1 - TBD = +* 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 +* Enhancement - Missing PayPal fee in WC order details for PUI purchase #714 + = 1.9.0 - 2022-07-04 = * Add - New Feature - Pay Upon Invoice (Germany only) #608 * Fix - Order not approved: payment via vaulted PayPal account fails #677 diff --git a/package.json b/package.json index 541f35f98..f9b601973 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "woocommerce-paypal-payments", - "version": "1.9.0", + "version": "1.9.1", "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 63e9e8361..d1b6b3339 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.0 +Stable tag: 1.9.1 License: GPLv2 License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -81,6 +81,15 @@ Follow the steps below to connect the plugin to your PayPal account: == Changelog == += 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 +* 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 +* Enhancement - Missing PayPal fee in WC order details for PUI purchase #714 + = 1.9.0 = * Add - New Feature - Pay Upon Invoice (Germany only) #608 * Fix - Order not approved: payment via vaulted PayPal account fails #677 diff --git a/woocommerce-paypal-payments.php b/woocommerce-paypal-payments.php index 4aa932a36..38817fd03 100644 --- a/woocommerce-paypal-payments.php +++ b/woocommerce-paypal-payments.php @@ -3,7 +3,7 @@ * 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.0 + * Version: 1.9.1 * Author: WooCommerce * Author URI: https://woocommerce.com/ * License: GPL-2.0 From 31823216e9835d64d80aa9744415a89c77c90b45 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 8 Jul 2022 11:54:23 +0200 Subject: [PATCH 025/145] Load pui js script only in chekcout page --- .../Gateway/PayUponInvoice/PayUponInvoice.php | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index 628ae89e6..f8a627616 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -11,11 +11,6 @@ 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\ApiClient\Factory\CaptureFactory; use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException; @@ -518,21 +513,23 @@ class PayUponInvoice { * Registers PUI assets. */ public function register_assets(): void { - wp_enqueue_script( - 'ppcp-pay-upon-invoice', - trailingslashit( $this->module_url ) . 'assets/js/pay-upon-invoice.js', - array(), - $this->asset_version - ); + if ( is_checkout() || is_checkout_pay_page() ) { + wp_enqueue_script( + 'ppcp-pay-upon-invoice', + trailingslashit( $this->module_url ) . 'assets/js/pay-upon-invoice.js', + array(), + $this->asset_version + ); - wp_localize_script( - 'ppcp-pay-upon-invoice', - 'FraudNetConfig', - array( - 'f' => $this->fraud_net->session_id(), - 's' => $this->fraud_net->source_website_id(), - 'sandbox' => $this->environment->current_environment_is( Environment::SANDBOX ), - ) - ); + wp_localize_script( + 'ppcp-pay-upon-invoice', + 'FraudNetConfig', + array( + 'f' => $this->fraud_net->session_id(), + 's' => $this->fraud_net->source_website_id(), + 'sandbox' => $this->environment->current_environment_is( Environment::SANDBOX ), + ) + ); + } } } From 37c608474bdacdfa3c35c576ea93197ca1921235 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 8 Jul 2022 12:01:19 +0200 Subject: [PATCH 026/145] Make pui capitalizacion consistent --- .../ppcp-onboarding/src/Endpoint/PayUponInvoiceEndpoint.php | 2 +- .../src/Render/OnboardingOptionsRenderer.php | 2 +- modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php | 2 +- .../src/Gateway/PayUponInvoice/PayUponInvoice.php | 2 +- .../src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php | 6 +++--- modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-onboarding/src/Endpoint/PayUponInvoiceEndpoint.php b/modules/ppcp-onboarding/src/Endpoint/PayUponInvoiceEndpoint.php index 5f81ca8a7..695e54b83 100644 --- a/modules/ppcp-onboarding/src/Endpoint/PayUponInvoiceEndpoint.php +++ b/modules/ppcp-onboarding/src/Endpoint/PayUponInvoiceEndpoint.php @@ -1,6 +1,6 @@ '; } diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index 3fcd30456..1aabbfc5a 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -364,7 +364,7 @@ class PayPalGateway extends \WC_Payment_Gateway { return __( 'PayPal Checkout', 'woocommerce-paypal-payments' ); } if ( $this->is_pui_tab() ) { - return __( 'Pay Upon Invoice', 'woocommerce-paypal-payments' ); + return __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ); } return __( 'PayPal', '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 628ae89e6..10fdd2484 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -418,7 +418,7 @@ class PayUponInvoice { printf( '

%1$s

', - esc_html__( 'Could not enable gateway because the connected PayPal account is not activated for Pay upon Invoice. Reconnect your account while Onboard with Pay Upon Invoice is selected to try again.', 'woocommerce-paypal-payments' ) + esc_html__( 'Could not enable gateway because the connected PayPal account is not activated for Pay upon Invoice. Reconnect your account while Onboard with Pay upon Invoice is selected to try again.', '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..fe9d53d38 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php @@ -103,7 +103,7 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { ) { $this->id = self::ID; - $this->method_title = __( 'Pay Upon Invoice', 'woocommerce-paypal-payments' ); + $this->method_title = __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ); $this->method_description = __( 'Pay upon Invoice is an invoice payment method in Germany. It is a local buy now, pay later payment method that allows the buyer to place an order, receive the goods, try them, verify they are in good order, and then pay the invoice within 30 days.', 'woocommerce-paypal-payments' ); $gateway_settings = get_option( 'woocommerce_ppcp-pay-upon-invoice-gateway_settings' ); @@ -141,7 +141,7 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { 'label' => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ), 'default' => 'no', 'desc_tip' => true, - 'description' => __( 'Enable/Disable Pay Upon Invoice payment gateway.', 'woocommerce-paypal-payments' ), + 'description' => __( 'Enable/Disable Pay upon Invoice payment gateway.', 'woocommerce-paypal-payments' ), ), 'title' => array( 'title' => __( 'Title', 'woocommerce-paypal-payments' ), @@ -206,7 +206,7 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { } } - $wc_order->update_status( 'on-hold', __( 'Awaiting Pay Upon Invoice payment.', 'woocommerce-paypal-payments' ) ); + $wc_order->update_status( 'on-hold', __( 'Awaiting Pay upon Invoice payment.', 'woocommerce-paypal-payments' ) ); $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); $payment_source = $this->payment_source_factory->from_wc_order( $wc_order, $birth_date ); diff --git a/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php b/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php index 049038e48..24faa5201 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php +++ b/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php @@ -66,7 +66,7 @@ class SectionsRenderer { $sections = array( PayPalGateway::ID => __( 'PayPal Checkout', 'woocommerce-paypal-payments' ), CreditCardGateway::ID => __( 'PayPal Card Processing', 'woocommerce-paypal-payments' ), - PayUponInvoiceGateway::ID => __( 'Pay Upon Invoice', 'woocommerce-paypal-payments' ), + PayUponInvoiceGateway::ID => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ), WebhooksStatusPage::ID => __( 'Webhooks Status', 'woocommerce-paypal-payments' ), ); From be219340768643eac2c428374a3be97f120168e4 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 8 Jul 2022 14:08:05 +0200 Subject: [PATCH 027/145] Update changelog --- changelog.txt | 2 ++ readme.txt | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 72fd09378..10b2f8a1f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -8,6 +8,8 @@ * Fix - Placeholders and card type detection not working for PayPal Card Processing (260) #685 * Fix - PUI gateway is displayed with unsupported store currency #711 * 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 = 1.9.0 - 2022-07-04 = * Add - New Feature - Pay Upon Invoice (Germany only) #608 diff --git a/readme.txt b/readme.txt index d1b6b3339..2cc995602 100644 --- a/readme.txt +++ b/readme.txt @@ -88,7 +88,9 @@ Follow the steps below to connect the plugin to your PayPal account: * 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 -* 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 = 1.9.0 = * Add - New Feature - Pay Upon Invoice (Germany only) #608 From 1336180fd9f0052ddcca7b6bfcfb361c595e7685 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 8 Jul 2022 19:52:22 +0400 Subject: [PATCH 028/145] 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 e73ebbadb66659c99a741f51578b9b30de9ed0b8 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 11 Jul 2022 09:19:27 +0300 Subject: [PATCH 029/145] Fix phpcs warnings --- modules/ppcp-subscription/src/RenewalHandler.php | 2 +- .../src/Gateway/PayUponInvoice/FraudNetSessionId.php | 1 + .../src/Gateway/PayUponInvoice/PayUponInvoice.php | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-subscription/src/RenewalHandler.php b/modules/ppcp-subscription/src/RenewalHandler.php index a7b458d0d..eebd76533 100644 --- a/modules/ppcp-subscription/src/RenewalHandler.php +++ b/modules/ppcp-subscription/src/RenewalHandler.php @@ -144,7 +144,7 @@ class RenewalHandler { * * @throws \Exception If customer cannot be read/found. */ - private function process_order( \WC_Order $wc_order ) { + private function process_order( \WC_Order $wc_order ): void { $user_id = (int) $wc_order->get_customer_id(); $customer = new \WC_Customer( $user_id ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/FraudNetSessionId.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/FraudNetSessionId.php index 966b5733f..358c50958 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/FraudNetSessionId.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/FraudNetSessionId.php @@ -31,6 +31,7 @@ class FraudNetSessionId { return WC()->session->get( 'ppcp_fraudnet_session_id' ); } + // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['pay_for_order'] ) && 'true' === $_GET['pay_for_order'] ) { $pui_pay_for_order_session_id = filter_input( INPUT_POST, 'pui_pay_for_order_session_id', FILTER_SANITIZE_STRING ); if ( $pui_pay_for_order_session_id && '' !== $pui_pay_for_order_session_id ) { diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index a9094607e..16bdbb3e8 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -518,7 +518,8 @@ class PayUponInvoice { 'ppcp-pay-upon-invoice', trailingslashit( $this->module_url ) . 'assets/js/pay-upon-invoice.js', array(), - $this->asset_version + $this->asset_version, + true ); wp_localize_script( From d699ba61bf82a5bbfeadd7b97d6746cf1cf20b69 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 11 Jul 2022 09:34:40 +0200 Subject: [PATCH 030/145] Check gateway enabled to load pui js --- .../src/Gateway/PayUponInvoice/PayUponInvoice.php | 4 +++- 1 file changed, 3 insertions(+), 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 16bdbb3e8..87f89997a 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -513,7 +513,9 @@ class PayUponInvoice { * Registers PUI assets. */ public function register_assets(): void { - if ( is_checkout() || is_checkout_pay_page() ) { + $gateway_settings = get_option( 'woocommerce_ppcp-pay-upon-invoice-gateway_settings' ); + $gateway_enabled = $gateway_settings['enabled'] ?? ''; + if ( $gateway_enabled === 'yes' && ( is_checkout() || is_checkout_pay_page() ) ) { wp_enqueue_script( 'ppcp-pay-upon-invoice', trailingslashit( $this->module_url ) . 'assets/js/pay-upon-invoice.js', From cbe10b5f6efda082f7d620f5028501299ac6aba2 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 11 Jul 2022 12:30:29 +0200 Subject: [PATCH 031/145] 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 032/145] 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 033/145] 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 034/145] 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 035/145] 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 036/145] 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 037/145] 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 038/145] 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 039/145] 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 040/145] 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 041/145] 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 042/145] 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 043/145] 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 044/145] 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 045/145] 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 046/145] 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 047/145] 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 048/145] 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 049/145] 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 050/145] 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 051/145] 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 052/145] 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 053/145] 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 054/145] 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 055/145] 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 056/145] 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 057/145] 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 058/145] 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 059/145] 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 060/145] 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 061/145] 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 062/145] 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 063/145] 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 064/145] 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 e2e01220e829394256dd3524ce81580ec65f8f17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Jul 2022 07:51:24 +0000 Subject: [PATCH 065/145] Bump terser from 5.9.0 to 5.14.2 in /modules/ppcp-button Bumps [terser](https://github.com/terser/terser) from 5.9.0 to 5.14.2. - [Release notes](https://github.com/terser/terser/releases) - [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md) - [Commits](https://github.com/terser/terser/commits) --- updated-dependencies: - dependency-name: terser dependency-type: indirect ... Signed-off-by: dependabot[bot] --- modules/ppcp-button/yarn.lock | 68 ++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/modules/ppcp-button/yarn.lock b/modules/ppcp-button/yarn.lock index f7f1da65e..e34a77cfb 100644 --- a/modules/ppcp-button/yarn.lock +++ b/modules/ppcp-button/yarn.lock @@ -873,6 +873,46 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3" integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA== +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" + integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@types/eslint-scope@^3.7.0": version "3.7.1" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.1.tgz#8dc390a7b4f9dd9f1284629efce982e41612116e" @@ -1057,10 +1097,10 @@ acorn-import-assertions@^1.7.6: resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78" integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA== -acorn@^8.4.1: - version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" - integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== +acorn@^8.4.1, acorn@^8.5.0: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== ajv-keywords@^3.5.2: version "3.5.2" @@ -2017,9 +2057,9 @@ signal-exit@^3.0.3: integrity sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q== source-map-support@~0.5.20: - version "0.5.20" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" - integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -2034,11 +2074,6 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@~0.7.2: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" @@ -2076,12 +2111,13 @@ terser-webpack-plugin@^5.1.3: terser "^5.7.2" terser@^5.7.2: - version "5.9.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.9.0.tgz#47d6e629a522963240f2b55fcaa3c99083d2c351" - integrity sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ== + version "5.14.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10" + integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA== dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" commander "^2.20.0" - source-map "~0.7.2" source-map-support "~0.5.20" to-fast-properties@^2.0.0: From b83f1fe3a4df19941b8911531c8895db21dd492a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Jul 2022 07:53:12 +0000 Subject: [PATCH 066/145] Bump terser from 5.9.0 to 5.14.2 in /modules/ppcp-wc-gateway Bumps [terser](https://github.com/terser/terser) from 5.9.0 to 5.14.2. - [Release notes](https://github.com/terser/terser/releases) - [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md) - [Commits](https://github.com/terser/terser/commits) --- updated-dependencies: - dependency-name: terser dependency-type: indirect ... Signed-off-by: dependabot[bot] --- modules/ppcp-wc-gateway/yarn.lock | 68 +++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/modules/ppcp-wc-gateway/yarn.lock b/modules/ppcp-wc-gateway/yarn.lock index 286b42eb8..d0b7c5df1 100644 --- a/modules/ppcp-wc-gateway/yarn.lock +++ b/modules/ppcp-wc-gateway/yarn.lock @@ -873,6 +873,46 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3" integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA== +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" + integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@types/eslint-scope@^3.7.0": version "3.7.1" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.1.tgz#8dc390a7b4f9dd9f1284629efce982e41612116e" @@ -1057,10 +1097,10 @@ acorn-import-assertions@^1.7.6: resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78" integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA== -acorn@^8.4.1: - version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" - integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== +acorn@^8.4.1, acorn@^8.5.0: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== ajv-keywords@^3.5.2: version "3.5.2" @@ -1902,9 +1942,9 @@ signal-exit@^3.0.3: integrity sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q== source-map-support@~0.5.20: - version "0.5.20" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" - integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -1919,11 +1959,6 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@~0.7.2: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" @@ -1961,12 +1996,13 @@ terser-webpack-plugin@^5.1.3: terser "^5.7.2" terser@^5.7.2: - version "5.9.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.9.0.tgz#47d6e629a522963240f2b55fcaa3c99083d2c351" - integrity sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ== + version "5.14.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10" + integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA== dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" commander "^2.20.0" - source-map "~0.7.2" source-map-support "~0.5.20" to-fast-properties@^2.0.0: From d9a7369d1907cb83614700fcf920d3d0659d67b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Jul 2022 08:10:57 +0000 Subject: [PATCH 067/145] Bump terser from 5.9.0 to 5.14.2 in /modules/ppcp-webhooks Bumps [terser](https://github.com/terser/terser) from 5.9.0 to 5.14.2. - [Release notes](https://github.com/terser/terser/releases) - [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md) - [Commits](https://github.com/terser/terser/commits) --- updated-dependencies: - dependency-name: terser dependency-type: indirect ... Signed-off-by: dependabot[bot] --- modules/ppcp-webhooks/yarn.lock | 68 +++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/modules/ppcp-webhooks/yarn.lock b/modules/ppcp-webhooks/yarn.lock index f7f1da65e..e34a77cfb 100644 --- a/modules/ppcp-webhooks/yarn.lock +++ b/modules/ppcp-webhooks/yarn.lock @@ -873,6 +873,46 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3" integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA== +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" + integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@types/eslint-scope@^3.7.0": version "3.7.1" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.1.tgz#8dc390a7b4f9dd9f1284629efce982e41612116e" @@ -1057,10 +1097,10 @@ acorn-import-assertions@^1.7.6: resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78" integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA== -acorn@^8.4.1: - version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" - integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== +acorn@^8.4.1, acorn@^8.5.0: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== ajv-keywords@^3.5.2: version "3.5.2" @@ -2017,9 +2057,9 @@ signal-exit@^3.0.3: integrity sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q== source-map-support@~0.5.20: - version "0.5.20" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" - integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -2034,11 +2074,6 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@~0.7.2: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" @@ -2076,12 +2111,13 @@ terser-webpack-plugin@^5.1.3: terser "^5.7.2" terser@^5.7.2: - version "5.9.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.9.0.tgz#47d6e629a522963240f2b55fcaa3c99083d2c351" - integrity sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ== + version "5.14.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10" + integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA== dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" commander "^2.20.0" - source-map "~0.7.2" source-map-support "~0.5.20" to-fast-properties@^2.0.0: From 64ad36073b6efc0aa68fd40cb3bd3ae256ff3010 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Jul 2022 08:11:16 +0000 Subject: [PATCH 068/145] Bump terser from 5.9.0 to 5.14.2 in /modules/ppcp-vaulting Bumps [terser](https://github.com/terser/terser) from 5.9.0 to 5.14.2. - [Release notes](https://github.com/terser/terser/releases) - [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md) - [Commits](https://github.com/terser/terser/commits) --- updated-dependencies: - dependency-name: terser dependency-type: indirect ... Signed-off-by: dependabot[bot] --- modules/ppcp-vaulting/yarn.lock | 68 +++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/modules/ppcp-vaulting/yarn.lock b/modules/ppcp-vaulting/yarn.lock index 0fe3ea117..8c98fce6a 100644 --- a/modules/ppcp-vaulting/yarn.lock +++ b/modules/ppcp-vaulting/yarn.lock @@ -873,6 +873,46 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3" integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA== +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" + integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@types/eslint-scope@^3.7.0": version "3.7.1" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.1.tgz#8dc390a7b4f9dd9f1284629efce982e41612116e" @@ -1057,10 +1097,10 @@ acorn-import-assertions@^1.7.6: resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78" integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA== -acorn@^8.4.1: - version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" - integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== +acorn@^8.4.1, acorn@^8.5.0: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== ajv-keywords@^3.5.2: version "3.5.2" @@ -2017,9 +2057,9 @@ signal-exit@^3.0.3: integrity sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q== source-map-support@~0.5.20: - version "0.5.20" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" - integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -2034,11 +2074,6 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@~0.7.2: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" @@ -2076,12 +2111,13 @@ terser-webpack-plugin@^5.1.3: terser "^5.7.2" terser@^5.7.2: - version "5.9.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.9.0.tgz#47d6e629a522963240f2b55fcaa3c99083d2c351" - integrity sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ== + version "5.14.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10" + integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA== dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" commander "^2.20.0" - source-map "~0.7.2" source-map-support "~0.5.20" to-fast-properties@^2.0.0: From e1cd7d5ad18adcbcc10027fe86c8047f4b7df0f8 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 20 Jul 2022 11:37:28 +0200 Subject: [PATCH 069/145] 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 070/145] 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 071/145] 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 072/145] 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 073/145] 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 074/145] 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 075/145] 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 076/145] 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 4de7321d30e5c32fb0b044c78f041b9b0c53eef7 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Thu, 21 Jul 2022 11:45:39 +0200 Subject: [PATCH 077/145] Fix wrong locale for PUI experience context --- .../src/Gateway/PayUponInvoice/PaymentSourceFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php index d4dc82562..40bc9a15f 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php @@ -50,7 +50,7 @@ class PaymentSourceFactory { $address['city'] ?? '', $address['postcode'] ?? '', $address['country'] ?? '', - 'en-DE', + 'de-DE', $merchant_name, $logo_url, array( $customer_service_instructions ) From 8b450764127591f9256e99148f783e6a8aa56b26 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Thu, 21 Jul 2022 14:15:25 +0200 Subject: [PATCH 078/145] Add phone field to pui when is removed from checkout --- .../Gateway/PayUponInvoice/PayUponInvoice.php | 20 +++++++++++++++++++ .../PayUponInvoice/PaymentSourceFactory.php | 6 +++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index 87f89997a..6d8f5ebff 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -322,6 +322,26 @@ class PayUponInvoice { ) ); + $checkout_fields = WC()->checkout()->get_checkout_fields(); + if ( ! array_key_exists( 'billing_phone', $checkout_fields['billing'] ) ) { + woocommerce_form_field( + 'billing_phone', + array( + /** + * Use translation from WooCommerce here. + * phpcs:disable WordPress.WP.I18n.TextDomainMismatch + */ + 'label' => __( 'Phone', 'woocommerce' ), + // phpcs:enable WordPress.WP.I18n.TextDomainMismatch + 'type' => 'tel', + 'class' => array( 'form-row-wide' ), + 'validate' => array( 'phone' ), + 'autocomplete' => 'tel', + 'required' => true, + ) + ); + } + echo '
    '; // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php index d4dc82562..a85251f15 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php @@ -24,8 +24,8 @@ class PaymentSourceFactory { * @return PaymentSource */ public function from_wc_order( WC_Order $order, string $birth_date ) { - $address = $order->get_address(); - + $address = $order->get_address(); + $phone = filter_input( INPUT_POST, 'billing_phone', FILTER_SANITIZE_STRING ) ?? $address['phone'] ?? ''; $phone_country_code = WC()->countries->get_country_calling_code( $address['country'] ); $phone_country_code = is_array( $phone_country_code ) && ! empty( $phone_country_code ) ? $phone_country_code[0] : $phone_country_code; if ( is_string( $phone_country_code ) && '' !== $phone_country_code ) { @@ -44,7 +44,7 @@ class PaymentSourceFactory { $address['last_name'] ?? '', $address['email'] ?? '', $birth_date, - preg_replace( '/[^0-9]/', '', $address['phone'] ) ?? '', + preg_replace( '/[^0-9]/', '', $phone ) ?? '', $phone_country_code, $address['address_1'] ?? '', $address['city'] ?? '', From dd8a1d28beb4488e277f7fec3d4d521ca45089a7 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Thu, 21 Jul 2022 14:16:44 +0200 Subject: [PATCH 079/145] Update changelog --- changelog.txt | 1 + readme.txt | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 10b2f8a1f..6879bff12 100644 --- a/changelog.txt +++ b/changelog.txt @@ -7,6 +7,7 @@ * 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 - 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 - Skip loading of PUI js file on all pages where PUI gateway is not displayed #723 * Enhancement - PUI feature capitalization not consistent #724 diff --git a/readme.txt b/readme.txt index 2cc995602..dec5134eb 100644 --- a/readme.txt +++ b/readme.txt @@ -87,7 +87,8 @@ Follow the steps below to connect the plugin to your PayPal account: * 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 - Skip loading of PUI js file on all pages where PUI gateway is not displayed #723 * Enhancement - PUI feature capitalization not consistent #724 From b7199dbbbf8e34d0b9111b6645fce2048884f71b Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 21 Jul 2022 15:25:30 +0300 Subject: [PATCH 080/145] 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 081/145] 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 082/145] 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 083/145] 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 084/145] 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 085/145] 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 bad4ed3a64ccb5fcf906cc693b72167ac3a3a7dd Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 22 Jul 2022 11:24:10 +0200 Subject: [PATCH 086/145] Check order currency on pay for order --- .../src/Helper/PayUponInvoiceHelper.php | 22 ++++++++++++++++++- 1 file changed, 21 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..2b6e4f534 100644 --- a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php +++ b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php @@ -86,7 +86,7 @@ class PayUponInvoiceHelper { return false; } - if ( 'EUR' !== get_woocommerce_currency() ) { + if ( ! $this->is_valid_currency() ) { return false; } @@ -137,4 +137,24 @@ class PayUponInvoiceHelper { return true; } + + /** + * Checks if currency is allowed for PUI. + * + * @return bool + */ + private function is_valid_currency(): bool { + global $wp; + $order_id = (int) $wp->query_vars['order-pay']; + if ( 0 === $order_id ) { + return 'EUR' === get_woocommerce_currency(); + } + + $order = wc_get_order( $order_id ); + if ( is_a( $order, WC_Order::class ) ) { + return 'EUR' === $order->get_currency(); + } + + return false; + } } From 55d57b59ce1d864238030759183ce36c82af2de6 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 22 Jul 2022 12:18:17 +0200 Subject: [PATCH 087/145] Ensure `order-pay` exists on query vars --- modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php index 2b6e4f534..8c1125348 100644 --- a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php +++ b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php @@ -145,7 +145,7 @@ class PayUponInvoiceHelper { */ private function is_valid_currency(): bool { global $wp; - $order_id = (int) $wp->query_vars['order-pay']; + $order_id = isset( $wp->query_vars['order-pay'] ) ? (int) $wp->query_vars['order-pay'] : 0; if ( 0 === $order_id ) { return 'EUR' === get_woocommerce_currency(); } From 55b2adf2aa2f449315cb8b27630fa0bc153f6e3e Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 25 Jul 2022 15:47:38 +0300 Subject: [PATCH 088/145] 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 089/145] 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 090/145] 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 091/145] 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 092/145] 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 093/145] 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 094/145] 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 095/145] 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 096/145] 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 097/145] 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 098/145] 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 099/145] 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 100/145] 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 101/145] 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 102/145] 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 103/145] 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 104/145] 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 105/145] 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 106/145] 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 107/145] 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 108/145] 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 109/145] 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 110/145] 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 111/145] 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 112/145] 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 ca6f3dd088674e3a7efe5aac13804babb11dc790 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 2 Aug 2022 16:58:47 +0200 Subject: [PATCH 113/145] Fix psalm --- .../src/Gateway/PayUponInvoice/PaymentSourceFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php index c7faeeff2..5e13ba0a8 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php @@ -25,7 +25,7 @@ class PaymentSourceFactory { */ public function from_wc_order( WC_Order $order, string $birth_date ) { $address = $order->get_address(); - $phone = filter_input( INPUT_POST, 'billing_phone', FILTER_SANITIZE_STRING ) ?? $address['phone'] ?? ''; + $phone = filter_input( INPUT_POST, 'billing_phone', FILTER_SANITIZE_STRING ) ?? $address['phone'] ?: ''; $phone_country_code = WC()->countries->get_country_calling_code( $address['country'] ); $phone_country_code = is_array( $phone_country_code ) && ! empty( $phone_country_code ) ? $phone_country_code[0] : $phone_country_code; if ( is_string( $phone_country_code ) && '' !== $phone_country_code ) { From 40365c5afbeb913ec05c26200390e55beece00ad Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 3 Aug 2022 09:37:40 +0300 Subject: [PATCH 114/145] Do not show card button gateway if paypal gateway disabled --- modules/ppcp-wc-gateway/src/WCGatewayModule.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index d01287f67..ac052b54c 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -292,7 +292,12 @@ class WCGatewayModule implements ModuleInterface { add_filter( 'woocommerce_payment_gateways', static function ( $methods ) use ( $container ): array { - $methods[] = $container->get( 'wcgateway.paypal-gateway' ); + $paypal_gateway = $container->get( 'wcgateway.paypal-gateway' ); + assert( $paypal_gateway instanceof \WC_Payment_Gateway ); + + $paypal_gateway_enabled = wc_string_to_bool( $paypal_gateway->get_option( 'enabled' ) ); + + $methods[] = $paypal_gateway; $dcc_applies = $container->get( 'api.helpers.dccapplies' ); /** @@ -304,7 +309,7 @@ class WCGatewayModule implements ModuleInterface { $methods[] = $container->get( 'wcgateway.credit-card-gateway' ); } - if ( $container->get( 'wcgateway.settings.allow_card_button_gateway' ) ) { + if ( $paypal_gateway_enabled && $container->get( 'wcgateway.settings.allow_card_button_gateway' ) ) { $methods[] = $container->get( 'wcgateway.card-button-gateway' ); } From b98400c6ce014830b17f859a3ff2446ddbed6d19 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 3 Aug 2022 11:25:08 +0200 Subject: [PATCH 115/145] Improve phpcs comment --- .../src/Gateway/PayUponInvoice/PayUponInvoice.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index 3b2bd5ff4..3e83e01fe 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -338,12 +338,8 @@ class PayUponInvoice { woocommerce_form_field( 'billing_phone', array( - /** - * Use translation from WooCommerce here. - * phpcs:disable WordPress.WP.I18n.TextDomainMismatch - */ + // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch 'label' => __( 'Phone', 'woocommerce' ), - // phpcs:enable WordPress.WP.I18n.TextDomainMismatch 'type' => 'tel', 'class' => array( 'form-row-wide' ), 'validate' => array( 'phone' ), From 878f271ae972f245401c979b35879e76ac08ac2e Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 3 Aug 2022 14:21:33 +0300 Subject: [PATCH 116/145] Update transaction id after manual capture --- .../Processor/AuthorizedPaymentsProcessor.php | 5 +++- .../AuthorizedPaymentsProcessorTest.php | 23 +++++++++++++------ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Processor/AuthorizedPaymentsProcessor.php b/modules/ppcp-wc-gateway/src/Processor/AuthorizedPaymentsProcessor.php index 52ae85f3b..d87c18bfb 100644 --- a/modules/ppcp-wc-gateway/src/Processor/AuthorizedPaymentsProcessor.php +++ b/modules/ppcp-wc-gateway/src/Processor/AuthorizedPaymentsProcessor.php @@ -31,7 +31,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice; */ class AuthorizedPaymentsProcessor { - use PaymentsStatusHandlingTrait; + use PaymentsStatusHandlingTrait, TransactionIdHandlingTrait; const SUCCESSFUL = 'SUCCESSFUL'; const ALREADY_CAPTURED = 'ALREADY_CAPTURED'; @@ -200,6 +200,9 @@ class AuthorizedPaymentsProcessor { $this->handle_capture_status( $capture, $wc_order ); + $transaction_id = $capture->id(); + $this->update_transaction_id( $transaction_id, $wc_order ); + if ( self::SUCCESSFUL === $result_status ) { if ( $capture->status()->is( CaptureStatus::COMPLETED ) ) { $wc_order->add_order_note( diff --git a/tests/PHPUnit/WcGateway/Processor/AuthorizedPaymentsProcessorTest.php b/tests/PHPUnit/WcGateway/Processor/AuthorizedPaymentsProcessorTest.php index 946ade989..8ebd63794 100644 --- a/tests/PHPUnit/WcGateway/Processor/AuthorizedPaymentsProcessorTest.php +++ b/tests/PHPUnit/WcGateway/Processor/AuthorizedPaymentsProcessorTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\WcGateway\Processor; +use Mockery\MockInterface; use Psr\Container\ContainerInterface; use Psr\Log\NullLogger; use WC_Order; @@ -26,7 +27,11 @@ use WooCommerce\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice; class AuthorizedPaymentsProcessorTest extends TestCase { + /** + * @var WC_Order&MockInterface + */ private $wcOrder; + private $paypalOrderId = 'abc'; private $authorizationId = 'qwe'; private $amount = 42.0; @@ -37,7 +42,7 @@ class AuthorizedPaymentsProcessorTest extends TestCase private $paymentsEndpoint; private $notice; private $config; - private $subscription_helperauthorization; + private $captureId = '123qwe'; private $testee; public function setUp(): void { @@ -79,7 +84,7 @@ class AuthorizedPaymentsProcessorTest extends TestCase $this->paymentsEndpoint ->expects('capture') ->with($this->authorizationId, equalTo(new Money($this->amount, $this->currency))) - ->andReturn($this->createCapture(CaptureStatus::COMPLETED)); + ->andReturn($this->createCapture($this->captureId, CaptureStatus::COMPLETED)); $this->assertEquals(AuthorizedPaymentsProcessor::SUCCESSFUL, $this->testee->process($this->wcOrder)); } @@ -99,7 +104,7 @@ class AuthorizedPaymentsProcessorTest extends TestCase $this->paymentsEndpoint ->expects('capture') ->with($authorizations[2]->id(), equalTo(new Money($this->amount, $this->currency))) - ->andReturn($this->createCapture(CaptureStatus::COMPLETED)); + ->andReturn($this->createCapture($this->captureId, CaptureStatus::COMPLETED)); $this->assertEquals(AuthorizedPaymentsProcessor::SUCCESSFUL, $this->testee->process($this->wcOrder)); } @@ -150,12 +155,13 @@ class AuthorizedPaymentsProcessorTest extends TestCase $this->paymentsEndpoint ->expects('capture') ->with($this->authorizationId, equalTo(new Money($this->amount, $this->currency))) - ->andReturn($this->createCapture(CaptureStatus::COMPLETED)); + ->andReturn($this->createCapture($this->captureId, CaptureStatus::COMPLETED)); $this->wcOrder->shouldReceive('payment_complete')->andReturn(true); - $this->wcOrder->expects('add_order_note'); + $this->wcOrder->expects('add_order_note')->twice(); $this->wcOrder->expects('update_meta_data'); - $this->wcOrder->expects('save'); + $this->wcOrder->expects('set_transaction_id')->with($this->captureId); + $this->wcOrder->shouldReceive('save')->atLeast()->times(1); $this->assertTrue( $this->testee->capture_authorized_payment($this->wcOrder) @@ -248,8 +254,11 @@ class AuthorizedPaymentsProcessorTest extends TestCase return new Authorization($id, new AuthorizationStatus($status)); } - private function createCapture(string $status): Capture { + private function createCapture(string $id, string $status): Capture { $capture = Mockery::mock(Capture::class); + $capture + ->shouldReceive('id') + ->andReturn($id); $capture ->shouldReceive('status') ->andReturn(new CaptureStatus($status)); From a893a1de8eaf802457633921ec49ba80f2de21c4 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 4 Aug 2022 09:55:27 +0300 Subject: [PATCH 117/145] 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 118/145] 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 119/145] 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 120/145] 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 121/145] 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 122/145] 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 e65dab755aca40bf5de19b4f9bf4377af4676026 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 8 Aug 2022 15:45:54 +0200 Subject: [PATCH 123/145] Display phone field in pui gateway when is optional in checkout form --- .../src/Gateway/PayUponInvoice/PayUponInvoice.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index 3e83e01fe..509b9cb27 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -333,8 +333,9 @@ class PayUponInvoice { ) ); - $checkout_fields = WC()->checkout()->get_checkout_fields(); - if ( ! array_key_exists( 'billing_phone', $checkout_fields['billing'] ) ) { + $checkout_fields = WC()->checkout()->get_checkout_fields(); + $checkout_phone_required = $checkout_fields['billing']['billing_phone']['required']; + if ( ! array_key_exists( 'billing_phone', $checkout_fields['billing'] ) || $checkout_phone_required === false ) { woocommerce_form_field( 'billing_phone', array( @@ -392,6 +393,9 @@ class PayUponInvoice { } $national_number = filter_input( INPUT_POST, 'billing_phone', FILTER_SANITIZE_STRING ); + if ( ! $national_number ) { + $errors->add( 'validation', __( 'Phone field cannot be empty.', 'woocommerce-paypal-payments' ) ); + } if ( $national_number ) { $numeric_phone_number = preg_replace( '/[^0-9]/', '', $national_number ); if ( $numeric_phone_number && ! preg_match( '/^[0-9]{1,14}?$/', $numeric_phone_number ) ) { From b57567cf84723294426e66b63b1d61bbe10ed5c3 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 8 Aug 2022 15:48:36 +0200 Subject: [PATCH 124/145] Fix undefined index on billing phone --- .../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 509b9cb27..2529afad4 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -334,7 +334,7 @@ class PayUponInvoice { ); $checkout_fields = WC()->checkout()->get_checkout_fields(); - $checkout_phone_required = $checkout_fields['billing']['billing_phone']['required']; + $checkout_phone_required = $checkout_fields['billing']['billing_phone']['required'] ?? false; if ( ! array_key_exists( 'billing_phone', $checkout_fields['billing'] ) || $checkout_phone_required === false ) { woocommerce_form_field( 'billing_phone', From 012204d393eb19c834e84004be527c3f7019f844 Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Mon, 8 Aug 2022 15:33:55 -0300 Subject: [PATCH 125/145] 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 126/145] 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 127/145] 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 4f59956fcac53af1e7f87bbb6c148b3223d42e59 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 9 Aug 2022 11:14:20 +0300 Subject: [PATCH 128/145] Do not include full path in exception --- modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php index f78871d1a..25902e227 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php +++ b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php @@ -118,7 +118,7 @@ trait ProcessPaymentTrait { * @return string */ protected function format_exception( Throwable $exception ): string { - $output = $exception->getMessage() . ' ' . $exception->getFile() . ':' . $exception->getLine(); + $output = $exception->getMessage() . ' ' . basename( $exception->getFile() ) . ':' . $exception->getLine(); $prev = $exception->getPrevious(); if ( ! $prev ) { return $output; From 4d37d53f6aa07620cc069ccb2c778035fbc42c53 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 9 Aug 2022 11:01:41 +0200 Subject: [PATCH 129/145] 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 From 93b483f9e16fb466b86e6040ea78857bc22eb5b4 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 9 Aug 2022 12:23:24 +0200 Subject: [PATCH 130/145] Set order phone number if exists in request --- .../src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php index 4596bd321..68d4a19a7 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php @@ -215,6 +215,12 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { } } + $phone_number = filter_input( INPUT_POST, 'billing_phone', FILTER_SANITIZE_STRING ) ?? ''; + if ( $phone_number ) { + $wc_order->set_billing_phone( $phone_number ); + $wc_order->save(); + } + $wc_order->update_status( 'on-hold', __( 'Awaiting Pay upon Invoice payment.', 'woocommerce-paypal-payments' ) ); $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); $payment_source = $this->payment_source_factory->from_wc_order( $wc_order, $birth_date ); From a2e24240467845a2a66bb697614c4ae70b8fe871 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 9 Aug 2022 15:51:50 +0200 Subject: [PATCH 131/145] Add wc order to the class --- modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php index 1ac5ecb73..3814e0b88 100644 --- a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php +++ b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php @@ -9,6 +9,8 @@ declare( strict_types=1 ); namespace WooCommerce\PayPalCommerce\WcGateway\Helper; +use WC_Order; + /** * Class PayUponInvoiceHelper */ From 48306ab0a34b3e8cbeb857f38c5de6368d61ce62 Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Tue, 9 Aug 2022 14:11:45 -0300 Subject: [PATCH 132/145] 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 423d52ab8..c6e693240 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,6 @@ *** Changelog *** -= 1.9.2 - TBD = += 1.9.2 - 2022-08-09 = * 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 f0896433d123fafef129b01d1cfceb1c5e5eb25e Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 10 Aug 2022 09:52:00 +0300 Subject: [PATCH 133/145] Allow to add output file prefix in package action --- .github/workflows/package.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 6d4d11f21..675de22f7 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -4,7 +4,11 @@ on: workflow_dispatch: inputs: packageVersion: - description: 'Package Version' + description: 'Package version' + required: false + type: string + filePrefix: + description: 'File prefix' required: false type: string @@ -12,6 +16,9 @@ jobs: package: runs-on: ubuntu-latest + env: + FILENAME: woocommerce-paypal-payments + name: Build package steps: - uses: actions/checkout@v2 @@ -34,6 +41,12 @@ jobs: - 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: Set file name + env: + FILE_PREFIX: ${{ github.event.inputs.filePrefix }} + run: echo "FILENAME=$FILE_PREFIX-$FILENAME" >> $GITHUB_ENV + if: github.event.inputs.filePrefix + - name: Upload uses: actions/upload-artifact@v3 with: From d16024c522d24376993fdc56a92553fafce9a5bf Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 10 Aug 2022 09:54:03 +0300 Subject: [PATCH 134/145] Prepend current version number when package action input has only suffix --- .github/workflows/package.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 675de22f7..549501d41 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -17,6 +17,7 @@ jobs: runs-on: ubuntu-latest env: + PACKAGE_VERSION: ${{ github.event.inputs.packageVersion }} FILENAME: woocommerce-paypal-payments name: Build package @@ -29,11 +30,13 @@ jobs: php-version: 7.1 tools: composer:v1 + - name: Fix plugin version input # Add the version number if only suffix entered + run: echo "PACKAGE_VERSION=$(sed -nE '/Version:/s/.* ([0-9.]+).*/\1/p' woocommerce-paypal-payments.php)-$PACKAGE_VERSION" >> $GITHUB_ENV + if: env.PACKAGE_VERSION && !contains(env.PACKAGE_VERSION, '.') + - 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 + if: env.PACKAGE_VERSION - name: Build run: yarn build @@ -50,5 +53,5 @@ jobs: - name: Upload uses: actions/upload-artifact@v3 with: - name: woocommerce-paypal-payments + name: ${{ env.FILENAME }} path: dist/ From 63e1d113ff4635fe59a72f2a19f0e5d72fef1640 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 8 Aug 2022 09:55:04 +0300 Subject: [PATCH 135/145] Render sections as tabs instead of links --- .../src/Settings/SectionsRenderer.php | 14 +++++++------- modules/ppcp-wc-gateway/src/WCGatewayModule.php | 10 ++++------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php b/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php index 275574e97..c92e72ee1 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php +++ b/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php @@ -56,14 +56,12 @@ class SectionsRenderer { /** * Renders the Sections tab. */ - public function render(): void { + public function render(): string { if ( ! $this->should_render() ) { - return; + return ''; } - echo '
        '; - - $array_keys = array_keys( $this->sections ); + $html = '

      '; + $html .= ''; + + return $html; } } diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index d01287f67..3305ae14d 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -66,12 +66,10 @@ class WCGatewayModule implements ModuleInterface { 'woocommerce_sections_checkout', function() use ( $c ) { $section_renderer = $c->get( 'wcgateway.settings.sections-renderer' ); - /** - * The Section Renderer. - * - * @var SectionsRenderer $section_renderer - */ - $section_renderer->render(); + assert( $section_renderer instanceof SectionsRenderer ); + + // phpcs:ignore WordPress.Security.EscapeOutput + echo $section_renderer->render(); } ); From 3e014bad84c71daeee6baee3d65f7687fa16a1cc Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 10 Aug 2022 09:16:53 +0300 Subject: [PATCH 136/145] Render tabs later (after WC Payments links) --- modules/ppcp-wc-gateway/src/WCGatewayModule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 3305ae14d..1a52ce30d 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -70,7 +70,7 @@ class WCGatewayModule implements ModuleInterface { // phpcs:ignore WordPress.Security.EscapeOutput echo $section_renderer->render(); - } + }, 20 ); add_action( From 3470010958f067df5da5dde6a1e74a2f1b5a8a2d Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 10 Aug 2022 09:10:38 +0200 Subject: [PATCH 137/145] Fix phpcs --- modules/ppcp-wc-gateway/src/WCGatewayModule.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 1a52ce30d..1ef7bf0f2 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -70,7 +70,8 @@ class WCGatewayModule implements ModuleInterface { // phpcs:ignore WordPress.Security.EscapeOutput echo $section_renderer->render(); - }, 20 + }, + 20 ); add_action( From 65c8cce2a6ee50fcc0f309e80e17264959998af3 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 10 Aug 2022 10:44:36 +0200 Subject: [PATCH 138/145] Do not check store currency when registering pui gateway --- modules/ppcp-wc-gateway/src/WCGatewayModule.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 853670025..b6a30b4d1 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -231,7 +231,7 @@ class WCGatewayModule implements ModuleInterface { add_action( 'init', function () use ( $c ) { - if ( 'DE' === $c->get( 'api.shop.country' ) && 'EUR' === $c->get( 'api.shop.currency' ) ) { + if ( 'DE' === $c->get( 'api.shop.country' ) ) { ( $c->get( 'wcgateway.pay-upon-invoice' ) )->init(); } @@ -312,7 +312,7 @@ class WCGatewayModule implements ModuleInterface { $methods[] = $container->get( 'wcgateway.card-button-gateway' ); } - if ( 'DE' === $container->get( 'api.shop.country' ) && 'EUR' === $container->get( 'api.shop.currency' ) ) { + if ( 'DE' === $container->get( 'api.shop.country' ) ) { $methods[] = $container->get( 'wcgateway.pay-upon-invoice-gateway' ); } From 5d8b783abbc5bff7eb380e173a7158e75e0e049e Mon Sep 17 00:00:00 2001 From: dinamiko Date: Thu, 11 Aug 2022 14:22:12 +0200 Subject: [PATCH 139/145] Move saved card payment logic into vaulting (WIP) --- modules/ppcp-vaulting/services.php | 13 ++ .../src/VaultedCreditCardHandler.php | 202 ++++++++++++++++++ modules/ppcp-wc-gateway/services.php | 17 +- .../src/Gateway/CreditCardGateway.php | 181 ++-------------- patchwork.json | 7 +- .../Gateway/CreditCardGatewayTest.php | 108 ++++++++++ tests/stubs/WC_Payment_Gateway_CC.php | 5 + 7 files changed, 352 insertions(+), 181 deletions(-) create mode 100644 modules/ppcp-vaulting/src/VaultedCreditCardHandler.php create mode 100644 tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php diff --git a/modules/ppcp-vaulting/services.php b/modules/ppcp-vaulting/services.php index f2c60328e..a0ad2d814 100644 --- a/modules/ppcp-vaulting/services.php +++ b/modules/ppcp-vaulting/services.php @@ -57,4 +57,17 @@ return array( $container->get( 'woocommerce.logger.woocommerce' ) ); }, + 'vaulting.credit-card-handler' => function(ContainerInterface $container): VaultedCreditCardHandler { + return new VaultedCreditCardHandler( + $container->get('subscription.helper'), + $container->get('vaulting.repository.payment-token'), + $container->get( 'api.factory.purchase-unit' ), + $container->get( 'api.factory.payer' ), + $container->get( 'api.factory.shipping-preference' ), + $container->get( 'api.endpoint.order' ), + $container->get( 'onboarding.environment' ), + $container->get( 'wcgateway.processor.authorized-payments' ), + $container->get( 'wcgateway.settings' ) + ); + }, ); diff --git a/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php new file mode 100644 index 000000000..1a90d57ea --- /dev/null +++ b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php @@ -0,0 +1,202 @@ +subscription_helper = $subscription_helper; + $this->payment_token_repository = $payment_token_repository; + $this->purchase_unit_factory = $purchase_unit_factory; + $this->payer_factory = $payer_factory; + $this->shipping_preference_factory = $shipping_preference_factory; + $this->order_endpoint = $order_endpoint; + $this->environment = $environment; + $this->authorized_payments_processor = $authorized_payments_processor; + $this->config = $config; + } + + public function handle_payment( + string $saved_credit_card, + WC_Order $wc_order + ): WC_Order { + + $change_payment = filter_input( INPUT_POST, 'woocommerce_change_payment', FILTER_SANITIZE_STRING ); + if($change_payment) { + if ( $this->subscription_helper->has_subscription( $wc_order->get_id() ) && $this->subscription_helper->is_subscription_change_payment() ) { + if ( $saved_credit_card ) { + update_post_meta( $wc_order->get_id(), 'payment_token_id', $saved_credit_card ); + + return $wc_order; + } + } + } + + $user_id = (int) $wc_order->get_customer_id(); + $customer = new WC_Customer( $user_id ); + $tokens = $this->payment_token_repository->all_for_user_id( (int) $customer->get_id() ); + $selected_token = null; + foreach ( $tokens as $token ) { + if ( $token->id() === $saved_credit_card ) { + $selected_token = $token; + break; + } + } + if ( ! $selected_token ) { + throw new RuntimeException('Saved card token not found.'); + } + + $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); + $payer = $this->payer_factory->from_customer( $customer ); + $shipping_preference = $this->shipping_preference_factory->from_state( + $purchase_unit, + '' + ); + + try { + $order = $this->order_endpoint->create( + array( $purchase_unit ), + $shipping_preference, + $payer, + $selected_token + ); + + $this->add_paypal_meta( $wc_order, $order, $this->environment ); + + if ( ! $order->status()->is( OrderStatus::COMPLETED ) ) { + throw new RuntimeException("Unexpected status for order {$order->id()} using a saved card: {$order->status()->name()}."); + } + + if ( ! in_array( + $order->intent(), + array( 'CAPTURE', 'AUTHORIZE' ), + true + ) ) { + throw new RuntimeException("Could neither capture nor authorize order {$order->id()} using a saved card. Status: {$order->status()->name()}. Intent: {$order->intent()}."); + } + + if ( $order->intent() === 'AUTHORIZE' ) { + $order = $this->order_endpoint->authorize( $order ); + $wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'false' ); + } + + $transaction_id = $this->get_paypal_order_transaction_id( $order ); + if ( $transaction_id ) { + $this->update_transaction_id( $transaction_id, $wc_order ); + } + + $this->handle_new_order_status( $order, $wc_order ); + + if ( $this->is_free_trial_order( $wc_order ) ) { + $this->authorized_payments_processor->void_authorizations( $order ); + $wc_order->payment_complete(); + } elseif ( $this->config->has( 'intent' ) && strtoupper( (string) $this->config->get( 'intent' ) ) === 'CAPTURE' ) { + $this->authorized_payments_processor->capture_authorized_payment( $wc_order ); + } + + return $wc_order; + } catch ( RuntimeException $error ) { + throw new RuntimeException($error->getMessage()); + } + } +} diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 63948c96f..de88b4bb3 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -95,40 +95,29 @@ return array( 'wcgateway.credit-card-gateway' => static function ( ContainerInterface $container ): CreditCardGateway { $order_processor = $container->get( 'wcgateway.order-processor' ); $settings_renderer = $container->get( 'wcgateway.settings.render' ); - $authorized_payments = $container->get( 'wcgateway.processor.authorized-payments' ); $settings = $container->get( 'wcgateway.settings' ); $module_url = $container->get( 'wcgateway.url' ); $session_handler = $container->get( 'session.handler' ); $refund_processor = $container->get( 'wcgateway.processor.refunds' ); $state = $container->get( 'onboarding.state' ); $transaction_url_provider = $container->get( 'wcgateway.transaction-url-provider' ); - $payment_token_repository = $container->get( 'vaulting.repository.payment-token' ); - $purchase_unit_factory = $container->get( 'api.factory.purchase-unit' ); - $payer_factory = $container->get( 'api.factory.payer' ); - $order_endpoint = $container->get( 'api.endpoint.order' ); $subscription_helper = $container->get( 'subscription.helper' ); $payments_endpoint = $container->get( 'api.endpoint.payments' ); $logger = $container->get( 'woocommerce.logger.woocommerce' ); - $environment = $container->get( 'onboarding.environment' ); + $vaulted_credit_card_handler = $container->get('vaulting.credit-card-handler'); return new CreditCardGateway( $settings_renderer, $order_processor, - $authorized_payments, $settings, $module_url, $session_handler, $refund_processor, $state, $transaction_url_provider, - $payment_token_repository, - $purchase_unit_factory, - $container->get( 'api.factory.shipping-preference' ), - $payer_factory, - $order_endpoint, $subscription_helper, $logger, - $environment, - $payments_endpoint + $payments_endpoint, + $vaulted_credit_card_handler ); }, 'wcgateway.card-button-gateway' => static function ( ContainerInterface $container ): CardButtonGateway { diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index 4b97120bc..67bcb014f 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -12,27 +12,17 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Gateway; use Exception; use Psr\Log\LoggerInterface; use WC_Order; -use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint; -use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; -use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; -use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; -use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; -use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Session\SessionHandler; -use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; +use WooCommerce\PayPalCommerce\Vaulting\VaultedCreditCardHandler; use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException; -use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; -use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor; -use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor; -use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait; use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer; use Psr\Container\ContainerInterface; @@ -41,8 +31,7 @@ use Psr\Container\ContainerInterface; */ class CreditCardGateway extends \WC_Payment_Gateway_CC { - use ProcessPaymentTrait, OrderMetaTrait, TransactionIdHandlingTrait, PaymentsStatusHandlingTrait, FreeTrialHandlerTrait, - GatewaySettingsRendererTrait; + use ProcessPaymentTrait, GatewaySettingsRendererTrait; const ID = 'ppcp-credit-card-gateway'; @@ -60,13 +49,6 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { */ protected $order_processor; - /** - * The processor for authorized payments. - * - * @var AuthorizedPaymentsProcessor - */ - protected $authorized_payments_processor; - /** * The settings. * @@ -74,6 +56,11 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { */ protected $config; + /** + * @var VaultedCreditCardHandler + */ + protected $vaulted_credit_card_handler; + /** * The URL to the module. * @@ -116,34 +103,6 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { */ private $payment_token_repository; - /** - * The purchase unit factory. - * - * @var PurchaseUnitFactory - */ - private $purchase_unit_factory; - - /** - * The shipping_preference factory. - * - * @var ShippingPreferenceFactory - */ - private $shipping_preference_factory; - - /** - * The payer factory. - * - * @var PayerFactory - */ - private $payer_factory; - - /** - * The order endpoint. - * - * @var OrderEndpoint - */ - private $order_endpoint; - /** * The subscription helper. * @@ -158,13 +117,6 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { */ protected $logger; - /** - * The environment. - * - * @var Environment - */ - protected $environment; - /** * The payments endpoint * @@ -177,62 +129,44 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { * * @param SettingsRenderer $settings_renderer The Settings Renderer. * @param OrderProcessor $order_processor The Order processor. - * @param AuthorizedPaymentsProcessor $authorized_payments_processor The Authorized Payments processor. * @param ContainerInterface $config The settings. * @param string $module_url The URL to the module. * @param SessionHandler $session_handler The Session Handler. * @param RefundProcessor $refund_processor The refund processor. * @param State $state The state. * @param TransactionUrlProvider $transaction_url_provider Service able to provide view transaction url base. - * @param PaymentTokenRepository $payment_token_repository The payment token repository. - * @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory. - * @param ShippingPreferenceFactory $shipping_preference_factory The shipping_preference factory. - * @param PayerFactory $payer_factory The payer factory. - * @param OrderEndpoint $order_endpoint The order endpoint. * @param SubscriptionHelper $subscription_helper The subscription helper. * @param LoggerInterface $logger The logger. - * @param Environment $environment The environment. * @param PaymentsEndpoint $payments_endpoint The payments endpoint. + * @param VaultedCreditCardHandler $vaulted_credit_card_handler The vaulted credit card handler. */ public function __construct( SettingsRenderer $settings_renderer, OrderProcessor $order_processor, - AuthorizedPaymentsProcessor $authorized_payments_processor, ContainerInterface $config, string $module_url, SessionHandler $session_handler, RefundProcessor $refund_processor, State $state, TransactionUrlProvider $transaction_url_provider, - PaymentTokenRepository $payment_token_repository, - PurchaseUnitFactory $purchase_unit_factory, - ShippingPreferenceFactory $shipping_preference_factory, - PayerFactory $payer_factory, - OrderEndpoint $order_endpoint, SubscriptionHelper $subscription_helper, LoggerInterface $logger, - Environment $environment, - PaymentsEndpoint $payments_endpoint + PaymentsEndpoint $payments_endpoint, + VaultedCreditCardHandler $vaulted_credit_card_handler ) { $this->id = self::ID; $this->settings_renderer = $settings_renderer; $this->order_processor = $order_processor; - $this->authorized_payments_processor = $authorized_payments_processor; $this->config = $config; $this->module_url = $module_url; $this->session_handler = $session_handler; $this->refund_processor = $refund_processor; $this->state = $state; $this->transaction_url_provider = $transaction_url_provider; - $this->payment_token_repository = $payment_token_repository; - $this->purchase_unit_factory = $purchase_unit_factory; - $this->shipping_preference_factory = $shipping_preference_factory; - $this->payer_factory = $payer_factory; - $this->order_endpoint = $order_endpoint; $this->subscription_helper = $subscription_helper; $this->logger = $logger; - $this->environment = $environment; $this->payments_endpoint = $payments_endpoint; + $this->vaulted_credit_card_handler = $vaulted_credit_card_handler; if ( $state->current_state() === State::STATE_ONBOARDED ) { $this->supports = array( 'refunds' ); @@ -424,101 +358,20 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { * If customer has chosen a saved credit card payment. */ $saved_credit_card = filter_input( INPUT_POST, 'saved_credit_card', FILTER_SANITIZE_STRING ); - $change_payment = filter_input( INPUT_POST, 'woocommerce_change_payment', FILTER_SANITIZE_STRING ); - if ( $saved_credit_card && ! isset( $change_payment ) ) { - - $user_id = (int) $wc_order->get_customer_id(); - $customer = new \WC_Customer( $user_id ); - $tokens = $this->payment_token_repository->all_for_user_id( (int) $customer->get_id() ); - - $selected_token = null; - foreach ( $tokens as $token ) { - if ( $token->id() === $saved_credit_card ) { - $selected_token = $token; - break; - } - } - - if ( ! $selected_token ) { - return $this->handle_payment_failure( - $wc_order, - new GatewayGenericException( new Exception( 'Saved card token not found.' ) ) - ); - } - - $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); - $payer = $this->payer_factory->from_customer( $customer ); - - $shipping_preference = $this->shipping_preference_factory->from_state( - $purchase_unit, - '' - ); - + if($saved_credit_card) { try { - $order = $this->order_endpoint->create( - array( $purchase_unit ), - $shipping_preference, - $payer, - $selected_token + $wc_order = $this->vaulted_credit_card_handler->handle_payment( + $saved_credit_card, + $wc_order ); - $this->add_paypal_meta( $wc_order, $order, $this->environment ); - - if ( ! $order->status()->is( OrderStatus::COMPLETED ) ) { - return $this->handle_payment_failure( - $wc_order, - new GatewayGenericException( new Exception( "Unexpected status for order {$order->id()} using a saved card: {$order->status()->name()}." ) ) - ); - } - - if ( ! in_array( - $order->intent(), - array( 'CAPTURE', 'AUTHORIZE' ), - true - ) ) { - return $this->handle_payment_failure( - $wc_order, - new GatewayGenericException( new Exception( "Could neither capture nor authorize order {$order->id()} using a saved card. Status: {$order->status()->name()}. Intent: {$order->intent()}." ) ) - ); - } - - if ( $order->intent() === 'AUTHORIZE' ) { - $order = $this->order_endpoint->authorize( $order ); - - $wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'false' ); - } - - $transaction_id = $this->get_paypal_order_transaction_id( $order ); - if ( $transaction_id ) { - $this->update_transaction_id( $transaction_id, $wc_order ); - } - - $this->handle_new_order_status( $order, $wc_order ); - - if ( $this->is_free_trial_order( $wc_order ) ) { - $this->authorized_payments_processor->void_authorizations( $order ); - $wc_order->payment_complete(); - } elseif ( $this->config->has( 'intent' ) && strtoupper( (string) $this->config->get( 'intent' ) ) === 'CAPTURE' ) { - $this->authorized_payments_processor->capture_authorized_payment( $wc_order ); - } - return $this->handle_payment_success( $wc_order ); - } catch ( RuntimeException $error ) { + + } catch(RuntimeException $error) { return $this->handle_payment_failure( $wc_order, $error ); } } - /** - * If customer has chosen change Subscription payment. - */ - if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) { - if ( $saved_credit_card ) { - update_post_meta( $order_id, 'payment_token_id', $saved_credit_card ); - - return $this->handle_payment_success( $wc_order ); - } - } - /** * If the WC_Order is paid through the approved webhook. */ diff --git a/patchwork.json b/patchwork.json index dd62edf7f..0236daa4b 100644 --- a/patchwork.json +++ b/patchwork.json @@ -1,5 +1,6 @@ { - "redefinable-internals": [ - "json_decode" - ] + "redefinable-internals": [ + "json_decode", + "filter_input" + ] } diff --git a/tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php new file mode 100644 index 000000000..4f751a924 --- /dev/null +++ b/tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php @@ -0,0 +1,108 @@ +settingsRenderer = Mockery::mock(SettingsRenderer::class); + $this->orderProcessor = Mockery::mock(OrderProcessor::class); + $this->config = Mockery::mock(ContainerInterface::class); + $this->moduleUrl = ''; + $this->sessionHandler = Mockery::mock(SessionHandler::class); + $this->refundProcessor = Mockery::mock(RefundProcessor::class); + $this->state = Mockery::mock(State::class); + $this->transactionUrlProvider = Mockery::mock(TransactionUrlProvider::class); + $this->subscriptionHelper = Mockery::mock(SubscriptionHelper::class); + $this->logger = Mockery::mock(LoggerInterface::class); + $this->paymentsEndpoint = Mockery::mock(PaymentsEndpoint::class); + $this->vaultedCreditCardHandler = Mockery::mock(VaultedCreditCardHandler::class); + + $this->state->shouldReceive('current_state')->andReturn(State::STATE_ONBOARDED); + $this->config->shouldReceive('has')->andReturn(true); + $this->config->shouldReceive('get')->andReturn(''); + + $this->testee = new CreditCardGateway( + $this->settingsRenderer, + $this->orderProcessor, + $this->config, + $this->moduleUrl, + $this->sessionHandler, + $this->refundProcessor, + $this->state, + $this->transactionUrlProvider, + $this->subscriptionHelper, + $this->logger, + $this->paymentsEndpoint, + $this->vaultedCreditCardHandler + ); + } + + public function testProcessPayment() + { + $wc_order = Mockery::mock(WC_Order::class); + when('wc_get_order')->justReturn($wc_order); + + $this->orderProcessor->shouldReceive('process') + ->with($wc_order) + ->andReturn(true); + $this->subscriptionHelper->shouldReceive('has_subscription') + ->andReturn(false); + $this->sessionHandler->shouldReceive('destroy_session_data')->once(); + + $result = $this->testee->process_payment(1); + $this->assertEquals('success', $result['result']); + } + + public function testProcessPaymentVaultedCard() + { + $wc_order = Mockery::mock(WC_Order::class); + when('wc_get_order')->justReturn($wc_order); + + $savedCreditCard = 'abc123'; + when('filter_input')->justReturn($savedCreditCard); + + $this->vaultedCreditCardHandler + ->shouldReceive('handle_payment') + ->with($savedCreditCard, $wc_order) + ->andReturn($wc_order); + + $this->sessionHandler->shouldReceive('destroy_session_data')->once(); + + $result = $this->testee->process_payment(1); + $this->assertEquals('success', $result['result']); + } +} diff --git a/tests/stubs/WC_Payment_Gateway_CC.php b/tests/stubs/WC_Payment_Gateway_CC.php index f2e36b984..7c7b26df6 100644 --- a/tests/stubs/WC_Payment_Gateway_CC.php +++ b/tests/stubs/WC_Payment_Gateway_CC.php @@ -3,5 +3,10 @@ declare(strict_types=1); class WC_Payment_Gateway_CC { + public function init_settings() {} + public function process_admin_options() {} + protected function get_return_url($wcOrder) { + return $wcOrder; + } } From 0e24b1d0f837add18ffb4fd34aeb32359c45c7c3 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 12 Aug 2022 09:57:47 +0200 Subject: [PATCH 140/145] Add unit tests for vaulted card handler --- .../src/VaultedCreditCardHandler.php | 8 +- .../src/Gateway/CreditCardGateway.php | 6 +- .../Vaulting/VaultedCreditCardHandlerTest.php | 146 ++++++++++++++++++ 3 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php diff --git a/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php index 1a90d57ea..dd0b93224 100644 --- a/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php +++ b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php @@ -118,7 +118,8 @@ class VaultedCreditCardHandler public function handle_payment( string $saved_credit_card, - WC_Order $wc_order + WC_Order $wc_order, + WC_Customer $customer ): WC_Order { $change_payment = filter_input( INPUT_POST, 'woocommerce_change_payment', FILTER_SANITIZE_STRING ); @@ -132,9 +133,7 @@ class VaultedCreditCardHandler } } - $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() ); + $tokens = $this->payment_token_repository->all_for_user_id( $wc_order->get_customer_id() ); $selected_token = null; foreach ( $tokens as $token ) { if ( $token->id() === $saved_credit_card ) { @@ -148,6 +147,7 @@ class VaultedCreditCardHandler $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, '' diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index 67bcb014f..8578ac072 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Gateway; use Exception; use Psr\Log\LoggerInterface; +use WC_Customer; use WC_Order; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; @@ -360,9 +361,12 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { $saved_credit_card = filter_input( INPUT_POST, 'saved_credit_card', FILTER_SANITIZE_STRING ); if($saved_credit_card) { try { + $customer = new WC_Customer( $wc_order->get_customer_id() ); + $wc_order = $this->vaulted_credit_card_handler->handle_payment( $saved_credit_card, - $wc_order + $wc_order, + $customer ); return $this->handle_payment_success( $wc_order ); diff --git a/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php b/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php new file mode 100644 index 000000000..7060bf8be --- /dev/null +++ b/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php @@ -0,0 +1,146 @@ +subscriptionHelper = Mockery::mock(SubscriptionHelper::class); + $this->paymentTokenRepository = Mockery::mock(PaymentTokenRepository::class); + $this->purchaseUnitFactory = Mockery::mock(PurchaseUnitFactory::class); + $this->payerFactory = Mockery::mock(PayerFactory::class); + $this->shippingPreferenceFactory = Mockery::mock(ShippingPreferenceFactory::class); + $this->orderEndpoint = Mockery::mock(OrderEndpoint::class); + $this->environment = Mockery::mock(Environment::class); + $this->authorizedPaymentProcessor = Mockery::mock(AuthorizedPaymentsProcessor::class); + $this->config = Mockery::mock(ContainerInterface::class); + + $this->testee = new VaultedCreditCardHandler( + $this->subscriptionHelper, + $this->paymentTokenRepository, + $this->purchaseUnitFactory, + $this->payerFactory, + $this->shippingPreferenceFactory, + $this->orderEndpoint, + $this->environment, + $this->authorizedPaymentProcessor, + $this->config + ); + } + + public function testHandlePaymentChangingPayment() + { + when('filter_input')->justReturn(1); + $wcOrder = Mockery::mock(\WC_Order::class); + $wcOrder->shouldReceive('get_id')->andReturn(1); + $this->subscriptionHelper->shouldReceive('has_subscription')->andReturn(true); + $this->subscriptionHelper->shouldReceive('is_subscription_change_payment')->andReturn(true); + expect('update_post_meta')->with(1, 'payment_token_id', 'abc123'); + + $customer = Mockery::mock(WC_Customer::class); + + $result = $this->testee->handle_payment('abc123', $wcOrder, $customer); + $this->assertInstanceOf(\WC_Order::class, $result); + } + + public function testHandlePayment() + { + $wcOrder = Mockery::mock(\WC_Order::class); + $wcOrder->shouldReceive('get_id')->andReturn(1); + $wcOrder->shouldReceive('get_customer_id')->andReturn(1); + $wcOrder->shouldReceive('update_meta_data')->andReturn(1); + $wcOrder->shouldReceive('save')->once(); + $wcOrder->shouldReceive('payment_complete')->andReturn(true); + + $token = Mockery::mock(PaymentToken::class); + $tokenId = 'abc123'; + $token->shouldReceive('id')->andReturn($tokenId); + $this->paymentTokenRepository->shouldReceive('all_for_user_id') + ->andReturn([$token]); + + $purchaseUnit = Mockery::mock(PurchaseUnit::class); + $this->purchaseUnitFactory->shouldReceive('from_wc_order') + ->andReturn($purchaseUnit); + + $customer = Mockery::mock(WC_Customer::class); + + $payer = Mockery::mock(Payer::class); + $this->payerFactory->shouldReceive('from_customer') + ->andReturn($payer); + $this->shippingPreferenceFactory->shouldReceive('from_state') + ->andReturn('some_preference'); + + $order = Mockery::mock(Order::class); + $order->shouldReceive('id')->andReturn('1'); + $order->shouldReceive('intent')->andReturn('CAPTURE'); + $paymentSource = Mockery::mock(PaymentSource::class); + $paymentSourceCard = Mockery::mock(PaymentSourceCard::class); + $paymentSource->shouldReceive('card')->andReturn($paymentSourceCard); + $order->shouldReceive('payment_source')->andReturn($paymentSource); + $orderStatus = Mockery::mock(OrderStatus::class); + $orderStatus->shouldReceive('is')->andReturn(true); + $order->shouldReceive('status')->andReturn($orderStatus); + + $order->shouldReceive('purchase_units')->andReturn([$purchaseUnit]); + $payments = Mockery::mock(Payments::class); + $capture = Mockery::mock(Capture::class); + $capture->shouldReceive('id')->andReturn('1'); + $captureStatus = Mockery::mock(CaptureStatus::class); + $captureStatus->shouldReceive('details')->andReturn(null); + $captureStatus->shouldReceive('name')->andReturn(CaptureStatus::COMPLETED); + $capture->shouldReceive('status')->andReturn($captureStatus); + $payments->shouldReceive('captures')->andReturn([$capture]); + $purchaseUnit->shouldReceive('payments')->andReturn($payments); + + $this->orderEndpoint->shouldReceive('create') + ->with([$purchaseUnit], 'some_preference', $payer, $token) + ->andReturn($order); + + $this->environment->shouldReceive('current_environment_is')->andReturn(true); + + $this->config->shouldReceive('has')->andReturn(false); + + $result = $this->testee->handle_payment($tokenId, $wcOrder, $customer); + $this->assertInstanceOf(\WC_Order::class, $result); + } +} From 76a81b535b1006d293b0ada4adff953c7b64ab2f Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 12 Aug 2022 10:38:42 +0200 Subject: [PATCH 141/145] Get payer from order instead of customer --- modules/ppcp-vaulting/src/VaultedCreditCardHandler.php | 6 ++---- modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php | 5 +---- tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php | 2 +- tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php | 1 + 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php index dd0b93224..7f305ba45 100644 --- a/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php +++ b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php @@ -118,8 +118,7 @@ class VaultedCreditCardHandler public function handle_payment( string $saved_credit_card, - WC_Order $wc_order, - WC_Customer $customer + WC_Order $wc_order ): WC_Order { $change_payment = filter_input( INPUT_POST, 'woocommerce_change_payment', FILTER_SANITIZE_STRING ); @@ -146,8 +145,7 @@ class VaultedCreditCardHandler } $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); - $payer = $this->payer_factory->from_customer( $customer ); - + $payer = $this->payer_factory->from_wc_order( $wc_order); $shipping_preference = $this->shipping_preference_factory->from_state( $purchase_unit, '' diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index 8578ac072..6b269f4fe 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -361,12 +361,9 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { $saved_credit_card = filter_input( INPUT_POST, 'saved_credit_card', FILTER_SANITIZE_STRING ); if($saved_credit_card) { try { - $customer = new WC_Customer( $wc_order->get_customer_id() ); - $wc_order = $this->vaulted_credit_card_handler->handle_payment( $saved_credit_card, - $wc_order, - $customer + $wc_order ); return $this->handle_payment_success( $wc_order ); diff --git a/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php b/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php index 7060bf8be..379b00f00 100644 --- a/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php +++ b/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php @@ -105,7 +105,7 @@ class VaultedCreditCardHandlerTest extends TestCase $customer = Mockery::mock(WC_Customer::class); $payer = Mockery::mock(Payer::class); - $this->payerFactory->shouldReceive('from_customer') + $this->payerFactory->shouldReceive('from_wc_order') ->andReturn($payer); $this->shippingPreferenceFactory->shouldReceive('from_state') ->andReturn('some_preference'); diff --git a/tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php index 4f751a924..e6f9c5697 100644 --- a/tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php +++ b/tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php @@ -90,6 +90,7 @@ class CreditCardGatewayTest extends TestCase public function testProcessPaymentVaultedCard() { $wc_order = Mockery::mock(WC_Order::class); + $wc_order->shouldReceive('get_customer_id')->andReturn(1); when('wc_get_order')->justReturn($wc_order); $savedCreditCard = 'abc123'; From 28d7e5d77182277eb1ddeff8e17c0dc0d47b7d46 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 12 Aug 2022 10:56:25 +0200 Subject: [PATCH 142/145] Fix phpcs --- modules/ppcp-vaulting/services.php | 6 +- .../src/VaultedCreditCardHandler.php | 65 +++++++++++++------ modules/ppcp-wc-gateway/services.php | 2 +- .../src/Gateway/CreditCardGateway.php | 52 ++++++++------- 4 files changed, 75 insertions(+), 50 deletions(-) diff --git a/modules/ppcp-vaulting/services.php b/modules/ppcp-vaulting/services.php index a0ad2d814..041260fd0 100644 --- a/modules/ppcp-vaulting/services.php +++ b/modules/ppcp-vaulting/services.php @@ -57,10 +57,10 @@ return array( $container->get( 'woocommerce.logger.woocommerce' ) ); }, - 'vaulting.credit-card-handler' => function(ContainerInterface $container): VaultedCreditCardHandler { + 'vaulting.credit-card-handler' => function( ContainerInterface $container ): VaultedCreditCardHandler { return new VaultedCreditCardHandler( - $container->get('subscription.helper'), - $container->get('vaulting.repository.payment-token'), + $container->get( 'subscription.helper' ), + $container->get( 'vaulting.repository.payment-token' ), $container->get( 'api.factory.purchase-unit' ), $container->get( 'api.factory.payer' ), $container->get( 'api.factory.shipping-preference' ), diff --git a/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php index 7f305ba45..52c3366d0 100644 --- a/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php +++ b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php @@ -26,8 +26,11 @@ use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait; -class VaultedCreditCardHandler -{ +/** + * Class VaultedCreditCardHandler + */ +class VaultedCreditCardHandler { + use OrderMetaTrait, TransactionIdHandlingTrait, PaymentsStatusHandlingTrait, FreeTrialHandlerTrait; /** @@ -93,6 +96,19 @@ class VaultedCreditCardHandler */ protected $config; + /** + * VaultedCreditCardHandler constructor + * + * @param SubscriptionHelper $subscription_helper The subscription helper. + * @param PaymentTokenRepository $payment_token_repository The payment token repository. + * @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory. + * @param PayerFactory $payer_factory The payer factory. + * @param ShippingPreferenceFactory $shipping_preference_factory The shipping_preference factory. + * @param OrderEndpoint $order_endpoint The order endpoint. + * @param Environment $environment The environment. + * @param AuthorizedPaymentsProcessor $authorized_payments_processor The processor for authorized payments. + * @param ContainerInterface $config The settings. + */ public function __construct( SubscriptionHelper $subscription_helper, PaymentTokenRepository $payment_token_repository, @@ -103,26 +119,33 @@ class VaultedCreditCardHandler Environment $environment, AuthorizedPaymentsProcessor $authorized_payments_processor, ContainerInterface $config - ) - { - $this->subscription_helper = $subscription_helper; - $this->payment_token_repository = $payment_token_repository; - $this->purchase_unit_factory = $purchase_unit_factory; - $this->payer_factory = $payer_factory; - $this->shipping_preference_factory = $shipping_preference_factory; - $this->order_endpoint = $order_endpoint; - $this->environment = $environment; + ) { + $this->subscription_helper = $subscription_helper; + $this->payment_token_repository = $payment_token_repository; + $this->purchase_unit_factory = $purchase_unit_factory; + $this->payer_factory = $payer_factory; + $this->shipping_preference_factory = $shipping_preference_factory; + $this->order_endpoint = $order_endpoint; + $this->environment = $environment; $this->authorized_payments_processor = $authorized_payments_processor; - $this->config = $config; + $this->config = $config; } + /** + * Handles the saved credit card payment. + * + * @param string $saved_credit_card The saved credit card. + * @param WC_Order $wc_order The WC order. + * @return WC_Order + * @throws RuntimeException When something went wrong with the payment process. + */ public function handle_payment( string $saved_credit_card, WC_Order $wc_order ): WC_Order { - $change_payment = filter_input( INPUT_POST, 'woocommerce_change_payment', FILTER_SANITIZE_STRING ); - if($change_payment) { + $change_payment = filter_input( INPUT_POST, 'woocommerce_change_payment', FILTER_SANITIZE_STRING ); + if ( $change_payment ) { if ( $this->subscription_helper->has_subscription( $wc_order->get_id() ) && $this->subscription_helper->is_subscription_change_payment() ) { if ( $saved_credit_card ) { update_post_meta( $wc_order->get_id(), 'payment_token_id', $saved_credit_card ); @@ -132,7 +155,7 @@ class VaultedCreditCardHandler } } - $tokens = $this->payment_token_repository->all_for_user_id( $wc_order->get_customer_id() ); + $tokens = $this->payment_token_repository->all_for_user_id( $wc_order->get_customer_id() ); $selected_token = null; foreach ( $tokens as $token ) { if ( $token->id() === $saved_credit_card ) { @@ -141,11 +164,11 @@ class VaultedCreditCardHandler } } if ( ! $selected_token ) { - throw new RuntimeException('Saved card token not found.'); + throw new RuntimeException( 'Saved card token not found.' ); } - $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); - $payer = $this->payer_factory->from_wc_order( $wc_order); + $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); + $payer = $this->payer_factory->from_wc_order( $wc_order ); $shipping_preference = $this->shipping_preference_factory->from_state( $purchase_unit, '' @@ -162,7 +185,7 @@ class VaultedCreditCardHandler $this->add_paypal_meta( $wc_order, $order, $this->environment ); if ( ! $order->status()->is( OrderStatus::COMPLETED ) ) { - throw new RuntimeException("Unexpected status for order {$order->id()} using a saved card: {$order->status()->name()}."); + throw new RuntimeException( "Unexpected status for order {$order->id()} using a saved card: {$order->status()->name()}." ); } if ( ! in_array( @@ -170,7 +193,7 @@ class VaultedCreditCardHandler array( 'CAPTURE', 'AUTHORIZE' ), true ) ) { - throw new RuntimeException("Could neither capture nor authorize order {$order->id()} using a saved card. Status: {$order->status()->name()}. Intent: {$order->intent()}."); + throw new RuntimeException( "Could neither capture nor authorize order {$order->id()} using a saved card. Status: {$order->status()->name()}. Intent: {$order->intent()}." ); } if ( $order->intent() === 'AUTHORIZE' ) { @@ -194,7 +217,7 @@ class VaultedCreditCardHandler return $wc_order; } catch ( RuntimeException $error ) { - throw new RuntimeException($error->getMessage()); + throw new RuntimeException( $error->getMessage() ); } } } diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index de88b4bb3..5bd0ffadb 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -104,7 +104,7 @@ return array( $subscription_helper = $container->get( 'subscription.helper' ); $payments_endpoint = $container->get( 'api.endpoint.payments' ); $logger = $container->get( 'woocommerce.logger.woocommerce' ); - $vaulted_credit_card_handler = $container->get('vaulting.credit-card-handler'); + $vaulted_credit_card_handler = $container->get( 'vaulting.credit-card-handler' ); return new CreditCardGateway( $settings_renderer, $order_processor, diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index 6b269f4fe..de1aa105e 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -58,6 +58,8 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { protected $config; /** + * The vaulted credit card handler. + * * @var VaultedCreditCardHandler */ protected $vaulted_credit_card_handler; @@ -128,17 +130,17 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { /** * CreditCardGateway constructor. * - * @param SettingsRenderer $settings_renderer The Settings Renderer. - * @param OrderProcessor $order_processor The Order processor. - * @param ContainerInterface $config The settings. - * @param string $module_url The URL to the module. - * @param SessionHandler $session_handler The Session Handler. - * @param RefundProcessor $refund_processor The refund processor. - * @param State $state The state. - * @param TransactionUrlProvider $transaction_url_provider Service able to provide view transaction url base. - * @param SubscriptionHelper $subscription_helper The subscription helper. - * @param LoggerInterface $logger The logger. - * @param PaymentsEndpoint $payments_endpoint The payments endpoint. + * @param SettingsRenderer $settings_renderer The Settings Renderer. + * @param OrderProcessor $order_processor The Order processor. + * @param ContainerInterface $config The settings. + * @param string $module_url The URL to the module. + * @param SessionHandler $session_handler The Session Handler. + * @param RefundProcessor $refund_processor The refund processor. + * @param State $state The state. + * @param TransactionUrlProvider $transaction_url_provider Service able to provide view transaction url base. + * @param SubscriptionHelper $subscription_helper The subscription helper. + * @param LoggerInterface $logger The logger. + * @param PaymentsEndpoint $payments_endpoint The payments endpoint. * @param VaultedCreditCardHandler $vaulted_credit_card_handler The vaulted credit card handler. */ public function __construct( @@ -155,18 +157,18 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { PaymentsEndpoint $payments_endpoint, VaultedCreditCardHandler $vaulted_credit_card_handler ) { - $this->id = self::ID; - $this->settings_renderer = $settings_renderer; - $this->order_processor = $order_processor; - $this->config = $config; - $this->module_url = $module_url; - $this->session_handler = $session_handler; - $this->refund_processor = $refund_processor; - $this->state = $state; - $this->transaction_url_provider = $transaction_url_provider; - $this->subscription_helper = $subscription_helper; - $this->logger = $logger; - $this->payments_endpoint = $payments_endpoint; + $this->id = self::ID; + $this->settings_renderer = $settings_renderer; + $this->order_processor = $order_processor; + $this->config = $config; + $this->module_url = $module_url; + $this->session_handler = $session_handler; + $this->refund_processor = $refund_processor; + $this->state = $state; + $this->transaction_url_provider = $transaction_url_provider; + $this->subscription_helper = $subscription_helper; + $this->logger = $logger; + $this->payments_endpoint = $payments_endpoint; $this->vaulted_credit_card_handler = $vaulted_credit_card_handler; if ( $state->current_state() === State::STATE_ONBOARDED ) { @@ -359,7 +361,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { * If customer has chosen a saved credit card payment. */ $saved_credit_card = filter_input( INPUT_POST, 'saved_credit_card', FILTER_SANITIZE_STRING ); - if($saved_credit_card) { + if ( $saved_credit_card ) { try { $wc_order = $this->vaulted_credit_card_handler->handle_payment( $saved_credit_card, @@ -368,7 +370,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { return $this->handle_payment_success( $wc_order ); - } catch(RuntimeException $error) { + } catch ( RuntimeException $error ) { return $this->handle_payment_failure( $wc_order, $error ); } } From 03fa159b43b69cec9dce18d5240379f02762eccf Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 12 Aug 2022 11:02:29 +0200 Subject: [PATCH 143/145] Consolidate conditional --- .../src/VaultedCreditCardHandler.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php index 52c3366d0..172837633 100644 --- a/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php +++ b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php @@ -145,14 +145,14 @@ class VaultedCreditCardHandler { ): WC_Order { $change_payment = filter_input( INPUT_POST, 'woocommerce_change_payment', FILTER_SANITIZE_STRING ); - if ( $change_payment ) { - if ( $this->subscription_helper->has_subscription( $wc_order->get_id() ) && $this->subscription_helper->is_subscription_change_payment() ) { - if ( $saved_credit_card ) { - update_post_meta( $wc_order->get_id(), 'payment_token_id', $saved_credit_card ); - - return $wc_order; - } - } + if ( + $change_payment + && $this->subscription_helper->has_subscription( $wc_order->get_id() ) + && $this->subscription_helper->is_subscription_change_payment() + && $saved_credit_card + ) { + update_post_meta( $wc_order->get_id(), 'payment_token_id', $saved_credit_card ); + return $wc_order; } $tokens = $this->payment_token_repository->all_for_user_id( $wc_order->get_customer_id() ); From ce67d7801135b38daa76963648febb3fe6d40a5a Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 16 Aug 2022 11:01:13 +0200 Subject: [PATCH 144/145] Fix stub method --- tests/stubs/WC_Payment_Gateway.php | 4 ++-- tests/stubs/WC_Payment_Gateway_CC.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/stubs/WC_Payment_Gateway.php b/tests/stubs/WC_Payment_Gateway.php index bb87ee010..12a9fbf7b 100644 --- a/tests/stubs/WC_Payment_Gateway.php +++ b/tests/stubs/WC_Payment_Gateway.php @@ -12,8 +12,8 @@ class WC_Payment_Gateway } - protected function get_return_url($wcOrder) { - return $wcOrder; + protected function get_return_url($order = null) { + return ''; } public function process_admin_options() { diff --git a/tests/stubs/WC_Payment_Gateway_CC.php b/tests/stubs/WC_Payment_Gateway_CC.php index 7c7b26df6..42cae586b 100644 --- a/tests/stubs/WC_Payment_Gateway_CC.php +++ b/tests/stubs/WC_Payment_Gateway_CC.php @@ -6,7 +6,7 @@ class WC_Payment_Gateway_CC public function init_settings() {} public function process_admin_options() {} - protected function get_return_url($wcOrder) { - return $wcOrder; + protected function get_return_url($order = null) { + return ''; } } From 9454ca4f16bd7f4f0f3e07c4b5dbca1f03cc9130 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 16 Aug 2022 11:06:18 +0200 Subject: [PATCH 145/145] Leave it as it was for now --- tests/stubs/WC_Payment_Gateway.php | 4 ++-- tests/stubs/WC_Payment_Gateway_CC.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/stubs/WC_Payment_Gateway.php b/tests/stubs/WC_Payment_Gateway.php index 12a9fbf7b..bb87ee010 100644 --- a/tests/stubs/WC_Payment_Gateway.php +++ b/tests/stubs/WC_Payment_Gateway.php @@ -12,8 +12,8 @@ class WC_Payment_Gateway } - protected function get_return_url($order = null) { - return ''; + protected function get_return_url($wcOrder) { + return $wcOrder; } public function process_admin_options() { diff --git a/tests/stubs/WC_Payment_Gateway_CC.php b/tests/stubs/WC_Payment_Gateway_CC.php index 42cae586b..7c7b26df6 100644 --- a/tests/stubs/WC_Payment_Gateway_CC.php +++ b/tests/stubs/WC_Payment_Gateway_CC.php @@ -6,7 +6,7 @@ class WC_Payment_Gateway_CC public function init_settings() {} public function process_admin_options() {} - protected function get_return_url($order = null) { - return ''; + protected function get_return_url($wcOrder) { + return $wcOrder; } }