From 539e211e6ba362c9832bedeaf9ee4edecd8dcf5e Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 23 Oct 2023 20:59:49 +0300 Subject: [PATCH 01/16] Fill form when continuation in block --- modules/ppcp-blocks/resources/js/checkout-block.js | 14 ++++++++++++++ modules/ppcp-blocks/src/PayPalPaymentMethod.php | 5 +++++ 2 files changed, 19 insertions(+) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index 85bcd278f..df9e524ad 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -24,6 +24,20 @@ const PayPalComponent = ({ const [paypalOrder, setPaypalOrder] = useState(null); + useEffect(() => { + // fill the form if in continuation (for product or mini-cart buttons) + if (!config.scriptData.continuation || !config.scriptData.continuation.order || window.ppcpContinuationFilled) { + return; + } + const addresses = paypalOrderToWcAddresses(config.scriptData.continuation.order); + wp.data.dispatch('wc/store/cart').setBillingAddress(addresses.billingAddress); + if (shippingData.needsShipping) { + wp.data.dispatch('wc/store/cart').setShippingAddress(addresses.shippingAddress); + } + // this useEffect should run only once, but adding this in case of some kind of full re-rendering + window.ppcpContinuationFilled = true; + }, []) + const [loaded, setLoaded] = useState(false); useEffect(() => { if (!loaded) { diff --git a/modules/ppcp-blocks/src/PayPalPaymentMethod.php b/modules/ppcp-blocks/src/PayPalPaymentMethod.php index 1da58914d..3c407f2a2 100644 --- a/modules/ppcp-blocks/src/PayPalPaymentMethod.php +++ b/modules/ppcp-blocks/src/PayPalPaymentMethod.php @@ -167,6 +167,11 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { $script_data['continuation']['cancel'] = array( 'html' => $this->cancellation_view->render_session_cancellation( $url, $this->session_handler->funding_source() ), ); + + $order = $this->session_handler->order(); + if ( $order ) { + $script_data['continuation']['order'] = $order->to_array(); + } } return array( From 14f54e1df1fbeb88338cd1cd1a03fc3c320cfcd3 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 24 Oct 2023 10:22:55 +0300 Subject: [PATCH 02/16] Default to wc data when filling checkout --- modules/ppcp-blocks/package.json | 3 ++- modules/ppcp-blocks/resources/js/checkout-block.js | 7 ++++++- modules/ppcp-blocks/yarn.lock | 5 +++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-blocks/package.json b/modules/ppcp-blocks/package.json index 53c08b7bb..501915e75 100644 --- a/modules/ppcp-blocks/package.json +++ b/modules/ppcp-blocks/package.json @@ -10,7 +10,8 @@ "Edge >= 14" ], "dependencies": { - "core-js": "^3.25.0" + "core-js": "^3.25.0", + "deepmerge": "^4.3.1" }, "devDependencies": { "@babel/core": "^7.19", diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index df9e524ad..314c06054 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -3,6 +3,7 @@ import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/ import {paypalAddressToWc, paypalOrderToWcAddresses} from "./Helper/Address"; import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading' import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher"; +import merge from "deepmerge"; const config = wc.wcSettings.getSetting('ppcp-gateway_data'); @@ -29,7 +30,11 @@ const PayPalComponent = ({ if (!config.scriptData.continuation || !config.scriptData.continuation.order || window.ppcpContinuationFilled) { return; } - const addresses = paypalOrderToWcAddresses(config.scriptData.continuation.order); + const paypalAddresses = paypalOrderToWcAddresses(config.scriptData.continuation.order); + const wcAddresses = wp.data.select('wc/store/cart').getCustomerData(); + const addresses = merge(wcAddresses, paypalAddresses, { + customMerge: key => (a, b) => a ? a : b, // overwrite empty strings + }); wp.data.dispatch('wc/store/cart').setBillingAddress(addresses.billingAddress); if (shippingData.needsShipping) { wp.data.dispatch('wc/store/cart').setShippingAddress(addresses.shippingAddress); diff --git a/modules/ppcp-blocks/yarn.lock b/modules/ppcp-blocks/yarn.lock index 209adc4a4..161d649ba 100644 --- a/modules/ppcp-blocks/yarn.lock +++ b/modules/ppcp-blocks/yarn.lock @@ -1425,6 +1425,11 @@ debug@^4.1.0, debug@^4.1.1: dependencies: ms "2.1.2" +deepmerge@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + electron-to-chromium@^1.4.251: version "1.4.284" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" From a416033c6012ba073aaa420cff884d424a5abd94 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 25 Oct 2023 09:36:56 +0300 Subject: [PATCH 03/16] Fix card button subscription support declaration --- .../src/Gateway/CardButtonGateway.php | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php index fecb495b6..010df9e9b 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php @@ -171,13 +171,17 @@ class CardButtonGateway extends \WC_Payment_Gateway { $this->payment_token_repository = $payment_token_repository; $this->logger = $logger; - if ( $this->onboarded ) { - $this->supports = array( 'refunds' ); - } - if ( $this->gateways_enabled() ) { - $this->supports = array( - 'refunds', - 'products', + $this->supports = array( + 'refunds', + 'products', + ); + + if ( + ( $this->config->has( 'vault_enabled' ) && $this->config->get( 'vault_enabled' ) ) + || ( $this->config->has( 'subscriptions_mode' ) && $this->config->get( 'subscriptions_mode' ) === 'subscriptions_api' ) + ) { + array_push( + $this->supports, 'subscriptions', 'subscription_cancellation', 'subscription_suspension', @@ -187,7 +191,7 @@ class CardButtonGateway extends \WC_Payment_Gateway { 'subscription_payment_method_change', 'subscription_payment_method_change_customer', 'subscription_payment_method_change_admin', - 'multiple_subscriptions', + 'multiple_subscriptions' ); } From 251d6c6053948f961d6227c60519c84abe98737b Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 25 Oct 2023 10:45:56 +0300 Subject: [PATCH 04/16] Remove unused --- .../src/Gateway/ProcessPaymentTrait.php | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php index 7f752ef18..3cf401b29 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php +++ b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php @@ -18,21 +18,6 @@ use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException; * Trait ProcessPaymentTrait */ trait ProcessPaymentTrait { - /** - * Checks if PayPal or Credit Card gateways are enabled. - * - * @return bool Whether any of the gateways is enabled. - */ - protected function gateways_enabled(): bool { - if ( $this->config->has( 'enabled' ) && $this->config->get( 'enabled' ) ) { - return true; - } - if ( $this->config->has( 'dcc_enabled' ) && $this->config->get( 'dcc_enabled' ) ) { - return true; - } - return false; - } - /** * Handles the payment failure. * From 3d293058fb03d36fec8432c68316407dca679f8b Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 25 Oct 2023 17:52:00 +0100 Subject: [PATCH 05/16] Add Support for product variants on ApplePay --- .../resources/js/ApplepayButton.js | 23 +- .../js/Context/SingleProductHandler.js | 12 +- modules/ppcp-applepay/services.php | 6 +- .../src/Assets/ApplePayButton.php | 40 ++- .../src/Assets/ApplePayDataObjectHttp.php | 116 ++++++- .../src/Assets/PropertiesDictionary.php | 40 +-- modules/ppcp-button/services.php | 31 +- .../src/Endpoint/AbstractCartEndpoint.php | 186 +----------- .../src/Endpoint/ChangeCartEndpoint.php | 9 +- .../src/Endpoint/SimulateCartEndpoint.php | 16 +- .../src/Helper/CartProductsHelper.php | 286 ++++++++++++++++++ 11 files changed, 519 insertions(+), 246 deletions(-) create mode 100644 modules/ppcp-button/src/Helper/CartProductsHelper.php diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 15615d7ee..f33c6e6d5 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -23,9 +23,6 @@ class ApplepayButton { this.ppcpConfig ); - //PRODUCT DETAIL PAGE - this.refreshContextData(); - this.updated_contact_info = [] this.selectedShippingMethod = [] this.nonce = document.getElementById('woocommerce-process-checkout-nonce').value @@ -35,6 +32,21 @@ class ApplepayButton { console.log('[ApplePayButton]', ...arguments); } } + + //PRODUCT DETAIL PAGE + this.refreshContextData(); + + if (this.context === 'product') { + jQuery(document).on('appleclick', () => { + (this.onshippingcontactselected())({ + shippingContact: { + locality: 'New York', + postalCode: '10001', + countryCode: 'US' + } + }); + }); + } } init(config) { @@ -260,6 +272,8 @@ class ApplepayButton { case 'product': // Refresh product data that makes the price change. this.productQuantity = document.querySelector('input.qty').value; + this.products = this.contextHandler.products(); + this.log('Products updated', this.products); break; } } @@ -389,6 +403,7 @@ class ApplepayButton { return { action: 'ppcp_update_shipping_contact', product_id: product_id, + products: JSON.stringify(this.products), caller_page: 'productDetail', product_quantity: this.productQuantity, simplified_contact: event.shippingContact, @@ -419,6 +434,7 @@ class ApplepayButton { action: 'ppcp_update_shipping_method', shipping_method: event.shippingMethod, product_id: product_id, + products: JSON.stringify(this.products), caller_page: 'productDetail', product_quantity: this.productQuantity, simplified_contact: this.updated_contact_info, @@ -456,6 +472,7 @@ class ApplepayButton { action: 'ppcp_create_order', 'caller_page': this.context, 'product_id': this.buttonConfig.product.id ?? null, + 'products': JSON.stringify(this.products), 'product_quantity': this.productQuantity ?? null, 'shipping_contact': shippingContact, 'billing_contact': billingContact, diff --git a/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js b/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js index ddb7ad531..f9adb7774 100644 --- a/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js @@ -49,12 +49,20 @@ class SingleProductHandler extends BaseHandler { } createOrder() { + return this.actionHandler().configuration().createOrder(); + } + + products() { + return this.actionHandler().getProducts(); + } + + actionHandler() { const errorHandler = new ErrorHandler( this.ppcpConfig.labels.error.generic, document.querySelector('.woocommerce-notices-wrapper') ); - const actionHandler = new SingleProductActionHandler( + return new SingleProductActionHandler( this.ppcpConfig, new UpdateCart( this.ppcpConfig.ajax.change_cart.endpoint, @@ -63,8 +71,6 @@ class SingleProductHandler extends BaseHandler { document.querySelector('form.cart'), errorHandler, ); - - return actionHandler.configuration().createOrder(); } } diff --git a/modules/ppcp-applepay/services.php b/modules/ppcp-applepay/services.php index 919f6a25b..6274a5fb8 100644 --- a/modules/ppcp-applepay/services.php +++ b/modules/ppcp-applepay/services.php @@ -100,7 +100,6 @@ return array( return new DataToAppleButtonScripts( $container->get( 'applepay.sdk_script_url' ), $container->get( 'wcgateway.settings' ) ); }, 'applepay.button' => static function ( ContainerInterface $container ): ApplePayButton { - return new ApplePayButton( $container->get( 'wcgateway.settings' ), $container->get( 'woocommerce.logger.woocommerce' ), @@ -108,8 +107,9 @@ return array( $container->get( 'applepay.url' ), $container->get( 'ppcp.asset-version' ), $container->get( 'applepay.data_to_scripts' ), - $container->get( 'wcgateway.settings.status' ) - ); + $container->get( 'wcgateway.settings.status' ), + $container->get( 'button.helper.cart-products' ) + ); }, 'applepay.blocks-payment-method' => static function ( ContainerInterface $container ): PaymentMethodTypeInterface { return new BlocksPaymentMethod( diff --git a/modules/ppcp-applepay/src/Assets/ApplePayButton.php b/modules/ppcp-applepay/src/Assets/ApplePayButton.php index 693c5bf04..fe7313a85 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayButton.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayButton.php @@ -14,6 +14,7 @@ use Psr\Log\LoggerInterface; use WC_Cart; use WC_Order; use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; +use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor; @@ -25,18 +26,21 @@ use WooCommerce\PayPalCommerce\Webhooks\Handler\RequestHandlerTrait; */ class ApplePayButton implements ButtonInterface { use RequestHandlerTrait; + /** * The settings. * * @var Settings */ private $settings; + /** * The logger. * * @var LoggerInterface */ private $logger; + /** * The response templates. * @@ -58,12 +62,14 @@ class ApplePayButton implements ButtonInterface { * @var string */ protected $id; + /** * The method title. * * @var string */ protected $method_title; + /** * The processor for orders. * @@ -84,18 +90,21 @@ class ApplePayButton implements ButtonInterface { * @var string */ private $version; + /** * The module URL. * * @var string */ private $module_url; + /** * The data to send to the ApplePay button script. * * @var DataToAppleButtonScripts */ private $script_data; + /** * The Settings status helper. * @@ -103,6 +112,13 @@ class ApplePayButton implements ButtonInterface { */ private $settings_status; + /** + * The cart products helper. + * + * @var CartProductsHelper + */ + protected $cart_products; + /** * PayPalPaymentMethod constructor. * @@ -113,6 +129,7 @@ class ApplePayButton implements ButtonInterface { * @param string $version The module version. * @param DataToAppleButtonScripts $data The data to send to the ApplePay button script. * @param SettingsStatus $settings_status The settings status helper. + * @param CartProductsHelper $cart_products The cart products helper. */ public function __construct( Settings $settings, @@ -121,7 +138,8 @@ class ApplePayButton implements ButtonInterface { string $module_url, string $version, DataToAppleButtonScripts $data, - SettingsStatus $settings_status + SettingsStatus $settings_status, + CartProductsHelper $cart_products ) { $this->settings = $settings; $this->response_templates = new ResponsesToApple(); @@ -133,6 +151,7 @@ class ApplePayButton implements ButtonInterface { $this->version = $version; $this->script_data = $data; $this->settings_status = $settings_status; + $this->cart_products = $cart_products; } /** @@ -822,15 +841,24 @@ class ApplePayButton implements ButtonInterface { * * @param ApplePayDataObjectHttp $applepay_request_data_object The request data object. * @return bool | string The cart item key after adding to the new cart. - * @throws \Exception If cannot be added to cart. + * @throws \Exception If it cannot be added to cart. */ public function prepare_cart( ApplePayDataObjectHttp $applepay_request_data_object ): string { $this->save_old_cart(); - $cart = WC()->cart; - return $cart->add_to_cart( - (int) $applepay_request_data_object->product_id(), - (int) $applepay_request_data_object->product_quantity() + $this->cart_products->set_cart( WC()->cart ); + + $product = $this->cart_products->product_from_data( + array( + 'id' => (int) $applepay_request_data_object->product_id(), + 'quantity' => (int) $applepay_request_data_object->product_quantity(), + 'variations' => $applepay_request_data_object->product_variations(), + 'extra' => $applepay_request_data_object->product_extra(), + 'booking' => $applepay_request_data_object->product_booking(), + ) ); + + $this->cart_products->add_products( array( $product ) ); + return $this->cart_products->cart_item_keys()[0]; } /** diff --git a/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php b/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php index ddc05a9fe..ab000a0e1 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php @@ -38,13 +38,6 @@ class ApplePayDataObjectHttp { */ protected $need_shipping; - /** - * The product id. - * - * @var mixed - */ - protected $product_id = ''; - /** * The caller page. * @@ -52,6 +45,13 @@ class ApplePayDataObjectHttp { */ protected $caller_page; + /** + * The product id. + * + * @var mixed + */ + protected $product_id = ''; + /** * The product quantity. * @@ -59,6 +59,27 @@ class ApplePayDataObjectHttp { */ protected $product_quantity = ''; + /** + * The product variations. + * + * @var string + */ + protected $product_variations = array(); + + /** + * The product extra. + * + * @var string + */ + protected $product_extra = array(); + + /** + * The product booking. + * + * @var string + */ + protected $product_booking = array(); + /** * The shipping methods. * @@ -166,6 +187,9 @@ class ApplePayDataObjectHttp { if ( ! $data ) { return; } + + $data = $this->preprocess_request_data( $data ); + $result = $this->update_required_data( $data, PropertiesDictionary::UPDATE_CONTACT_SINGLE_PROD_REQUIRED_FIELDS, @@ -198,6 +222,9 @@ class ApplePayDataObjectHttp { if ( ! $data ) { return; } + + $data = $this->preprocess_request_data( $data ); + $result = $this->update_required_data( $data, PropertiesDictionary::UPDATE_METHOD_SINGLE_PROD_REQUIRED_FIELDS, @@ -226,6 +253,9 @@ class ApplePayDataObjectHttp { if ( ! $data ) { return; } + + $data = $this->preprocess_request_data( $data ); + $data[ PropertiesDictionary::CALLER_PAGE ] = $caller_page; $result = $this->update_required_data( $data, @@ -261,6 +291,27 @@ class ApplePayDataObjectHttp { $this->update_shipping_method( $data ); } + /** + * Pre-processes request data to transform it to a standard format. + * + * @param array $data + * @return array + */ + protected function preprocess_request_data( array $data ): array { + // Fill product variables if a products object is received. + if ( is_array( $data[ PropertiesDictionary::PRODUCTS ] ?? null ) ) { + $product = $data[ PropertiesDictionary::PRODUCTS ][0]; + + $data[ PropertiesDictionary::PRODUCT_ID ] = $product['id'] ?? 0; + $data[ PropertiesDictionary::PRODUCT_QUANTITY ] = $product['quantity'] ?? array(); + $data[ PropertiesDictionary::PRODUCT_VARIATIONS ] = $product['variations'] ?? array(); + $data[ PropertiesDictionary::PRODUCT_EXTRA ] = $product['extra'] ?? array(); + $data[ PropertiesDictionary::PRODUCT_BOOKING ] = $product['booking'] ?? array(); + } + unset( $data[ PropertiesDictionary::PRODUCTS ] ); + return $data; + } + /** * Checks if the array contains all required fields and if those * are not empty. @@ -300,7 +351,7 @@ class ApplePayDataObjectHttp { */ protected function assign_data_object_values( array $data ): void { foreach ( $data as $key => $value ) { - // Null values may give origin to type errors. If necessary replace condition this with a specialized field filter. + // Null values may give origin to type errors. If necessary replace this condition with a specialized field filter. if ( null === $value ) { continue; } @@ -547,6 +598,33 @@ class ApplePayDataObjectHttp { return $this->product_quantity; } + /** + * Returns the product variations. + * + * @return string + */ + public function product_variations(): array { + return $this->product_variations; + } + + /** + * Returns the product extra. + * + * @return string + */ + public function product_extra(): array { + return $this->product_extra; + } + + /** + * Returns the product booking. + * + * @return string + */ + public function product_booking(): array { + return $this->product_booking; + } + /** * Returns the nonce. * @@ -580,7 +658,7 @@ class ApplePayDataObjectHttp { * @return array|false|null */ public function get_filtered_request_data() { - return filter_input_array( + $data = filter_input_array( INPUT_POST, array( PropertiesDictionary::CALLER_PAGE => FILTER_SANITIZE_SPECIAL_CHARS, @@ -604,8 +682,28 @@ class ApplePayDataObjectHttp { ), PropertiesDictionary::PRODUCT_ID => FILTER_SANITIZE_NUMBER_INT, PropertiesDictionary::PRODUCT_QUANTITY => FILTER_SANITIZE_NUMBER_INT, + PropertiesDictionary::PRODUCT_VARIATIONS => array( + 'filter' => FILTER_SANITIZE_SPECIAL_CHARS, + 'flags' => FILTER_REQUIRE_ARRAY, + ), + PropertiesDictionary::PRODUCT_EXTRA => array( + 'filter' => FILTER_SANITIZE_SPECIAL_CHARS, + 'flags' => FILTER_REQUIRE_ARRAY, + ), + PropertiesDictionary::PRODUCT_BOOKING => array( + 'filter' => FILTER_SANITIZE_SPECIAL_CHARS, + 'flags' => FILTER_REQUIRE_ARRAY, + ), ) ); + + $products = json_decode( wp_unslash( $_POST[ PropertiesDictionary::PRODUCTS ] ?? '' ), true ); + + if ( $products ) { + $data[ PropertiesDictionary::PRODUCTS ] = $products; + } + + return $data; } /** diff --git a/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php b/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php index fdb374fb7..61e8aa006 100644 --- a/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php +++ b/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php @@ -14,7 +14,6 @@ namespace WooCommerce\PayPalCommerce\Applepay\Assets; */ class PropertiesDictionary { - public const BILLING_CONTACT_INVALID = 'billing Contact Invalid'; public const CREATE_ORDER_SINGLE_PROD_REQUIRED_FIELDS = @@ -62,18 +61,20 @@ class PropertiesDictionary { self::SIMPLIFIED_CONTACT, ); - public const PRODUCT_ID = 'product_id'; - - public const SIMPLIFIED_CONTACT = 'simplified_contact'; - - public const SHIPPING_METHOD = 'shipping_method'; - - public const SHIPPING_CONTACT = 'shipping_contact'; + public const PRODUCTS = 'products'; + public const PRODUCT_ID = 'product_id'; + public const PRODUCT_QUANTITY = 'product_quantity'; + public const PRODUCT_VARIATIONS = 'product_variations'; + public const PRODUCT_EXTRA = 'product_extra'; + public const PRODUCT_BOOKING = 'product_booking'; + public const SIMPLIFIED_CONTACT = 'simplified_contact'; + public const SHIPPING_METHOD = 'shipping_method'; + public const SHIPPING_CONTACT = 'shipping_contact'; public const SHIPPING_CONTACT_INVALID = 'shipping Contact Invalid'; + public const BILLING_CONTACT = 'billing_contact'; - public const NONCE = 'nonce'; - + public const NONCE = 'nonce'; public const WCNONCE = 'woocommerce-process-checkout-nonce'; public const CREATE_ORDER_CART_REQUIRED_FIELDS = @@ -83,25 +84,16 @@ class PropertiesDictionary { self::SHIPPING_CONTACT, ); - public const PRODUCT_QUANTITY = 'product_quantity'; - public const CALLER_PAGE = 'caller_page'; - public const BILLING_CONTACT = 'billing_contact'; - public const NEED_SHIPPING = 'need_shipping'; public const UPDATE_SHIPPING_CONTACT = 'ppcp_update_shipping_contact'; - - public const UPDATE_SHIPPING_METHOD = 'ppcp_update_shipping_method'; - - public const CREATE_ORDER = 'ppcp_create_order'; - - public const CREATE_ORDER_CART = 'ppcp_create_order_cart'; - - public const REDIRECT = 'ppcp_redirect'; - - public const VALIDATE = 'ppcp_validate'; + public const UPDATE_SHIPPING_METHOD = 'ppcp_update_shipping_method'; + public const CREATE_ORDER = 'ppcp_create_order'; + public const CREATE_ORDER_CART = 'ppcp_create_order_cart'; + public const REDIRECT = 'ppcp_redirect'; + public const VALIDATE = 'ppcp_validate'; /** * Returns the possible list of button colors. diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php index 5ca7fb38f..374f15910 100644 --- a/modules/ppcp-button/services.php +++ b/modules/ppcp-button/services.php @@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\Button; use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveSubscriptionEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\SimulateCartEndpoint; +use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper; use WooCommerce\PayPalCommerce\Button\Helper\CheckoutFormSaver; use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint; use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator; @@ -128,24 +129,24 @@ return array( if ( ! \WC()->cart ) { throw new RuntimeException( 'cant initialize endpoint at this moment' ); } - $smart_button = $container->get( 'button.smart-button' ); - $cart = WC()->cart; - $request_data = $container->get( 'button.request-data' ); - $data_store = \WC_Data_Store::load( 'product' ); - $logger = $container->get( 'woocommerce.logger.woocommerce' ); - return new SimulateCartEndpoint( $smart_button, $cart, $request_data, $data_store, $logger ); + $smart_button = $container->get( 'button.smart-button' ); + $cart = WC()->cart; + $request_data = $container->get( 'button.request-data' ); + $cart_products = $container->get( 'button.helper.cart-products' ); + $logger = $container->get( 'woocommerce.logger.woocommerce' ); + return new SimulateCartEndpoint( $smart_button, $cart, $request_data, $cart_products, $logger ); }, 'button.endpoint.change-cart' => static function ( ContainerInterface $container ): ChangeCartEndpoint { if ( ! \WC()->cart ) { throw new RuntimeException( 'cant initialize endpoint at this moment' ); } - $cart = WC()->cart; - $shipping = WC()->shipping(); - $request_data = $container->get( 'button.request-data' ); + $cart = WC()->cart; + $shipping = WC()->shipping(); + $request_data = $container->get( 'button.request-data' ); $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, $purchase_unit_factory, $data_store, $logger ); + $cart_products = $container->get( 'button.helper.cart-products' ); + $logger = $container->get( 'woocommerce.logger.woocommerce' ); + return new ChangeCartEndpoint( $cart, $shipping, $request_data, $purchase_unit_factory, $cart_products, $logger ); }, 'button.endpoint.create-order' => static function ( ContainerInterface $container ): CreateOrderEndpoint { $request_data = $container->get( 'button.request-data' ); @@ -251,6 +252,12 @@ return array( $container->get( 'woocommerce.logger.woocommerce' ) ); }, + + 'button.helper.cart-products' => static function ( ContainerInterface $container ): CartProductsHelper { + $data_store = \WC_Data_Store::load( 'product' ); + return new CartProductsHelper( $data_store ); + }, + 'button.helper.three-d-secure' => static function ( ContainerInterface $container ): ThreeDSecure { $logger = $container->get( 'woocommerce.logger.woocommerce' ); return new ThreeDSecure( $logger ); diff --git a/modules/ppcp-button/src/Endpoint/AbstractCartEndpoint.php b/modules/ppcp-button/src/Endpoint/AbstractCartEndpoint.php index 3133cf544..604c25124 100644 --- a/modules/ppcp-button/src/Endpoint/AbstractCartEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/AbstractCartEndpoint.php @@ -10,6 +10,7 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint; use Exception; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; +use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper; /** * Abstract Class AbstractCartEndpoint @@ -26,11 +27,11 @@ abstract class AbstractCartEndpoint implements EndpointInterface { protected $cart; /** - * The product data store. + * The cart products helper. * - * @var \WC_Data_Store + * @var CartProductsHelper */ - protected $product_data_store; + protected $cart_products; /** * The request data helper. @@ -53,13 +54,6 @@ abstract class AbstractCartEndpoint implements EndpointInterface { */ protected $logger_tag = ''; - /** - * The added cart item IDs - * - * @var array - */ - private $cart_item_keys = array(); - /** * The nonce. * @@ -110,44 +104,13 @@ abstract class AbstractCartEndpoint implements EndpointInterface { protected function add_products( array $products ): bool { $this->cart->empty_cart( false ); - $success = true; - foreach ( $products as $product ) { - - // Add extras to POST, they are usually added by custom plugins. - if ( $product['extra'] && is_array( $product['extra'] ) ) { - // Handle cases like field[]. - $query = http_build_query( $product['extra'] ); - parse_str( $query, $extra ); - - foreach ( $extra as $key => $value ) { - $_POST[ $key ] = $value; - } - } - - if ( $product['product']->is_type( 'booking' ) ) { - $success = $success && $this->add_booking_product( - $product['product'], - $product['booking'] - ); - } elseif ( $product['product']->is_type( 'variable' ) ) { - $success = $success && $this->add_variable_product( - $product['product'], - $product['quantity'], - $product['variations'] - ); - } else { - $success = $success && $this->add_product( - $product['product'], - $product['quantity'] - ); - } - } - - if ( ! $success ) { + try { + $this->cart_products->add_products( $products ); + } catch ( Exception $e ) { $this->handle_error(); } - return $success; + return true; } /** @@ -193,7 +156,7 @@ abstract class AbstractCartEndpoint implements EndpointInterface { */ protected function products_from_request() { $data = $this->request_data->read_request( $this->nonce() ); - $products = $this->products_from_data( $data ); + $products = $this->cart_products->products_from_data( $data ); if ( ! $products ) { wp_send_json_error( array( @@ -212,141 +175,12 @@ abstract class AbstractCartEndpoint implements EndpointInterface { return $products; } - /** - * Returns product information from a data array. - * - * @param array $data The data array. - * - * @return array|null - */ - protected function products_from_data( array $data ): ?array { - - $products = array(); - - if ( - ! isset( $data['products'] ) - || ! is_array( $data['products'] ) - ) { - return null; - } - foreach ( $data['products'] as $product ) { - if ( ! isset( $product['quantity'] ) || ! isset( $product['id'] ) ) { - return null; - } - - $wc_product = wc_get_product( (int) $product['id'] ); - - if ( ! $wc_product ) { - return null; - } - $products[] = array( - 'product' => $wc_product, - 'quantity' => (int) $product['quantity'], - 'variations' => $product['variations'] ?? null, - 'booking' => $product['booking'] ?? null, - 'extra' => $product['extra'] ?? null, - ); - } - return $products; - } - - /** - * Adds a product to the cart. - * - * @param \WC_Product $product The Product. - * @param int $quantity The Quantity. - * - * @return bool - * @throws Exception When product could not be added. - */ - private function add_product( \WC_Product $product, int $quantity ): bool { - $cart_item_key = $this->cart->add_to_cart( $product->get_id(), $quantity ); - - if ( $cart_item_key ) { - $this->cart_item_keys[] = $cart_item_key; - } - return false !== $cart_item_key; - } - - /** - * Adds variations to the cart. - * - * @param \WC_Product $product The Product. - * @param int $quantity The Quantity. - * @param array $post_variations The variations. - * - * @return bool - * @throws Exception When product could not be added. - */ - private function add_variable_product( - \WC_Product $product, - int $quantity, - array $post_variations - ): bool { - - $variations = array(); - foreach ( $post_variations as $key => $value ) { - $variations[ $value['name'] ] = $value['value']; - } - - $variation_id = $this->product_data_store->find_matching_product_variation( $product, $variations ); - - // ToDo: Check stock status for variation. - $cart_item_key = $this->cart->add_to_cart( - $product->get_id(), - $quantity, - $variation_id, - $variations - ); - - if ( $cart_item_key ) { - $this->cart_item_keys[] = $cart_item_key; - } - return false !== $cart_item_key; - } - - /** - * Adds booking to the cart. - * - * @param \WC_Product $product The Product. - * @param array $data Data used by the booking plugin. - * - * @return bool - * @throws Exception When product could not be added. - */ - private function add_booking_product( - \WC_Product $product, - array $data - ): bool { - - if ( ! is_callable( 'wc_bookings_get_posted_data' ) ) { - return false; - } - - $cart_item_data = array( - 'booking' => wc_bookings_get_posted_data( $data, $product ), - ); - - $cart_item_key = $this->cart->add_to_cart( $product->get_id(), 1, 0, array(), $cart_item_data ); - - if ( $cart_item_key ) { - $this->cart_item_keys[] = $cart_item_key; - } - return false !== $cart_item_key; - } - /** * Removes stored cart items from WooCommerce cart. * * @return void */ protected function remove_cart_items(): void { - foreach ( $this->cart_item_keys as $cart_item_key ) { - if ( ! $cart_item_key ) { - continue; - } - $this->cart->remove_cart_item( $cart_item_key ); - } + $this->cart_products->remove_cart_items(); } - } diff --git a/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php b/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php index 95979d1b2..701076bed 100644 --- a/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php @@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint; use Exception; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; +use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper; /** * Class ChangeCartEndpoint @@ -41,7 +42,7 @@ class ChangeCartEndpoint extends AbstractCartEndpoint { * @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 CartProductsHelper $cart_products The cart products helper. * @param LoggerInterface $logger The logger. */ public function __construct( @@ -49,7 +50,7 @@ class ChangeCartEndpoint extends AbstractCartEndpoint { \WC_Shipping $shipping, RequestData $request_data, PurchaseUnitFactory $purchase_unit_factory, - \WC_Data_Store $product_data_store, + CartProductsHelper $cart_products, LoggerInterface $logger ) { @@ -57,7 +58,7 @@ class ChangeCartEndpoint extends AbstractCartEndpoint { $this->shipping = $shipping; $this->request_data = $request_data; $this->purchase_unit_factory = $purchase_unit_factory; - $this->product_data_store = $product_data_store; + $this->cart_products = $cart_products; $this->logger = $logger; $this->logger_tag = 'updating'; @@ -70,6 +71,8 @@ class ChangeCartEndpoint extends AbstractCartEndpoint { * @throws Exception On error. */ protected function handle_data(): bool { + $this->cart_products->set_cart( $this->cart ); + $products = $this->products_from_request(); if ( ! $products ) { diff --git a/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php b/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php index 25ac4ba91..e1b1247c4 100644 --- a/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php @@ -13,6 +13,7 @@ use Exception; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Entity\Money; use WooCommerce\PayPalCommerce\Button\Assets\SmartButton; +use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper; /** * Class SimulateCartEndpoint @@ -38,23 +39,23 @@ class SimulateCartEndpoint extends AbstractCartEndpoint { /** * ChangeCartEndpoint constructor. * - * @param SmartButton $smart_button The SmartButton. - * @param \WC_Cart $cart The current WC cart object. - * @param RequestData $request_data The request data helper. - * @param \WC_Data_Store $product_data_store The data store for products. - * @param LoggerInterface $logger The logger. + * @param SmartButton $smart_button The SmartButton. + * @param \WC_Cart $cart The current WC cart object. + * @param RequestData $request_data The request data helper. + * @param CartProductsHelper $cart_products The cart products helper. + * @param LoggerInterface $logger The logger. */ public function __construct( SmartButton $smart_button, \WC_Cart $cart, RequestData $request_data, - \WC_Data_Store $product_data_store, + CartProductsHelper $cart_products, LoggerInterface $logger ) { $this->smart_button = $smart_button; $this->cart = clone $cart; $this->request_data = $request_data; - $this->product_data_store = $product_data_store; + $this->cart_products = $cart_products; $this->logger = $logger; $this->logger_tag = 'simulation'; @@ -147,6 +148,7 @@ class SimulateCartEndpoint extends AbstractCartEndpoint { // Store a reference to the real cart. $this->real_cart = WC()->cart; WC()->cart = $this->cart; + $this->cart_products->set_cart( $this->cart ); } /** diff --git a/modules/ppcp-button/src/Helper/CartProductsHelper.php b/modules/ppcp-button/src/Helper/CartProductsHelper.php new file mode 100644 index 000000000..eec427825 --- /dev/null +++ b/modules/ppcp-button/src/Helper/CartProductsHelper.php @@ -0,0 +1,286 @@ +product_data_store = $product_data_store; + } + + /** + * Sets a new cart instance. + * + * @param WC_Cart $cart + * @return void + */ + public function set_cart( WC_Cart $cart ): void { + $this->cart = $cart; + } + + /** + * Returns products information from a data array. + * + * @param array $data The data array. + * + * @return array|null + */ + public function products_from_data( array $data ): ?array { + + $products = array(); + + if ( + ! isset( $data['products'] ) + || ! is_array( $data['products'] ) + ) { + return null; + } + foreach ( $data['products'] as $product ) { + $product = $this->products_from_data( $product ); + if ( $product ) { + $products[] = $product; + } + } + return $products; + } + + /** + * Returns product information from a data array. + * + * @param array $product + * @return array|null + */ + public function product_from_data( array $product ): ?array { + if ( ! isset( $product['quantity'] ) || ! isset( $product['id'] ) ) { + return null; + } + + $wc_product = wc_get_product( (int) $product['id'] ); + + if ( ! $wc_product ) { + return null; + } + return array( + 'product' => $wc_product, + 'quantity' => (int) $product['quantity'], + 'variations' => $product['variations'] ?? null, + 'booking' => $product['booking'] ?? null, + 'extra' => $product['extra'] ?? null, + ); + } + + /** + * Adds products to cart. + * + * @param array $products Array of products to be added to cart. + * @return bool + * @throws Exception Add to cart methods throw an exception on fail. + */ + public function add_products( array $products ): bool { + $success = true; + foreach ( $products as $product ) { + + // Add extras to POST, they are usually added by custom plugins. + if ( $product['extra'] && is_array( $product['extra'] ) ) { + // Handle cases like field[]. + $query = http_build_query( $product['extra'] ); + parse_str( $query, $extra ); + + foreach ( $extra as $key => $value ) { + $_POST[ $key ] = $value; + } + } + + if ( $product['product']->is_type( 'booking' ) ) { + $success = $success && $this->add_booking_product( + $product['product'], + $product['booking'] + ); + } elseif ( $product['product']->is_type( 'variable' ) ) { + $success = $success && $this->add_variable_product( + $product['product'], + $product['quantity'], + $product['variations'] + ); + } else { + $success = $success && $this->add_product( + $product['product'], + $product['quantity'] + ); + } + } + + if ( ! $success ) { + throw new Exception( 'Error adding products to cart.' ); + } + + return true; + } + + /** + * Adds a product to the cart. + * + * @param \WC_Product $product The Product. + * @param int $quantity The Quantity. + * + * @return bool + * @throws Exception When product could not be added. + */ + public function add_product( \WC_Product $product, int $quantity ): bool { + $this->validate_cart(); + + $cart_item_key = $this->cart->add_to_cart( $product->get_id(), $quantity ); + + if ( $cart_item_key ) { + $this->cart_item_keys[] = $cart_item_key; + } + return false !== $cart_item_key; + } + + /** + * Adds variations to the cart. + * + * @param \WC_Product $product The Product. + * @param int $quantity The Quantity. + * @param array $post_variations The variations. + * + * @return bool + * @throws Exception When product could not be added. + */ + public function add_variable_product( + \WC_Product $product, + int $quantity, + array $post_variations + ): bool { + $this->validate_cart(); + + $variations = array(); + foreach ( $post_variations as $key => $value ) { + $variations[ $value['name'] ] = $value['value']; + } + + $variation_id = $this->product_data_store->find_matching_product_variation( $product, $variations ); + + // ToDo: Check stock status for variation. + $cart_item_key = $this->cart->add_to_cart( + $product->get_id(), + $quantity, + $variation_id, + $variations + ); + + if ( $cart_item_key ) { + $this->cart_item_keys[] = $cart_item_key; + } + return false !== $cart_item_key; + } + + /** + * Adds booking to the cart. + * + * @param \WC_Product $product The Product. + * @param array $data Data used by the booking plugin. + * + * @return bool + * @throws Exception When product could not be added. + */ + public function add_booking_product( + \WC_Product $product, + array $data + ): bool { + $this->validate_cart(); + + if ( ! is_callable( 'wc_bookings_get_posted_data' ) ) { + return false; + } + + $cart_item_data = array( + 'booking' => wc_bookings_get_posted_data( $data, $product ), + ); + + $cart_item_key = $this->cart->add_to_cart( $product->get_id(), 1, 0, array(), $cart_item_data ); + + if ( $cart_item_key ) { + $this->cart_item_keys[] = $cart_item_key; + } + return false !== $cart_item_key; + } + + /** + * Removes stored cart items from WooCommerce cart. + * + * @return void + * @throws Exception + */ + public function remove_cart_items(): void { + $this->validate_cart(); + + foreach ( $this->cart_item_keys as $cart_item_key ) { + if ( ! $cart_item_key ) { + continue; + } + $this->cart->remove_cart_item( $cart_item_key ); + } + } + + /** + * @return array + */ + public function cart_item_keys(): array { + return $this->cart_item_keys; + } + + /** + * Throws the cart not set exception. + * + * @return void + * @throws Exception + */ + private function validate_cart(): void { + if ( ! $this->cart ) { + throw new Exception( 'Cart not set.' ); + } + } +} From 889df4d88c3d00da76d835b89e3265e175ca1dcc Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 25 Oct 2023 18:40:36 +0100 Subject: [PATCH 06/16] Fix ApplePay product page variants --- .../ppcp-applepay/resources/js/ApplepayButton.js | 13 ------------- .../src/Assets/ApplePayDataObjectHttp.php | 14 +++++++++++++- .../ppcp-button/src/Helper/CartProductsHelper.php | 2 +- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index f33c6e6d5..9aabf5476 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -33,20 +33,7 @@ class ApplepayButton { } } - //PRODUCT DETAIL PAGE this.refreshContextData(); - - if (this.context === 'product') { - jQuery(document).on('appleclick', () => { - (this.onshippingcontactselected())({ - shippingContact: { - locality: 'New York', - postalCode: '10001', - countryCode: 'US' - } - }); - }); - } } init(config) { diff --git a/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php b/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php index ab000a0e1..64b49f8f9 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php @@ -254,6 +254,7 @@ class ApplePayDataObjectHttp { return; } + $data = $this->append_products_to_data( $data, $_POST ); $data = $this->preprocess_request_data( $data ); $data[ PropertiesDictionary::CALLER_PAGE ] = $caller_page; @@ -697,7 +698,18 @@ class ApplePayDataObjectHttp { ) ); - $products = json_decode( wp_unslash( $_POST[ PropertiesDictionary::PRODUCTS ] ?? '' ), true ); + return $this->append_products_to_data( $data, $_POST ); + } + + /** + * Appends product to a data array. + * + * @param array $data The data. + * @param array $request_data The request data. + * @return array + */ + public function append_products_to_data( array $data, array $request_data ): array { + $products = json_decode( wp_unslash( $request_data[ PropertiesDictionary::PRODUCTS ] ?? '' ), true ); if ( $products ) { $data[ PropertiesDictionary::PRODUCTS ] = $products; diff --git a/modules/ppcp-button/src/Helper/CartProductsHelper.php b/modules/ppcp-button/src/Helper/CartProductsHelper.php index eec427825..506c968d6 100644 --- a/modules/ppcp-button/src/Helper/CartProductsHelper.php +++ b/modules/ppcp-button/src/Helper/CartProductsHelper.php @@ -78,7 +78,7 @@ class CartProductsHelper { return null; } foreach ( $data['products'] as $product ) { - $product = $this->products_from_data( $product ); + $product = $this->product_from_data( $product ); if ( $product ) { $products[] = $product; } From 46b47dc97b1140dc42f51209a5b364e81eca7ebc Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 26 Oct 2023 10:58:05 +0100 Subject: [PATCH 07/16] Fix lint and tests --- modules/ppcp-applepay/services.php | 2 +- modules/ppcp-applepay/src/ApplepayModule.php | 59 +++--- .../src/Assets/ApplePayDataObjectHttp.php | 18 +- modules/ppcp-button/services.php | 2 +- .../src/Endpoint/SimulateCartEndpoint.php | 10 +- .../src/Helper/CartProductsHelper.php | 55 +++-- .../ppcp-googlepay/src/GooglepayModule.php | 192 +++++++++--------- .../Endpoint/ChangeCartEndpointTest.php | 7 +- 8 files changed, 181 insertions(+), 164 deletions(-) diff --git a/modules/ppcp-applepay/services.php b/modules/ppcp-applepay/services.php index 6274a5fb8..a49ab7fad 100644 --- a/modules/ppcp-applepay/services.php +++ b/modules/ppcp-applepay/services.php @@ -109,7 +109,7 @@ return array( $container->get( 'applepay.data_to_scripts' ), $container->get( 'wcgateway.settings.status' ), $container->get( 'button.helper.cart-products' ) - ); + ); }, 'applepay.blocks-payment-method' => static function ( ContainerInterface $container ): PaymentMethodTypeInterface { return new BlocksPaymentMethod( diff --git a/modules/ppcp-applepay/src/ApplepayModule.php b/modules/ppcp-applepay/src/ApplepayModule.php index c63750fbc..81aedbc2f 100644 --- a/modules/ppcp-applepay/src/ApplepayModule.php +++ b/modules/ppcp-applepay/src/ApplepayModule.php @@ -40,6 +40,7 @@ class ApplepayModule implements ModuleInterface { * {@inheritDoc} */ public function run( ContainerInterface $c ): void { + $module = $this; // Clears product status when appropriate. add_action( @@ -51,38 +52,44 @@ class ApplepayModule implements ModuleInterface { } ); - // Check if the module is applicable, correct country, currency, ... etc. - if ( ! $c->get( 'applepay.eligible' ) ) { - return; - } + add_action( + 'init', + static function () use ( $c, $module ) { - // Load the button handler. - $apple_payment_method = $c->get( 'applepay.button' ); - // add onboarding and referrals hooks. - assert( $apple_payment_method instanceof ApplepayButton ); - $apple_payment_method->initialize(); + // Check if the module is applicable, correct country, currency, ... etc. + if ( ! $c->get( 'applepay.eligible' ) ) { + return; + } - // Show notice if there are product availability issues. - $availability_notice = $c->get( 'applepay.availability_notice' ); - assert( $availability_notice instanceof AvailabilityNotice ); - $availability_notice->execute(); + // Load the button handler. + $apple_payment_method = $c->get( 'applepay.button' ); + // add onboarding and referrals hooks. + assert( $apple_payment_method instanceof ApplepayButton ); + $apple_payment_method->initialize(); - // Return if server not supported. - if ( ! $c->get( 'applepay.server_supported' ) ) { - return; - } + // Show notice if there are product availability issues. + $availability_notice = $c->get( 'applepay.availability_notice' ); + assert( $availability_notice instanceof AvailabilityNotice ); + $availability_notice->execute(); - // Check if this merchant can activate / use the buttons. - // We allow non referral merchants as they can potentially still use ApplePay, we just have no way of checking the capability. - if ( ( ! $c->get( 'applepay.available' ) ) && $c->get( 'applepay.is_referral' ) ) { - return; - } + // Return if server not supported. + if ( ! $c->get( 'applepay.server_supported' ) ) { + return; + } - $this->load_assets( $c, $apple_payment_method ); - $this->handle_validation_file( $c ); - $this->render_buttons( $c, $apple_payment_method ); + // Check if this merchant can activate / use the buttons. + // We allow non referral merchants as they can potentially still use ApplePay, we just have no way of checking the capability. + if ( ( ! $c->get( 'applepay.available' ) ) && $c->get( 'applepay.is_referral' ) ) { + return; + } - $apple_payment_method->bootstrap_ajax_request(); + $module->load_assets( $c, $apple_payment_method ); + $module->handle_validation_file( $c ); + $module->render_buttons( $c, $apple_payment_method ); + + $apple_payment_method->bootstrap_ajax_request(); + } + ); } /** diff --git a/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php b/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php index 64b49f8f9..219e3c6d9 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php @@ -62,21 +62,21 @@ class ApplePayDataObjectHttp { /** * The product variations. * - * @var string + * @var array */ protected $product_variations = array(); /** * The product extra. * - * @var string + * @var array */ protected $product_extra = array(); /** * The product booking. * - * @var string + * @var array */ protected $product_booking = array(); @@ -295,7 +295,7 @@ class ApplePayDataObjectHttp { /** * Pre-processes request data to transform it to a standard format. * - * @param array $data + * @param array $data The data. * @return array */ protected function preprocess_request_data( array $data ): array { @@ -602,7 +602,7 @@ class ApplePayDataObjectHttp { /** * Returns the product variations. * - * @return string + * @return array */ public function product_variations(): array { return $this->product_variations; @@ -611,7 +611,7 @@ class ApplePayDataObjectHttp { /** * Returns the product extra. * - * @return string + * @return array */ public function product_extra(): array { return $this->product_extra; @@ -620,7 +620,7 @@ class ApplePayDataObjectHttp { /** * Returns the product booking. * - * @return string + * @return array */ public function product_booking(): array { return $this->product_booking; @@ -698,6 +698,10 @@ class ApplePayDataObjectHttp { ) ); + if ( ! $data ) { + return false; + } + return $this->append_products_to_data( $data, $_POST ); } diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php index 374f15910..9267307d8 100644 --- a/modules/ppcp-button/services.php +++ b/modules/ppcp-button/services.php @@ -254,7 +254,7 @@ return array( }, 'button.helper.cart-products' => static function ( ContainerInterface $container ): CartProductsHelper { - $data_store = \WC_Data_Store::load( 'product' ); + $data_store = \WC_Data_Store::load( 'product' ); return new CartProductsHelper( $data_store ); }, diff --git a/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php b/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php index e1b1247c4..1040cc680 100644 --- a/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php @@ -52,11 +52,11 @@ class SimulateCartEndpoint extends AbstractCartEndpoint { CartProductsHelper $cart_products, LoggerInterface $logger ) { - $this->smart_button = $smart_button; - $this->cart = clone $cart; - $this->request_data = $request_data; - $this->cart_products = $cart_products; - $this->logger = $logger; + $this->smart_button = $smart_button; + $this->cart = clone $cart; + $this->request_data = $request_data; + $this->cart_products = $cart_products; + $this->logger = $logger; $this->logger_tag = 'simulation'; } diff --git a/modules/ppcp-button/src/Helper/CartProductsHelper.php b/modules/ppcp-button/src/Helper/CartProductsHelper.php index 506c968d6..804c26598 100644 --- a/modules/ppcp-button/src/Helper/CartProductsHelper.php +++ b/modules/ppcp-button/src/Helper/CartProductsHelper.php @@ -53,7 +53,7 @@ class CartProductsHelper { /** * Sets a new cart instance. * - * @param WC_Cart $cart + * @param WC_Cart $cart The cart. * @return void */ public function set_cart( WC_Cart $cart ): void { @@ -89,7 +89,7 @@ class CartProductsHelper { /** * Returns product information from a data array. * - * @param array $product + * @param array $product The product data array, usually provided by the product page form. * @return array|null */ public function product_from_data( array $product ): ?array { @@ -135,20 +135,20 @@ class CartProductsHelper { if ( $product['product']->is_type( 'booking' ) ) { $success = $success && $this->add_booking_product( - $product['product'], - $product['booking'] - ); + $product['product'], + $product['booking'] + ); } elseif ( $product['product']->is_type( 'variable' ) ) { $success = $success && $this->add_variable_product( - $product['product'], - $product['quantity'], - $product['variations'] - ); + $product['product'], + $product['quantity'], + $product['variations'] + ); } else { $success = $success && $this->add_product( - $product['product'], - $product['quantity'] - ); + $product['product'], + $product['quantity'] + ); } } @@ -169,7 +169,9 @@ class CartProductsHelper { * @throws Exception When product could not be added. */ public function add_product( \WC_Product $product, int $quantity ): bool { - $this->validate_cart(); + if ( ! $this->cart ) { + throw new Exception( 'Cart not set.' ); + } $cart_item_key = $this->cart->add_to_cart( $product->get_id(), $quantity ); @@ -194,7 +196,9 @@ class CartProductsHelper { int $quantity, array $post_variations ): bool { - $this->validate_cart(); + if ( ! $this->cart ) { + throw new Exception( 'Cart not set.' ); + } $variations = array(); foreach ( $post_variations as $key => $value ) { @@ -230,7 +234,9 @@ class CartProductsHelper { \WC_Product $product, array $data ): bool { - $this->validate_cart(); + if ( ! $this->cart ) { + throw new Exception( 'Cart not set.' ); + } if ( ! is_callable( 'wc_bookings_get_posted_data' ) ) { return false; @@ -252,10 +258,12 @@ class CartProductsHelper { * Removes stored cart items from WooCommerce cart. * * @return void - * @throws Exception + * @throws Exception Throws if there's a failure removing the cart items. */ public function remove_cart_items(): void { - $this->validate_cart(); + if ( ! $this->cart ) { + throw new Exception( 'Cart not set.' ); + } foreach ( $this->cart_item_keys as $cart_item_key ) { if ( ! $cart_item_key ) { @@ -266,21 +274,12 @@ class CartProductsHelper { } /** + * Returns the cart item keys of the items added to cart. + * * @return array */ public function cart_item_keys(): array { return $this->cart_item_keys; } - /** - * Throws the cart not set exception. - * - * @return void - * @throws Exception - */ - private function validate_cart(): void { - if ( ! $this->cart ) { - throw new Exception( 'Cart not set.' ); - } - } } diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index b2a07c47d..4a9ab5644 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -50,103 +50,105 @@ class GooglepayModule implements ModuleInterface { } ); - // Check if the module is applicable, correct country, currency, ... etc. - if ( ! $c->get( 'googlepay.eligible' ) ) { - return; - } - - // Load the button handler. - $button = $c->get( 'googlepay.button' ); - assert( $button instanceof ButtonInterface ); - $button->initialize(); - - // Show notice if there are product availability issues. - $availability_notice = $c->get( 'googlepay.availability_notice' ); - assert( $availability_notice instanceof AvailabilityNotice ); - $availability_notice->execute(); - - // Check if this merchant can activate / use the buttons. - // We allow non referral merchants as they can potentially still use GooglePay, we just have no way of checking the capability. - if ( ( ! $c->get( 'googlepay.available' ) ) && $c->get( 'googlepay.is_referral' ) ) { - return; - } - - // Initializes button rendering. add_action( - 'wp', - static function () use ( $c, $button ) { - if ( is_admin() ) { - return; - } - $button->render(); - } - ); - - // Enqueue frontend scripts. - add_action( - 'wp_enqueue_scripts', - static function () use ( $c, $button ) { - $smart_button = $c->get( 'button.smart-button' ); - assert( $smart_button instanceof SmartButtonInterface ); - if ( $smart_button->should_load_ppcp_script() ) { - $button->enqueue(); - } - - if ( has_block( 'woocommerce/checkout' ) || has_block( 'woocommerce/cart' ) ) { - /** - * Should add this to the ButtonInterface. - * - * @psalm-suppress UndefinedInterfaceMethod - */ - $button->enqueue_styles(); - } - } - ); - - // Enqueue backend scripts. - add_action( - 'admin_enqueue_scripts', - static function () use ( $c, $button ) { - if ( ! is_admin() ) { - return; - } - /** - * Should add this to the ButtonInterface. - * - * @psalm-suppress UndefinedInterfaceMethod - */ - $button->enqueue_admin(); - } - ); - - // Registers buttons on blocks pages. - add_action( - 'woocommerce_blocks_payment_method_type_registration', - function( PaymentMethodRegistry $payment_method_registry ) use ( $c, $button ): void { - if ( $button->is_enabled() ) { - $payment_method_registry->register( $c->get( 'googlepay.blocks-payment-method' ) ); - } - } - ); - - // Adds GooglePay component to the backend button preview settings. - add_action( - 'woocommerce_paypal_payments_admin_gateway_settings', - function( array $settings ) use ( $c, $button ): array { - if ( is_array( $settings['components'] ) ) { - $settings['components'][] = 'googlepay'; - } - return $settings; - } - ); - - // Initialize AJAX endpoints. - add_action( - 'wc_ajax_' . UpdatePaymentDataEndpoint::ENDPOINT, + 'init', static function () use ( $c ) { - $endpoint = $c->get( 'googlepay.endpoint.update-payment-data' ); - assert( $endpoint instanceof UpdatePaymentDataEndpoint ); - $endpoint->handle_request(); + + // Check if the module is applicable, correct country, currency, ... etc. + if ( ! $c->get( 'googlepay.eligible' ) ) { + return; + } + + // Load the button handler. + $button = $c->get( 'googlepay.button' ); + assert( $button instanceof ButtonInterface ); + $button->initialize(); + + // Show notice if there are product availability issues. + $availability_notice = $c->get( 'googlepay.availability_notice' ); + assert( $availability_notice instanceof AvailabilityNotice ); + $availability_notice->execute(); + + // Check if this merchant can activate / use the buttons. + // We allow non referral merchants as they can potentially still use GooglePay, we just have no way of checking the capability. + if ( ( ! $c->get( 'googlepay.available' ) ) && $c->get( 'googlepay.is_referral' ) ) { + return; + } + + // Initializes button rendering. + if ( ! is_admin() ) { + $button->render(); + } + + // Enqueue frontend scripts. + add_action( + 'wp_enqueue_scripts', + static function () use ( $c, $button ) { + $smart_button = $c->get( 'button.smart-button' ); + assert( $smart_button instanceof SmartButtonInterface ); + if ( $smart_button->should_load_ppcp_script() ) { + $button->enqueue(); + } + + if ( has_block( 'woocommerce/checkout' ) || has_block( 'woocommerce/cart' ) ) { + /** + * Should add this to the ButtonInterface. + * + * @psalm-suppress UndefinedInterfaceMethod + */ + $button->enqueue_styles(); + } + } + ); + + // Enqueue backend scripts. + add_action( + 'admin_enqueue_scripts', + static function () use ( $c, $button ) { + if ( ! is_admin() ) { + return; + } + + /** + * Should add this to the ButtonInterface. + * + * @psalm-suppress UndefinedInterfaceMethod + */ + $button->enqueue_admin(); + } + ); + + // Registers buttons on blocks pages. + add_action( + 'woocommerce_blocks_payment_method_type_registration', + function( PaymentMethodRegistry $payment_method_registry ) use ( $c, $button ): void { + if ( $button->is_enabled() ) { + $payment_method_registry->register( $c->get( 'googlepay.blocks-payment-method' ) ); + } + } + ); + + // Adds GooglePay component to the backend button preview settings. + add_action( + 'woocommerce_paypal_payments_admin_gateway_settings', + function( array $settings ) use ( $c ): array { + if ( is_array( $settings['components'] ) ) { + $settings['components'][] = 'googlepay'; + } + return $settings; + } + ); + + // Initialize AJAX endpoints. + add_action( + 'wc_ajax_' . UpdatePaymentDataEndpoint::ENDPOINT, + static function () use ( $c ) { + $endpoint = $c->get( 'googlepay.endpoint.update-payment-data' ); + assert( $endpoint instanceof UpdatePaymentDataEndpoint ); + $endpoint->handle_request(); + } + ); + } ); } diff --git a/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php b/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php index 64b74d91d..427e9ce79 100644 --- a/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php +++ b/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php @@ -6,6 +6,7 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; +use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper; use WooCommerce\PayPalCommerce\TestCase; use Mockery; use WooCommerce\WooCommerce\Logging\Logger\NullLogger; @@ -91,12 +92,16 @@ class ChangeCartEndpointTest extends TestCase ->expects('from_wc_cart') ->andReturn($pu); + $productsHelper = new CartProductsHelper( + $dataStore + ); + $testee = new ChangeCartEndpoint( $cart, $shipping, $requestData, $purchase_unit_factory, - $dataStore, + $productsHelper, new NullLogger() ); From 4fbc47f1cf5326aaa0f4c382d103ef102ae9713c Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 26 Oct 2023 12:18:07 +0100 Subject: [PATCH 08/16] Revert rendering of GooglePay button to wp action. --- .../src/Assets/DataToAppleButtonScripts.php | 2 +- modules/ppcp-googlepay/src/GooglepayModule.php | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php index 6558ac472..ae0056c58 100644 --- a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php +++ b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php @@ -199,7 +199,7 @@ class DataToAppleButtonScripts { 'totalLabel' => $total_label, ), 'ajax_url' => admin_url( 'admin-ajax.php' ), - 'buttonMarkup' => $button_markup, + 'buttonMarkup' => $button_markup, // Is this being used? ); } } diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index 4a9ab5644..94f70ea09 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -76,9 +76,15 @@ class GooglepayModule implements ModuleInterface { } // Initializes button rendering. - if ( ! is_admin() ) { - $button->render(); - } + add_action( + 'wp', + static function () use ( $c, $button ) { + if ( is_admin() ) { + return; + } + $button->render(); + } + ); // Enqueue frontend scripts. add_action( From 2b9ac486166baf7fffd74f527a86a537136adb7b Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 26 Oct 2023 16:33:00 +0100 Subject: [PATCH 09/16] Fix ApplePay nonce --- .../resources/js/ApplepayButton.js | 2 +- modules/ppcp-applepay/src/ApplepayModule.php | 22 +++++++++++++++ .../src/Assets/DataToAppleButtonScripts.php | 27 ++++++++----------- .../src/Assets/PropertiesDictionary.php | 5 ++-- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 9aabf5476..19c03c5d2 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -25,7 +25,7 @@ class ApplepayButton { this.updated_contact_info = [] this.selectedShippingMethod = [] - this.nonce = document.getElementById('woocommerce-process-checkout-nonce').value + this.nonce = document.getElementById('woocommerce-process-checkout-nonce')?.value this.log = function() { if ( this.buttonConfig.is_debug ) { diff --git a/modules/ppcp-applepay/src/ApplepayModule.php b/modules/ppcp-applepay/src/ApplepayModule.php index 81aedbc2f..b81329811 100644 --- a/modules/ppcp-applepay/src/ApplepayModule.php +++ b/modules/ppcp-applepay/src/ApplepayModule.php @@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\Applepay; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; use WooCommerce\PayPalCommerce\Applepay\Assets\ApplePayButton; use WooCommerce\PayPalCommerce\Applepay\Assets\AppleProductStatus; +use WooCommerce\PayPalCommerce\Applepay\Assets\PropertiesDictionary; use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Applepay\Helper\AvailabilityNotice; @@ -90,6 +91,27 @@ class ApplepayModule implements ModuleInterface { $apple_payment_method->bootstrap_ajax_request(); } ); + + add_filter( + 'nonce_user_logged_out', + /** + * Prevents nonce from being changed for non logged in users. + * + * @param int $uid The uid. + * @param string|int $action The action. + * @return int + * + * @psalm-suppress MissingClosureParamType + */ + function ( $uid, $action ) { + if ( $action === PropertiesDictionary::NONCE_ACTION ) { + return 0; + } + return $uid; + }, + 100, + 2 + ); } /** diff --git a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php index ae0056c58..0b15fbe78 100644 --- a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php +++ b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php @@ -169,37 +169,32 @@ class DataToAppleButtonScripts { if ( ! $cart ) { return array(); } - $nonce = wp_nonce_field( 'woocommerce-process_checkout', 'woocommerce-process-checkout-nonce' ); - $button_markup = - '
' - . $nonce - . '
'; - $type = $this->settings->has( 'applepay_button_type' ) ? $this->settings->get( 'applepay_button_type' ) : ''; - $color = $this->settings->has( 'applepay_button_color' ) ? $this->settings->get( 'applepay_button_color' ) : ''; - $lang = $this->settings->has( 'applepay_button_language' ) ? $this->settings->get( 'applepay_button_language' ) : ''; - $lang = apply_filters( 'woocommerce_paypal_payments_applepay_button_language', $lang ); + + $type = $this->settings->has( 'applepay_button_type' ) ? $this->settings->get( 'applepay_button_type' ) : ''; + $color = $this->settings->has( 'applepay_button_color' ) ? $this->settings->get( 'applepay_button_color' ) : ''; + $lang = $this->settings->has( 'applepay_button_language' ) ? $this->settings->get( 'applepay_button_language' ) : ''; + $lang = apply_filters( 'woocommerce_paypal_payments_applepay_button_language', $lang ); return array( - 'sdk_url' => $this->sdk_url, - 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false, - 'button' => array( + 'sdk_url' => $this->sdk_url, + 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false, + 'button' => array( 'wrapper' => 'applepay-container', 'mini_cart_wrapper' => 'applepay-container-minicart', 'type' => $type, 'color' => $color, 'lang' => $lang, ), - 'product' => array( + 'product' => array( 'needShipping' => $cart->needs_shipping(), 'subtotal' => $cart->get_subtotal(), ), - 'shop' => array( + 'shop' => array( 'countryCode' => $shop_country_code, 'currencyCode' => $currency_code, 'totalLabel' => $total_label, ), - 'ajax_url' => admin_url( 'admin-ajax.php' ), - 'buttonMarkup' => $button_markup, // Is this being used? + 'ajax_url' => admin_url( 'admin-ajax.php' ), ); } } diff --git a/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php b/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php index 61e8aa006..25227e11b 100644 --- a/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php +++ b/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php @@ -74,8 +74,9 @@ class PropertiesDictionary { public const SHIPPING_CONTACT_INVALID = 'shipping Contact Invalid'; public const BILLING_CONTACT = 'billing_contact'; - public const NONCE = 'nonce'; - public const WCNONCE = 'woocommerce-process-checkout-nonce'; + public const NONCE = 'nonce'; + public const NONCE_ACTION = 'woocommerce-process_checkout'; + public const WCNONCE = 'woocommerce-process-checkout-nonce'; public const CREATE_ORDER_CART_REQUIRED_FIELDS = array( From 7f072be611a33d2393b90d7888df2790382d093e Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 27 Oct 2023 09:59:55 +0300 Subject: [PATCH 10/16] Fix address merge --- modules/ppcp-blocks/package.json | 3 +-- .../resources/js/Helper/Address.js | 26 +++++++++++++++++++ .../resources/js/checkout-block.js | 7 ++--- modules/ppcp-blocks/yarn.lock | 5 ---- 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-blocks/package.json b/modules/ppcp-blocks/package.json index 501915e75..53c08b7bb 100644 --- a/modules/ppcp-blocks/package.json +++ b/modules/ppcp-blocks/package.json @@ -10,8 +10,7 @@ "Edge >= 14" ], "dependencies": { - "core-js": "^3.25.0", - "deepmerge": "^4.3.1" + "core-js": "^3.25.0" }, "devDependencies": { "@babel/core": "^7.19", diff --git a/modules/ppcp-blocks/resources/js/Helper/Address.js b/modules/ppcp-blocks/resources/js/Helper/Address.js index d522d208e..6a999f141 100644 --- a/modules/ppcp-blocks/resources/js/Helper/Address.js +++ b/modules/ppcp-blocks/resources/js/Helper/Address.js @@ -124,3 +124,29 @@ export const paypalOrderToWcAddresses = (order) => { return {billingAddress, shippingAddress}; } + +/** + * Merges two WC addresses. + * The objects can contain either the WC form fields or billingAddress, shippingAddress objects. + * + * @param {Object} address1 + * @param {Object} address2 + * @returns {any} + */ +export const mergeWcAddress = (address1, address2) => { + if ('billingAddress' in address1) { + return { + billingAddress: mergeWcAddress(address1.billingAddress, address2.billingAddress), + shippingAddress: mergeWcAddress(address1.shippingAddress, address2.shippingAddress), + } + } + + let address2WithoutEmpty = {...address2}; + Object.keys(address2).forEach(key => { + if (address2[key] === '') { + delete address2WithoutEmpty[key]; + } + }); + + return {...address1, ...address2WithoutEmpty}; +} diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index 314c06054..582fdede3 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -1,9 +1,8 @@ import {useEffect, useState} from '@wordpress/element'; import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry'; -import {paypalAddressToWc, paypalOrderToWcAddresses} from "./Helper/Address"; +import {mergeWcAddress, paypalAddressToWc, paypalOrderToWcAddresses} from "./Helper/Address"; import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading' import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher"; -import merge from "deepmerge"; const config = wc.wcSettings.getSetting('ppcp-gateway_data'); @@ -32,9 +31,7 @@ const PayPalComponent = ({ } const paypalAddresses = paypalOrderToWcAddresses(config.scriptData.continuation.order); const wcAddresses = wp.data.select('wc/store/cart').getCustomerData(); - const addresses = merge(wcAddresses, paypalAddresses, { - customMerge: key => (a, b) => a ? a : b, // overwrite empty strings - }); + const addresses = mergeWcAddress(wcAddresses, paypalAddresses); wp.data.dispatch('wc/store/cart').setBillingAddress(addresses.billingAddress); if (shippingData.needsShipping) { wp.data.dispatch('wc/store/cart').setShippingAddress(addresses.shippingAddress); diff --git a/modules/ppcp-blocks/yarn.lock b/modules/ppcp-blocks/yarn.lock index 161d649ba..209adc4a4 100644 --- a/modules/ppcp-blocks/yarn.lock +++ b/modules/ppcp-blocks/yarn.lock @@ -1425,11 +1425,6 @@ debug@^4.1.0, debug@^4.1.1: dependencies: ms "2.1.2" -deepmerge@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" - integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== - electron-to-chromium@^1.4.251: version "1.4.284" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" From 17d11defc443fc1ff10da863a421a7eaba11ff1a Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 30 Oct 2023 12:20:15 +0200 Subject: [PATCH 11/16] Update supported ACDC countries --- modules/ppcp-api-client/services.php | 262 +++++++++++++++++++++++++++ 1 file changed, 262 insertions(+) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index bad667b78..25c280f41 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -596,6 +596,37 @@ return array( 'SGD', 'USD', ), + 'BE' => array( + 'EUR', + 'USD', + 'CAD', + 'GBP', + 'PLN', + 'SEK', + 'CHF', + ), + 'BG' => array( + 'EUR', + 'USD', + ), + 'CY' => array( + 'EUR', + 'USD', + 'CAD', + 'GBP', + 'AUD', + 'CZK', + 'DKK', + 'NOK', + 'PLN', + 'SEK', + 'CHF', + ), + 'CZ' => array( + 'EUR', + 'USD', + 'CZK', + ), 'DE' => array( 'AUD', 'CAD', @@ -614,6 +645,16 @@ return array( 'SGD', 'USD', ), + 'DK' => array( + 'EUR', + 'USD', + 'DKK', + 'NOK', + ), + 'EE' => array( + 'EUR', + 'USD', + ), 'ES' => array( 'AUD', 'CAD', @@ -632,6 +673,10 @@ return array( 'SGD', 'USD', ), + 'FI' => array( + 'EUR', + 'USD', + ), 'FR' => array( 'AUD', 'CAD', @@ -668,6 +713,16 @@ return array( 'SGD', 'USD', ), + 'GR' => array( + 'EUR', + 'USD', + 'GBP', + ), + 'HU' => array( + 'EUR', + 'USD', + 'HUF', + ), 'IT' => array( 'AUD', 'CAD', @@ -686,6 +741,43 @@ return array( 'SGD', 'USD', ), + 'LT' => array( + 'EUR', + 'USD', + 'CAD', + 'GBP', + 'JPY', + 'AUD', + 'CZK', + 'DKK', + 'HUF', + 'PLN', + 'SEK', + 'CHF', + 'NZD', + 'NOK', + ), + 'LU' => array( + 'EUR', + 'USD', + 'GBP', + 'CAD', + ), + 'LV' => array( + 'EUR', + 'USD', + 'CAD', + 'GBP', + 'JPY', + 'AUD', + 'CZK', + 'DKK', + 'HUF', + 'NOK', + 'PLN', + 'SEK', + 'CHF', + ), 'US' => array( 'AUD', 'CAD', @@ -712,9 +804,74 @@ return array( 'SGD', 'USD', ), + 'MT' => array( + 'EUR', + 'USD', + 'CAD', + 'GBP', + 'NOK', + ), 'MX' => array( 'MXN', ), + 'NL' => array( + 'EUR', + 'GBP', + 'AUD', + 'CZK', + 'HUF', + 'CHF', + 'CAD', + 'USD', + ), + 'NO' => array( + 'EUR', + 'USD', + 'CAD', + 'GBP', + 'AUD', + 'DKK', + 'PLN', + 'SEK', + 'CZK', + ), + 'PL' => array( + 'EUR', + 'USD', + 'CAD', + 'GBP', + 'AUD', + 'PLN', + 'SEK', + 'CZK', + ), + 'PT' => array( + 'EUR', + 'USD', + 'GBP', + ), + 'RO' => array( + 'EUR', + 'USD', + 'GBP', + ), + 'SE' => array( + 'EUR', + 'USD', + 'NOK', + 'SEK', + ), + 'SI' => array( + 'EUR', + 'USD', + ), + 'SK' => array( + 'EUR', + 'USD', + 'GBP', + 'CZK', + 'HUF', + ), 'JP' => array( 'AUD', 'CAD', @@ -752,16 +909,51 @@ return array( 'visa' => array(), 'amex' => array( 'AUD' ), ), + 'BE' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR', 'USD', 'CAD' ), + ), + 'BG' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR' ), + ), + 'CY' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR' ), + ), + 'CZ' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'CZK' ), + ), 'DE' => array( 'mastercard' => array(), 'visa' => array(), 'amex' => array( 'EUR' ), ), + 'DK' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'DKK' ), + ), + 'EE' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array(), + ), 'ES' => array( 'mastercard' => array(), 'visa' => array(), 'amex' => array( 'EUR' ), ), + 'FI' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR' ), + ), 'FR' => array( 'mastercard' => array(), 'visa' => array(), @@ -772,6 +964,16 @@ return array( 'visa' => array(), 'amex' => array( 'GBP', 'USD' ), ), + 'GR' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR' ), + ), + 'HU' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'HUF' ), + ), 'IT' => array( 'mastercard' => array(), 'visa' => array(), @@ -789,11 +991,71 @@ return array( 'amex' => array( 'CAD' ), 'jcb' => array( 'CAD' ), ), + 'LT' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR' ), + ), + 'LU' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR' ), + ), + 'LV' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR', 'USD' ), + ), + 'MT' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR' ), + ), 'MX' => array( 'mastercard' => array(), 'visa' => array(), 'amex' => array(), ), + 'NL' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR', 'USD' ), + ), + 'NO' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'NOK' ), + ), + 'PL' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR', 'USD', 'GBP', 'PLN' ), + ), + 'PT' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR', 'USD', 'CAD', 'GBP' ), + ), + 'RO' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR', 'USD' ), + ), + 'SE' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR', 'SEK' ), + ), + 'SI' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR' ), + ), + 'SK' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR', 'GBP' ), + ), 'JP' => array( 'mastercard' => array(), 'visa' => array(), From 93b39f9bb35114143c2cb1ec19d35557f8ba55ff Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 30 Oct 2023 16:16:49 +0200 Subject: [PATCH 12/16] Show Shop Pay Later messages on product category page --- modules/ppcp-button/src/Helper/ContextTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-button/src/Helper/ContextTrait.php b/modules/ppcp-button/src/Helper/ContextTrait.php index 189796b75..a7381b1b1 100644 --- a/modules/ppcp-button/src/Helper/ContextTrait.php +++ b/modules/ppcp-button/src/Helper/ContextTrait.php @@ -86,7 +86,7 @@ trait ContextTrait { return $context; } - if ( is_shop() ) { + if ( is_shop() || is_product_category() ) { return 'shop'; } From ddf4e5c068e4b3061ffc80fee8dec6447625c395 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 30 Oct 2023 16:33:15 +0200 Subject: [PATCH 13/16] Fix LV ACDC currencies --- modules/ppcp-api-client/services.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 25c280f41..c3a560288 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -768,15 +768,6 @@ return array( 'USD', 'CAD', 'GBP', - 'JPY', - 'AUD', - 'CZK', - 'DKK', - 'HUF', - 'NOK', - 'PLN', - 'SEK', - 'CHF', ), 'US' => array( 'AUD', From 2f33c29fed55e01c5d729e9ad3e950d293232c5d Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 30 Oct 2023 16:45:56 +0200 Subject: [PATCH 14/16] Fix LU ACDC currencies --- modules/ppcp-api-client/services.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index c3a560288..130eec237 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -760,8 +760,6 @@ return array( 'LU' => array( 'EUR', 'USD', - 'GBP', - 'CAD', ), 'LV' => array( 'EUR', From 8beab46cb43acd822b2f73803d7638afe023d2ac Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 30 Oct 2023 16:52:24 +0200 Subject: [PATCH 15/16] Fix MT, NO, PL, PT ACDC currencies --- modules/ppcp-api-client/services.php | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 130eec237..c5478554f 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -798,7 +798,15 @@ return array( 'USD', 'CAD', 'GBP', + 'JPY', + 'AUD', + 'CZK', + 'DKK', + 'HUF', 'NOK', + 'PLN', + 'SEK', + 'CHF', ), 'MX' => array( 'MXN', @@ -818,11 +826,7 @@ return array( 'USD', 'CAD', 'GBP', - 'AUD', - 'DKK', - 'PLN', - 'SEK', - 'CZK', + 'NOK', ), 'PL' => array( 'EUR', @@ -830,6 +834,7 @@ return array( 'CAD', 'GBP', 'AUD', + 'DKK', 'PLN', 'SEK', 'CZK', @@ -837,7 +842,9 @@ return array( 'PT' => array( 'EUR', 'USD', + 'CAD', 'GBP', + 'CZK', ), 'RO' => array( 'EUR', From 19236d24c14cce326f8662b19b769ffffcdbdbc6 Mon Sep 17 00:00:00 2001 From: Mayisha Date: Tue, 31 Oct 2023 13:24:56 +0600 Subject: [PATCH 16/16] woorelease: Product version bump update --- changelog.txt | 2 +- readme.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index 5bb4c9623..a840377a9 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,6 @@ *** Changelog *** -= 2.4.0 - xxxx-xx-xx = += 2.4.0 - 2023-10-31 = * Fix - Mini-Cart Bug cause of wrong DOM-Structure in v2.3.1 #1735 * Fix - ACDC disappearing after plugin updates #1751 * Fix - Subscription module hooks #1748 diff --git a/readme.txt b/readme.txt index 8e13325fd..7df738d23 100644 --- a/readme.txt +++ b/readme.txt @@ -180,7 +180,7 @@ If you encounter issues with the PayPal buttons not appearing after an update, p == Changelog == -= 2.4.0 - xxxx-xx-xx = += 2.4.0 - 2023-10-31 = * Fix - Mini-Cart Bug cause of wrong DOM-Structure in v2.3.1 #1735 * Fix - ACDC disappearing after plugin updates #1751 * Fix - Subscription module hooks #1748