From d49eb6035819ea7e5ff1859d6f336eac6340c48c Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Mon, 6 Nov 2023 10:15:56 +0000 Subject: [PATCH 01/11] Testing shipping fields updating ApplePay addresses on checkout page --- .../resources/js/ApplepayButton.js | 46 ++++++++++++++---- .../resources/js/Context/BaseHandler.js | 4 +- .../src/Assets/ApplePayButton.php | 21 ++++++-- .../src/Endpoint/CartScriptParamsEndpoint.php | 48 +++++++++++++++---- 4 files changed, 98 insertions(+), 21 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index f9c188741..1241175bc 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -67,11 +67,6 @@ class ApplepayButton { this.onButtonClick(); }); } - - // Listen for changes on any input within the WooCommerce checkout form - jQuery('form.checkout').on('change', 'input, select, textarea', () => { - this.fetchTransactionInfo(); - }); }); } } @@ -196,11 +191,14 @@ class ApplepayButton { const formData = new FormData(document.querySelector(checkoutFormSelector)); this.form_saved = Object.fromEntries(formData.entries()); // This line should be reviewed, the paypal.Applepay().confirmOrder fails if we add it. - //this.update_request_data_with_form(paymentDataRequest); + this.update_request_data_with_form(paymentDataRequest); } catch (error) { console.error(error); } - const session = this.applePaySession(paymentDataRequest) + + this.log('=== paymentDataRequest', paymentDataRequest); + + const session = this.applePaySession(paymentDataRequest); const formValidator = PayPalCommerceGateway.early_checkout_validation_enabled ? new FormValidator( PayPalCommerceGateway.ajax.validate_checkout.endpoint, @@ -226,11 +224,43 @@ class ApplepayButton { update_request_data_with_form(paymentDataRequest) { paymentDataRequest.billingContact = this.fill_billing_contact(this.form_saved); - paymentDataRequest.applicationData = this.fill_application_data(this.form_saved); + + // "applicationData" is originating a "PayPalApplePayError: An internal server error has occurred" on paypal.Applepay().confirmOrder(). + //paymentDataRequest.applicationData = this.fill_application_data(this.form_saved); + if (!this.buttonConfig.product.needShipping) { return; } paymentDataRequest.shippingContact = this.fill_shipping_contact(this.form_saved); + + const rate = this.transactionInfo.chosenShippingMethods[0]; + + paymentDataRequest.shippingMethods = []; + for (const shippingPackage of this.transactionInfo.shippingPackages) { + if (rate === shippingPackage.id) { + paymentDataRequest.shippingMethods.push({ + 'label' : shippingPackage.label, + 'detail' : '', + 'amount' : shippingPackage.cost_str, + 'identifier' : shippingPackage.id, + }); + break; + } + } + + for (const shippingPackage of this.transactionInfo.shippingPackages) { + if (rate !== shippingPackage.id) { + paymentDataRequest.shippingMethods.push({ + 'label' : shippingPackage.label, + 'detail' : '', + 'amount' : shippingPackage.cost_str, + 'identifier' : shippingPackage.id, + }); + break; + } + } + + this.log('=== paymentDataRequest.shippingMethods', paymentDataRequest.shippingMethods); } paymentDataRequest() { diff --git a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js index 3deb4a47e..958508de2 100644 --- a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js @@ -36,7 +36,9 @@ class BaseHandler { countryCode: data.country_code, currencyCode: data.currency_code, totalPriceStatus: 'FINAL', - totalPrice: data.total_str + totalPrice: data.total_str, + chosenShippingMethods: data.chosen_shipping_methods || null, + shippingPackages: data.shipping_packages || null, }); }); diff --git a/modules/ppcp-applepay/src/Assets/ApplePayButton.php b/modules/ppcp-applepay/src/Assets/ApplePayButton.php index fe7313a85..92c6dc0cb 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayButton.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayButton.php @@ -405,6 +405,10 @@ class ApplePayButton implements ButtonInterface { public function create_wc_order(): void { $applepay_request_data_object = $this->applepay_data_object_http(); //phpcs:disable WordPress.Security.NonceVerification + + $this->logger->info('== $_POST =='); + $this->logger->info(print_r($_POST, true)); + $context = wc_clean( wp_unslash( $_POST['caller_page'] ?? '' ) ); if ( ! is_string( $context ) ) { $this->response_templates->response_with_data_errors( @@ -418,6 +422,10 @@ class ApplePayButton implements ButtonInterface { return; } $applepay_request_data_object->order_data( $context ); + + $this->logger->info('== $applepay_request_data_object =='); + $this->logger->info(print_r($applepay_request_data_object, true)); + $this->update_posted_data( $applepay_request_data_object ); if ( $context === 'product' ) { $cart_item_key = $this->prepare_cart( $applepay_request_data_object ); @@ -596,10 +604,10 @@ class ApplePayButton implements ButtonInterface { $address['country'] ?? $shop_country_code ); WC()->customer->set_shipping_postcode( - $address['postcode'] ?? $shop_country_code + $address['postcode'] ?? '' ); WC()->customer->set_shipping_city( - $address['city'] ?? $shop_country_code + $address['city'] ?? '' ); } @@ -776,13 +784,14 @@ class ApplePayButton implements ButtonInterface { add_action( 'woocommerce_checkout_create_order', static function ( WC_Order $order, array $data ) use ( $applepay_request_data_object ) { +$this->logger->info('== HOOK woocommerce_checkout_create_order'); if ( ! empty( $applepay_request_data_object->shipping_method() ) ) { $billing_address = $applepay_request_data_object->billing_address(); $shipping_address = $applepay_request_data_object->shipping_address(); // apple puts email in shipping_address while we get it from WC's billing_address. $billing_address['email'] = $shipping_address['email']; $billing_address['phone'] = $shipping_address['phone']; - +$this->logger->info(print_r($billing_address, true)); $order->set_address( $billing_address, 'billing' ); $order->set_address( $shipping_address, 'shipping' ); } @@ -867,9 +876,12 @@ class ApplePayButton implements ButtonInterface { * @param ApplePayDataObjectHttp $applepay_request_data_object The Apple Pay request data. */ protected function update_posted_data( $applepay_request_data_object ): void { + // TODO : get checkout form data in here to fill more fields like: ensure billing email and phone are filled. + add_filter( 'woocommerce_checkout_posted_data', function ( array $data ) use ( $applepay_request_data_object ): array { +$this->logger->info('== HOOK woocommerce_checkout_posted_data'); $data['payment_method'] = 'ppcp-gateway'; $data['shipping_method'] = $applepay_request_data_object->shipping_method(); @@ -883,7 +895,7 @@ class ApplePayButton implements ButtonInterface { $data['billing_state'] = $applepay_request_data_object->billing_address()['state'] ?? ''; $data['billing_postcode'] = $applepay_request_data_object->billing_address()['postcode'] ?? ''; - if ( ! empty( $applepay_request_data_object->shipping_method() ) ) { + if ( ! empty( $applepay_request_data_object->need_shipping() ) ) { $data['billing_email'] = $applepay_request_data_object->shipping_address()['email'] ?? ''; $data['billing_phone'] = $applepay_request_data_object->shipping_address()['phone'] ?? ''; $data['shipping_first_name'] = $applepay_request_data_object->shipping_address()['first_name'] ?? ''; @@ -898,6 +910,7 @@ class ApplePayButton implements ButtonInterface { $data['shipping_email'] = $applepay_request_data_object->shipping_address()['email'] ?? ''; $data['shipping_phone'] = $applepay_request_data_object->shipping_address()['phone'] ?? ''; } +$this->logger->info(print_r($data, true)); return $data; } diff --git a/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php b/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php index 83d4c74cf..b8bde1c45 100644 --- a/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php @@ -79,17 +79,49 @@ class CartScriptParamsEndpoint implements EndpointInterface { $shop_country_code = $base_location['country'] ?? ''; $currency_code = get_woocommerce_currency(); + + $calculated_packages = WC()->shipping->calculate_shipping( + WC()->cart->get_shipping_packages() + ); + + $shipping_packages = array(); + + foreach ( $calculated_packages[0]['rates'] as $rate ) { + $rate_cost = $rate->get_cost(); + + /** + * The shipping rate. + * + * @var \WC_Shipping_Rate $rate + */ + $shipping_packages[] = array( + 'id' => $rate->get_id(), + 'label' => $rate->get_label(), + 'cost' => (float) $rate_cost, + 'cost_str' => ( new Money( (float) $rate_cost, $currency_code ) )->value_str(), + 'description' => html_entity_decode( + wp_strip_all_tags( + wc_price( (float) $rate->get_cost(), array( 'currency' => get_woocommerce_currency() ) ) + ) + ), + ); + } + + wp_send_json_success( array( - 'url_params' => $script_data['url_params'], - 'button' => $script_data['button'], - 'messages' => $script_data['messages'], - 'amount' => WC()->cart->get_total( 'raw' ), + 'url_params' => $script_data['url_params'], + 'button' => $script_data['button'], + 'messages' => $script_data['messages'], + 'amount' => WC()->cart->get_total( 'raw' ), - 'total' => $total, - 'total_str' => ( new Money( $total, $currency_code ) )->value_str(), - 'currency_code' => $currency_code, - 'country_code' => $shop_country_code, + 'total' => $total, + 'total_str' => ( new Money( $total, $currency_code ) )->value_str(), + 'currency_code' => $currency_code, + 'country_code' => $shop_country_code, + + 'chosen_shipping_methods' => WC()->session->get( 'chosen_shipping_methods' ), + 'shipping_packages' => $shipping_packages ) ); From b352b702c92cd3b2f88edeb55513b7f45a64b24c Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 8 Nov 2023 11:56:53 +0000 Subject: [PATCH 02/11] Fix browser detection on ApplePay module. --- modules/ppcp-applepay/services.php | 5 +++++ modules/ppcp-applepay/src/Assets/PropertiesDictionary.php | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-applepay/services.php b/modules/ppcp-applepay/services.php index fce1eddda..96a237f03 100644 --- a/modules/ppcp-applepay/services.php +++ b/modules/ppcp-applepay/services.php @@ -93,6 +93,11 @@ return array( // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $user_agent = wp_unslash( $_SERVER['HTTP_USER_AGENT'] ?? '' ); if ( $user_agent ) { + foreach ( PropertiesDictionary::DISALLOWED_USER_AGENTS as $disallowed_agent ) { + if ( strpos( $user_agent, $disallowed_agent ) !== false ) { + return false; + } + } foreach ( PropertiesDictionary::ALLOWED_USER_AGENTS as $allowed_agent ) { if ( strpos( $user_agent, $allowed_agent ) !== false ) { return true; diff --git a/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php b/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php index d1c388ffd..ce4be2a46 100644 --- a/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php +++ b/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php @@ -13,7 +13,8 @@ namespace WooCommerce\PayPalCommerce\Applepay\Assets; * Class PropertiesDictionary */ class PropertiesDictionary { - public const ALLOWED_USER_AGENTS = array( 'Safari', 'Macintosh', 'iPhone', 'iPad', 'iPod' ); + public const DISALLOWED_USER_AGENTS = array( 'Chrome' ); + public const ALLOWED_USER_AGENTS = array( 'Safari', 'Macintosh', 'iPhone', 'iPad', 'iPod' ); public const BILLING_CONTACT_INVALID = 'billing Contact Invalid'; From c2086fdb8564881c3d504b1be2391b98d8f54f13 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 8 Nov 2023 14:12:42 +0000 Subject: [PATCH 03/11] Update ApplePay browser support status tests. --- modules/ppcp-applepay/extensions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-applepay/extensions.php b/modules/ppcp-applepay/extensions.php index bdea5a19f..a4688916b 100644 --- a/modules/ppcp-applepay/extensions.php +++ b/modules/ppcp-applepay/extensions.php @@ -54,7 +54,7 @@ return array( } // Device eligibility. - $device_eligibility_text = __( 'Your current browser/device does not seem to support Apple Pay ❌.', 'woocommerce-paypal-payments' ); + $device_eligibility_text = __( 'Status: Your current browser/device does not seem to support Apple Pay ❌.', 'woocommerce-paypal-payments' ); $device_eligibility_notes = sprintf( // translators: %1$s and %2$s are the opening and closing of HTML tag. __( 'Though the button may display in previews, it won\'t appear in the shop. For details, refer to the %1$sApple Pay requirements%2$s.', 'woocommerce-paypal-payments' ), @@ -62,7 +62,7 @@ return array( '' ); if ( $container->get( 'applepay.is_browser_supported' ) ) { - $device_eligibility_text = __( 'Your browser/device supports Apple Pay ✔️.', 'woocommerce-paypal-payments' ); + $device_eligibility_text = __( 'Status: Your current browser/device seems to support Apple Pay ✔️.', 'woocommerce-paypal-payments' ); $device_eligibility_notes = __( 'The Apple Pay button will be visible both in previews and below the PayPal buttons in the shop.', 'woocommerce-paypal-payments' ); } From 5bcb047e3a5a6d543dde2e80b8a3979cb9054e77 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 8 Nov 2023 14:38:07 +0000 Subject: [PATCH 04/11] Update ApplePay browser support status texts. --- modules/ppcp-applepay/extensions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-applepay/extensions.php b/modules/ppcp-applepay/extensions.php index a4688916b..f0ef87d36 100644 --- a/modules/ppcp-applepay/extensions.php +++ b/modules/ppcp-applepay/extensions.php @@ -54,7 +54,7 @@ return array( } // Device eligibility. - $device_eligibility_text = __( 'Status: Your current browser/device does not seem to support Apple Pay ❌.', 'woocommerce-paypal-payments' ); + $device_eligibility_text = __( 'Status: Your current browser/device does not seem to support Apple Pay ❌', 'woocommerce-paypal-payments' ); $device_eligibility_notes = sprintf( // translators: %1$s and %2$s are the opening and closing of HTML tag. __( 'Though the button may display in previews, it won\'t appear in the shop. For details, refer to the %1$sApple Pay requirements%2$s.', 'woocommerce-paypal-payments' ), @@ -62,7 +62,7 @@ return array( '' ); if ( $container->get( 'applepay.is_browser_supported' ) ) { - $device_eligibility_text = __( 'Status: Your current browser/device seems to support Apple Pay ✔️.', 'woocommerce-paypal-payments' ); + $device_eligibility_text = __( 'Status: Your current browser/device seems to support Apple Pay ✔️', 'woocommerce-paypal-payments' ); $device_eligibility_notes = __( 'The Apple Pay button will be visible both in previews and below the PayPal buttons in the shop.', 'woocommerce-paypal-payments' ); } From 3a39bccc54de91f268f954c95da5a4cb0e36d18b Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 8 Nov 2023 18:07:19 +0000 Subject: [PATCH 05/11] Add ApplePay checkout data mode settings option. Refactor admin indentation. --- modules/ppcp-applepay/extensions.php | 44 ++++++++++---- .../resources/js/ApplepayButton.js | 60 ++++++++++++++----- .../src/Assets/DataToAppleButtonScripts.php | 42 ++++++++----- .../src/Assets/PropertiesDictionary.php | 16 +++++ modules/ppcp-googlepay/extensions.php | 12 ++-- .../ppcp-wc-gateway/resources/css/common.scss | 6 ++ 6 files changed, 134 insertions(+), 46 deletions(-) diff --git a/modules/ppcp-applepay/extensions.php b/modules/ppcp-applepay/extensions.php index bdea5a19f..5b52f8050 100644 --- a/modules/ppcp-applepay/extensions.php +++ b/modules/ppcp-applepay/extensions.php @@ -162,8 +162,8 @@ return array( ), ), ), - 'applepay_button_domain_registration' => array( - 'title' => str_repeat( ' ', 6 ) . __( 'Domain Registration', 'woocommerce-paypal-payments' ), + 'applepay_button_domain_registration' => array( + 'title' => __( 'Domain Registration', 'woocommerce-paypal-payments' ), 'type' => 'ppcp-text', 'text' => '' @@ -177,13 +177,14 @@ return array( 'Registering the website domain on the PayPal site is mandated by Apple. Payments will fail if the Apple Pay button is used on an unregistered domain.', 'woocommerce-paypal-payments' ), + 'classes' => array('ppcp-field-indent'), 'class' => array(), 'screens' => array( State::STATE_ONBOARDED ), 'gateway' => 'paypal', 'requirements' => array(), ), - 'applepay_button_domain_validation' => array( - 'title' => str_repeat( ' ', 6 ) . __( 'Domain Validation', 'woocommerce-paypal-payments' ), + 'applepay_button_domain_validation' => array( + 'title' => __( 'Domain Validation', 'woocommerce-paypal-payments' ), 'type' => 'ppcp-text', 'text' => $domain_validation_text . '

' @@ -199,13 +200,14 @@ return array( 'Apple requires the website domain to be registered and validated. PayPal Payments automatically presents your domain association file for Apple to validate the manually registered domain.', 'woocommerce-paypal-payments' ), + 'classes' => array('ppcp-field-indent'), 'class' => array(), 'screens' => array( State::STATE_ONBOARDED ), 'gateway' => 'paypal', 'requirements' => array(), ), - 'applepay_button_device_eligibility' => array( - 'title' => str_repeat( ' ', 6 ) . __( 'Device Eligibility', 'woocommerce-paypal-payments' ), + 'applepay_button_device_eligibility' => array( + 'title' => __( 'Device Eligibility', 'woocommerce-paypal-payments' ), 'type' => 'ppcp-text', 'text' => $device_eligibility_text . '

' @@ -216,19 +218,21 @@ return array( 'Apple Pay demands certain Apple devices for secure payment execution. This helps determine if your current device is compliant.', 'woocommerce-paypal-payments' ), + 'classes' => array('ppcp-field-indent'), 'class' => array(), 'screens' => array( State::STATE_ONBOARDED ), 'gateway' => 'paypal', 'requirements' => array(), ), - 'applepay_button_type' => array( - 'title' => str_repeat( ' ', 6 ) . __( 'Button Label', 'woocommerce-paypal-payments' ), + 'applepay_button_type' => array( + 'title' => __( 'Button Label', 'woocommerce-paypal-payments' ), 'type' => 'select', 'desc_tip' => true, 'description' => __( 'This controls the label of the Apple Pay button.', 'woocommerce-paypal-payments' ), + 'classes' => array('ppcp-field-indent'), 'class' => array(), 'input_class' => array( 'wc-enhanced-select' ), 'default' => 'pay', @@ -237,8 +241,8 @@ return array( 'gateway' => 'paypal', 'requirements' => array(), ), - 'applepay_button_color' => array( - 'title' => str_repeat( ' ', 6 ) . __( 'Button Color', 'woocommerce-paypal-payments' ), + 'applepay_button_color' => array( + 'title' => __( 'Button Color', 'woocommerce-paypal-payments' ), 'type' => 'select', 'desc_tip' => true, 'description' => __( @@ -247,6 +251,7 @@ return array( ), 'label' => '', 'input_class' => array( 'wc-enhanced-select' ), + 'classes' => array('ppcp-field-indent'), 'class' => array(), 'default' => 'black', 'options' => PropertiesDictionary::button_colors(), @@ -254,14 +259,15 @@ return array( 'gateway' => 'paypal', 'requirements' => array(), ), - 'applepay_button_language' => array( - 'title' => str_repeat( ' ', 6 ) . __( 'Button Language', 'woocommerce-paypal-payments' ), + 'applepay_button_language' => array( + 'title' => __( 'Button Language', 'woocommerce-paypal-payments' ), 'type' => 'select', 'desc_tip' => true, 'description' => __( 'The language and region used for the displayed Apple Pay button. The default value is the current language and region setting in a browser.', 'woocommerce-paypal-payments' ), + 'classes' => array('ppcp-field-indent'), 'class' => array(), 'input_class' => array( 'wc-enhanced-select' ), 'default' => 'en', @@ -270,6 +276,20 @@ return array( 'gateway' => 'paypal', 'requirements' => array(), ), + 'applepay_checkout_data_mode' => array( + 'title' => __( 'Send checkout billing and shipping data to Apple Pay', 'woocommerce-paypal-payments' ), + 'type' => 'select', + 'classes' => array('ppcp-field-indent'), + 'class' => array(), + 'input_class' => array( 'wc-enhanced-select' ), + 'desc_tip' => true, + 'description' => __( 'Using the WC form data increases convenience for the customers, but can cause issues if Apple Pay details do not match the billing and shipping data in the checkout form.', 'woocommerce-paypal-payments' ), + 'default' => PropertiesDictionary::BILLING_DATA_MODE_DEFAULT, + 'options' => PropertiesDictionary::billing_data_modes(), + 'screens' => array( State::STATE_ONBOARDED ), + 'gateway' => 'paypal', + 'requirements' => array(), + ), ) ); }, diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 34ad4454e..5cc3a225c 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -132,7 +132,7 @@ class ApplepayButton { const session = new ApplePaySession(4, paymentRequest) session.begin() - if (this.buttonConfig.product.needShipping) { + if (this.shouldAllowShippingInButton()) { session.onshippingmethodselected = this.onshippingmethodselected(session) session.onshippingcontactselected = this.onshippingcontactselected(session) } @@ -222,20 +222,50 @@ class ApplepayButton { this.applePaySession(paymentDataRequest) } + /** + * If the button should show the shipping fields. + * + * @returns {false|*} + */ + shouldAllowShippingInButton() { + return this.contextHandler.shippingAllowed() + && this.buttonConfig.product.needShipping + && (this.context !== 'checkout' || this.shouldAllowShippingInButton()); + } + + /** + * If the button should be updated with the form addresses. + * + * @returns {boolean} + */ + shouldUpdateButtonWithFormData() { + return this.ppcpConfig?.preferences?.checkout_data_mode === 'use_applepay'; + } + update_request_data_with_form(paymentDataRequest) { - paymentDataRequest.billingContact = this.fill_billing_contact(this.form_saved); - - // "applicationData" is originating a "PayPalApplePayError: An internal server error has occurred" on paypal.Applepay().confirmOrder(). - //paymentDataRequest.applicationData = this.fill_application_data(this.form_saved); - - if (!this.buttonConfig.product.needShipping) { + if (!this.shouldUpdateButtonWithFormData()) { return; } + + // Add billing address. + paymentDataRequest.billingContact = this.fill_billing_contact(this.form_saved); + + // Add custom data. + // "applicationData" is originating a "PayPalApplePayError: An internal server error has occurred" on paypal.Applepay().confirmOrder(). + // paymentDataRequest.applicationData = this.fill_application_data(this.form_saved); + + if (!this.shouldAllowShippingInButton()) { + return; + } + + // Add shipping address. paymentDataRequest.shippingContact = this.fill_shipping_contact(this.form_saved); + // Get shipping methods. const rate = this.transactionInfo.chosenShippingMethods[0]; - paymentDataRequest.shippingMethods = []; + + // Add selected shipping method. for (const shippingPackage of this.transactionInfo.shippingPackages) { if (rate === shippingPackage.id) { paymentDataRequest.shippingMethods.push({ @@ -248,6 +278,7 @@ class ApplepayButton { } } + // Add other shipping methods. for (const shippingPackage of this.transactionInfo.shippingPackages) { if (rate !== shippingPackage.id) { paymentDataRequest.shippingMethods.push({ @@ -256,7 +287,6 @@ class ApplepayButton { 'amount' : shippingPackage.cost_str, 'identifier' : shippingPackage.id, }); - break; } } @@ -271,11 +301,11 @@ class ApplepayButton { merchantCapabilities: applepayConfig.merchantCapabilities, supportedNetworks: applepayConfig.supportedNetworks, requiredShippingContactFields: ["postalAddress", "email", "phone"], - requiredBillingContactFields: ["postalAddress", "email", "phone"], + requiredBillingContactFields: ["postalAddress"], } - if (!this.contextHandler.shippingAllowed()) { - baseRequest.requiredShippingContactFields = []; + if (!this.shouldAllowShippingInButton()) { + baseRequest.requiredShippingContactFields = ["email", "phone"]; } const paymentDataRequest = Object.assign({}, baseRequest); @@ -429,7 +459,7 @@ class ApplepayButton { caller_page: 'productDetail', product_quantity: this.productQuantity, simplified_contact: event.shippingContact, - need_shipping: this.buttonConfig.product.needShipping, + need_shipping: this.shouldAllowShippingInButton(), 'woocommerce-process-checkout-nonce': this.nonce, }; case 'cart': @@ -441,7 +471,7 @@ class ApplepayButton { action: 'ppcp_update_shipping_contact', simplified_contact: event.shippingContact, caller_page: 'cart', - need_shipping: this.buttonConfig.product.needShipping, + need_shipping: this.shouldAllowShippingInButton(), 'woocommerce-process-checkout-nonce': this.nonce, }; } @@ -548,7 +578,7 @@ class ApplepayButton { if (confirmOrderResponse.approveApplePayPayment.status === "APPROVED") { try { - if (!this.contextHandler.shippingAllowed()) { + if (!this.shouldAllowShippingInButton()) { // No shipping, expect immediate capture, ex: PayNow. let approveFailed = false; diff --git a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php index afe0126ef..1c3891b48 100644 --- a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php +++ b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php @@ -144,31 +144,35 @@ class DataToAppleButtonScripts { $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' ) : ''; + $checkout_data_mode = $this->settings->has( 'applepay_checkout_data_mode' ) ? $this->settings->get( 'applepay_checkout_data_mode' ) : PropertiesDictionary::BILLING_DATA_MODE_DEFAULT; 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, + 'preferences' => array( + 'checkout_data_mode' => $checkout_data_mode, + ), + 'button' => array( 'wrapper' => 'applepay-container', 'mini_cart_wrapper' => 'applepay-container-minicart', 'type' => $type, 'color' => $color, 'lang' => $lang, ), - 'product' => array( + 'product' => array( 'needShipping' => $product_need_shipping, 'id' => $product_id, 'price' => $product_price, 'isVariation' => $is_variation, 'stock' => $product_stock, ), - 'shop' => array( + 'shop' => array( 'countryCode' => $shop_country_code, 'currencyCode' => $currency_code, 'totalLabel' => $total_label, ), - 'ajax_url' => admin_url( 'admin-ajax.php' ), - 'nonce' => wp_create_nonce( 'woocommerce-process_checkout' ), + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'woocommerce-process_checkout' ), ); } @@ -191,14 +195,18 @@ class DataToAppleButtonScripts { return array(); } - $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 ); + $checkout_data_mode = $this->settings->has( 'applepay_checkout_data_mode' ) ? $this->settings->get( 'applepay_checkout_data_mode' ) : PropertiesDictionary::BILLING_DATA_MODE_DEFAULT; return array( 'sdk_url' => $this->sdk_url, 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false, + 'preferences' => array( + 'checkout_data_mode' => $checkout_data_mode, + ), 'button' => array( 'wrapper' => 'applepay-container', 'mini_cart_wrapper' => 'applepay-container-minicart', @@ -235,14 +243,18 @@ class DataToAppleButtonScripts { $currency_code, $total_label ) { - $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 ); + $checkout_data_mode = $this->settings->has( 'applepay_checkout_data_mode' ) ? $this->settings->get( 'applepay_checkout_data_mode' ) : PropertiesDictionary::BILLING_DATA_MODE_DEFAULT; return array( 'sdk_url' => $this->sdk_url, 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false, + 'preferences' => array( + 'checkout_data_mode' => $checkout_data_mode, + ), 'button' => array( 'wrapper' => 'applepay-container', 'mini_cart_wrapper' => 'applepay-container-minicart', diff --git a/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php b/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php index d1c388ffd..4cdb3190f 100644 --- a/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php +++ b/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php @@ -17,6 +17,10 @@ class PropertiesDictionary { public const BILLING_CONTACT_INVALID = 'billing Contact Invalid'; + public const BILLING_DATA_MODE_USE_WC = 'use_wc'; + public const BILLING_DATA_MODE_USE_APPLEPAY = 'use_applepay'; + public const BILLING_DATA_MODE_DEFAULT = self::BILLING_DATA_MODE_USE_WC; + public const CREATE_ORDER_SINGLE_PROD_REQUIRED_FIELDS = array( self::WCNONCE, @@ -186,4 +190,16 @@ class PropertiesDictionary { 'zh-TW' => __( 'Chinese (Traditional)', 'woocommerce-paypal-payments' ), ); } + + /** + * Returns the possible list of billing data modes. + * + * @return array + */ + public static function billing_data_modes(): array { + return array( + PropertiesDictionary::BILLING_DATA_MODE_USE_WC => __( 'Use WC checkout form data (do not show shipping address fields)', 'woocommerce-paypal-payments' ), + PropertiesDictionary::BILLING_DATA_MODE_USE_APPLEPAY => __( 'Do not use WC checkout form data (request billing and shipping addresses on Apple Pay)', 'woocommerce-paypal-payments' ), + ); + } } diff --git a/modules/ppcp-googlepay/extensions.php b/modules/ppcp-googlepay/extensions.php index 18c8955be..e2080a8f5 100644 --- a/modules/ppcp-googlepay/extensions.php +++ b/modules/ppcp-googlepay/extensions.php @@ -135,13 +135,14 @@ return array( ), ), 'googlepay_button_type' => array( - 'title' => str_repeat( ' ', 6 ) . __( 'Button Label', 'woocommerce-paypal-payments' ), + 'title' => __( 'Button Label', 'woocommerce-paypal-payments' ), 'type' => 'select', 'desc_tip' => true, 'description' => __( 'This controls the label of the Google Pay button.', 'woocommerce-paypal-payments' ), + 'classes' => array('ppcp-field-indent'), 'class' => array(), 'input_class' => array( 'wc-enhanced-select' ), 'default' => 'pay', @@ -151,7 +152,7 @@ return array( 'requirements' => array(), ), 'googlepay_button_color' => array( - 'title' => str_repeat( ' ', 6 ) . __( 'Button Color', 'woocommerce-paypal-payments' ), + 'title' => __( 'Button Color', 'woocommerce-paypal-payments' ), 'type' => 'select', 'desc_tip' => true, 'description' => __( @@ -160,6 +161,7 @@ return array( ), 'label' => '', 'input_class' => array( 'wc-enhanced-select' ), + 'classes' => array('ppcp-field-indent'), 'class' => array(), 'default' => 'black', 'options' => PropertiesDictionary::button_colors(), @@ -168,13 +170,14 @@ return array( 'requirements' => array(), ), 'googlepay_button_language' => array( - 'title' => str_repeat( ' ', 6 ) . __( 'Button Language', 'woocommerce-paypal-payments' ), + 'title' => __( 'Button Language', 'woocommerce-paypal-payments' ), 'type' => 'select', 'desc_tip' => true, 'description' => __( 'The language and region used for the displayed Google Pay button. The default value is the current language and region setting in a browser.', 'woocommerce-paypal-payments' ), + 'classes' => array('ppcp-field-indent'), 'class' => array(), 'input_class' => array( 'wc-enhanced-select' ), 'default' => 'en', @@ -184,13 +187,14 @@ return array( 'requirements' => array(), ), 'googlepay_button_shipping_enabled' => array( - 'title' => str_repeat( ' ', 6 ) . __( 'Shipping Callback', 'woocommerce-paypal-payments' ), + 'title' => __( 'Shipping Callback', 'woocommerce-paypal-payments' ), 'type' => 'checkbox', 'desc_tip' => true, 'description' => __( 'Synchronizes your available shipping options with Google Pay. Enabling this may impact the buyer experience.', 'woocommerce-paypal-payments' ), + 'classes' => array('ppcp-field-indent'), 'label' => __( 'Enable Google Pay shipping callback', 'woocommerce-paypal-payments' ), 'default' => 'no', 'screens' => array( State::STATE_ONBOARDED ), diff --git a/modules/ppcp-wc-gateway/resources/css/common.scss b/modules/ppcp-wc-gateway/resources/css/common.scss index 299375005..259ecc672 100644 --- a/modules/ppcp-wc-gateway/resources/css/common.scss +++ b/modules/ppcp-wc-gateway/resources/css/common.scss @@ -9,3 +9,9 @@ filter: grayscale(100%); opacity: 0.5; } + +.ppcp-field-indent { + th { + padding-left: 20px; + } +} From 2f5f51e5181c09a985480da0834e28b93047b6bd Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 9 Nov 2023 17:50:57 +0000 Subject: [PATCH 06/11] Uniformize ApplePay javascript coding style. Add support for checkout form data to ApplePay. Add checkout shipping mode option in ApplePay settings. --- modules/ppcp-applepay/extensions.php | 28 +- .../resources/js/ApplepayButton.js | 298 +++++++++++------- .../resources/js/Context/BaseHandler.js | 6 +- .../resources/js/Context/CheckoutHandler.js | 19 +- .../src/Assets/ApplePayButton.php | 55 +--- .../src/Assets/DataToAppleButtonScripts.php | 26 +- .../src/Assets/PropertiesDictionary.php | 4 +- .../src/Endpoint/CartScriptParamsEndpoint.php | 96 +++--- modules/ppcp-googlepay/extensions.php | 8 +- 9 files changed, 292 insertions(+), 248 deletions(-) diff --git a/modules/ppcp-applepay/extensions.php b/modules/ppcp-applepay/extensions.php index 5b52f8050..ec5c27b96 100644 --- a/modules/ppcp-applepay/extensions.php +++ b/modules/ppcp-applepay/extensions.php @@ -162,7 +162,7 @@ return array( ), ), ), - 'applepay_button_domain_registration' => array( + 'applepay_button_domain_registration' => array( 'title' => __( 'Domain Registration', 'woocommerce-paypal-payments' ), 'type' => 'ppcp-text', 'text' => @@ -177,13 +177,13 @@ return array( 'Registering the website domain on the PayPal site is mandated by Apple. Payments will fail if the Apple Pay button is used on an unregistered domain.', 'woocommerce-paypal-payments' ), - 'classes' => array('ppcp-field-indent'), + 'classes' => array( 'ppcp-field-indent' ), 'class' => array(), 'screens' => array( State::STATE_ONBOARDED ), 'gateway' => 'paypal', 'requirements' => array(), ), - 'applepay_button_domain_validation' => array( + 'applepay_button_domain_validation' => array( 'title' => __( 'Domain Validation', 'woocommerce-paypal-payments' ), 'type' => 'ppcp-text', 'text' => $domain_validation_text @@ -200,13 +200,13 @@ return array( 'Apple requires the website domain to be registered and validated. PayPal Payments automatically presents your domain association file for Apple to validate the manually registered domain.', 'woocommerce-paypal-payments' ), - 'classes' => array('ppcp-field-indent'), + 'classes' => array( 'ppcp-field-indent' ), 'class' => array(), 'screens' => array( State::STATE_ONBOARDED ), 'gateway' => 'paypal', 'requirements' => array(), ), - 'applepay_button_device_eligibility' => array( + 'applepay_button_device_eligibility' => array( 'title' => __( 'Device Eligibility', 'woocommerce-paypal-payments' ), 'type' => 'ppcp-text', 'text' => $device_eligibility_text @@ -218,13 +218,13 @@ return array( 'Apple Pay demands certain Apple devices for secure payment execution. This helps determine if your current device is compliant.', 'woocommerce-paypal-payments' ), - 'classes' => array('ppcp-field-indent'), + 'classes' => array( 'ppcp-field-indent' ), 'class' => array(), 'screens' => array( State::STATE_ONBOARDED ), 'gateway' => 'paypal', 'requirements' => array(), ), - 'applepay_button_type' => array( + 'applepay_button_type' => array( 'title' => __( 'Button Label', 'woocommerce-paypal-payments' ), 'type' => 'select', 'desc_tip' => true, @@ -232,7 +232,7 @@ return array( 'This controls the label of the Apple Pay button.', 'woocommerce-paypal-payments' ), - 'classes' => array('ppcp-field-indent'), + 'classes' => array( 'ppcp-field-indent' ), 'class' => array(), 'input_class' => array( 'wc-enhanced-select' ), 'default' => 'pay', @@ -241,7 +241,7 @@ return array( 'gateway' => 'paypal', 'requirements' => array(), ), - 'applepay_button_color' => array( + 'applepay_button_color' => array( 'title' => __( 'Button Color', 'woocommerce-paypal-payments' ), 'type' => 'select', 'desc_tip' => true, @@ -251,7 +251,7 @@ return array( ), 'label' => '', 'input_class' => array( 'wc-enhanced-select' ), - 'classes' => array('ppcp-field-indent'), + 'classes' => array( 'ppcp-field-indent' ), 'class' => array(), 'default' => 'black', 'options' => PropertiesDictionary::button_colors(), @@ -259,7 +259,7 @@ return array( 'gateway' => 'paypal', 'requirements' => array(), ), - 'applepay_button_language' => array( + 'applepay_button_language' => array( 'title' => __( 'Button Language', 'woocommerce-paypal-payments' ), 'type' => 'select', 'desc_tip' => true, @@ -267,7 +267,7 @@ return array( 'The language and region used for the displayed Apple Pay button. The default value is the current language and region setting in a browser.', 'woocommerce-paypal-payments' ), - 'classes' => array('ppcp-field-indent'), + 'classes' => array( 'ppcp-field-indent' ), 'class' => array(), 'input_class' => array( 'wc-enhanced-select' ), 'default' => 'en', @@ -276,10 +276,10 @@ return array( 'gateway' => 'paypal', 'requirements' => array(), ), - 'applepay_checkout_data_mode' => array( + 'applepay_checkout_data_mode' => array( 'title' => __( 'Send checkout billing and shipping data to Apple Pay', 'woocommerce-paypal-payments' ), 'type' => 'select', - 'classes' => array('ppcp-field-indent'), + 'classes' => array( 'ppcp-field-indent' ), 'class' => array(), 'input_class' => array( 'wc-enhanced-select' ), 'desc_tip' => true, diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 5cc3a225c..d8e23fb63 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -16,7 +16,7 @@ class ApplepayButton { this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; this.paymentsClient = null; - this.form_saved = false; + this.formData = null; this.contextHandler = ContextHandlerFactory.create( this.context, @@ -24,10 +24,13 @@ class ApplepayButton { this.ppcpConfig ); - this.updated_contact_info = [] + this.updatedContactInfo = [] this.selectedShippingMethod = [] this.nonce = document.getElementById('woocommerce-process-checkout-nonce')?.value || buttonConfig.nonce + // Stores initialization data sent to the button. + this.initialPaymentRequest = null; + this.log = function() { if ( this.buttonConfig.is_debug ) { console.log('[ApplePayButton]', ...arguments); @@ -35,6 +38,13 @@ class ApplepayButton { } this.refreshContextData(); + + // Debug helpers + jQuery(document).on('ppcp-applepay-debug', () => { + console.log('ApplePayButton', this.context, this); + }); + document.ppcpApplepayButtons = document.ppcpApplepayButtons || {}; + document.ppcpApplepayButtons[this.context] = this; } init(config) { @@ -47,6 +57,7 @@ class ApplepayButton { this.isInitialized = true; this.applePayConfig = config; const isEligible = this.applePayConfig.isEligible; + if (isEligible) { this.fetchTransactionInfo().then(() => { const isSubscriptionProduct = this.ppcpConfig?.data_client_id?.has_subscriptions === true; @@ -84,6 +95,7 @@ class ApplepayButton { async fetchTransactionInfo() { this.transactionInfo = await this.contextHandler.transactionInfo(); } + /** * Returns configurations relative to this button context. */ @@ -108,6 +120,7 @@ class ApplepayButton { return config; } + initEventHandlers() { const { wrapper, ppcpButtonWrapper } = this.contextConfig(); const wrapper_id = '#' + wrapper; @@ -127,22 +140,25 @@ class ApplepayButton { syncButtonVisibility(); } + /** + * Starts an ApplePay session. + */ applePaySession(paymentRequest) { this.log('applePaySession', paymentRequest); - const session = new ApplePaySession(4, paymentRequest) - session.begin() + const session = new ApplePaySession(4, paymentRequest); + session.begin(); - if (this.shouldAllowShippingInButton()) { - session.onshippingmethodselected = this.onshippingmethodselected(session) - session.onshippingcontactselected = this.onshippingcontactselected(session) + if (this.shouldRequireShippingInButton()) { + session.onshippingmethodselected = this.onShippingMethodSelected(session); + session.onshippingcontactselected = this.onShippingContactSelected(session); } - session.onvalidatemerchant = this.onvalidatemerchant(session); - session.onpaymentauthorized = this.onpaymentauthorized(session); + session.onvalidatemerchant = this.onValidateMerchant(session); + session.onpaymentauthorized = this.onPaymentAuthorized(session); return session; } /** - * Add a Apple Pay purchase button + * Adds an Apple Pay purchase button. */ addButton() { this.log('addButton', this.context); @@ -179,8 +195,9 @@ class ApplepayButton { async onButtonClick() { this.log('onButtonClick', this.context); - const paymentDataRequest = this.paymentDataRequest(); - // trigger woocommerce validation if we are in the checkout page + const paymentRequest = this.paymentRequest(); + + // Trigger woocommerce validation if we are in the checkout page. if (this.context === 'checkout') { const checkoutFormSelector = 'form.woocommerce-checkout'; const errorHandler = new ErrorHandler( @@ -189,16 +206,16 @@ class ApplepayButton { ); try { const formData = new FormData(document.querySelector(checkoutFormSelector)); - this.form_saved = Object.fromEntries(formData.entries()); - // This line should be reviewed, the widgetBuilder.paypal.Applepay().confirmOrder fails if we add it. - this.update_request_data_with_form(paymentDataRequest); + this.formData = Object.fromEntries(formData.entries()); + + this.updateRequestDataWithForm(paymentRequest); } catch (error) { console.error(error); } - this.log('=== paymentDataRequest', paymentDataRequest); + this.log('=== paymentRequest', paymentRequest); - const session = this.applePaySession(paymentDataRequest); + const session = this.applePaySession(paymentRequest); const formValidator = PayPalCommerceGateway.early_checkout_validation_enabled ? new FormValidator( PayPalCommerceGateway.ajax.validate_checkout.endpoint, @@ -219,7 +236,9 @@ class ApplepayButton { } return; } - this.applePaySession(paymentDataRequest) + + // Default session initialization. + this.applePaySession(paymentRequest); } /** @@ -227,10 +246,10 @@ class ApplepayButton { * * @returns {false|*} */ - shouldAllowShippingInButton() { + shouldRequireShippingInButton() { return this.contextHandler.shippingAllowed() && this.buttonConfig.product.needShipping - && (this.context !== 'checkout' || this.shouldAllowShippingInButton()); + && (this.context !== 'checkout' || this.shouldUpdateButtonWithFormData()); } /** @@ -239,41 +258,70 @@ class ApplepayButton { * @returns {boolean} */ shouldUpdateButtonWithFormData() { - return this.ppcpConfig?.preferences?.checkout_data_mode === 'use_applepay'; + if (this.context !== 'checkout') { + return false; + } + return this.buttonConfig?.preferences?.checkout_data_mode === 'use_applepay'; } - update_request_data_with_form(paymentDataRequest) { + /** + * Indicates how payment completion should be handled if with the context handler default actions. + * Or with ApplePay module specific completion. + * + * @returns {boolean} + */ + shouldCompletePaymentWithContextHandler() { + // Data already handled, ex: PayNow + if (!this.contextHandler.shippingAllowed()) { + return true; + } + // Use WC form data mode in Checkout. + if (this.context === 'checkout' && !this.shouldUpdateButtonWithFormData()) { + return true; + } + return false; + } + + /** + * Updates ApplePay paymentRequest with form data. + */ + updateRequestDataWithForm(paymentRequest) { if (!this.shouldUpdateButtonWithFormData()) { return; } // Add billing address. - paymentDataRequest.billingContact = this.fill_billing_contact(this.form_saved); + paymentRequest.billingContact = this.fillBillingContact(this.formData); // Add custom data. // "applicationData" is originating a "PayPalApplePayError: An internal server error has occurred" on paypal.Applepay().confirmOrder(). - // paymentDataRequest.applicationData = this.fill_application_data(this.form_saved); + // paymentRequest.applicationData = this.fillApplicationData(this.formData); - if (!this.shouldAllowShippingInButton()) { + if (!this.shouldRequireShippingInButton()) { return; } // Add shipping address. - paymentDataRequest.shippingContact = this.fill_shipping_contact(this.form_saved); + paymentRequest.shippingContact = this.fillShippingContact(this.formData); // Get shipping methods. const rate = this.transactionInfo.chosenShippingMethods[0]; - paymentDataRequest.shippingMethods = []; + paymentRequest.shippingMethods = []; // Add selected shipping method. for (const shippingPackage of this.transactionInfo.shippingPackages) { if (rate === shippingPackage.id) { - paymentDataRequest.shippingMethods.push({ + const shippingMethod = { 'label' : shippingPackage.label, 'detail' : '', 'amount' : shippingPackage.cost_str, 'identifier' : shippingPackage.id, - }); + }; + + // Remember this shipping method as the selected one. + this.selectedShippingMethod = shippingMethod; + + paymentRequest.shippingMethods.push(shippingMethod); break; } } @@ -281,7 +329,7 @@ class ApplepayButton { // Add other shipping methods. for (const shippingPackage of this.transactionInfo.shippingPackages) { if (rate !== shippingPackage.id) { - paymentDataRequest.shippingMethods.push({ + paymentRequest.shippingMethods.push({ 'label' : shippingPackage.label, 'detail' : '', 'amount' : shippingPackage.cost_str, @@ -290,10 +338,13 @@ class ApplepayButton { } } - this.log('=== paymentDataRequest.shippingMethods', paymentDataRequest.shippingMethods); + // Store for reuse in case this data is not provided by ApplePay on authorization. + this.initialPaymentRequest = paymentRequest; + + this.log('=== paymentRequest.shippingMethods', paymentRequest.shippingMethods); } - paymentDataRequest() { + paymentRequest() { const applepayConfig = this.applePayConfig const buttonConfig = this.buttonConfig let baseRequest = { @@ -301,22 +352,28 @@ class ApplepayButton { merchantCapabilities: applepayConfig.merchantCapabilities, supportedNetworks: applepayConfig.supportedNetworks, requiredShippingContactFields: ["postalAddress", "email", "phone"], - requiredBillingContactFields: ["postalAddress"], + requiredBillingContactFields: ["postalAddress"], // ApplePay does not implement billing email and phone fields. } - if (!this.shouldAllowShippingInButton()) { - baseRequest.requiredShippingContactFields = ["email", "phone"]; + if (!this.shouldRequireShippingInButton()) { + if (this.shouldCompletePaymentWithContextHandler()) { + // Data needs handled externally. + baseRequest.requiredShippingContactFields = []; + } else { + // Minimum data required for order creation. + baseRequest.requiredShippingContactFields = ["email", "phone"]; + } } - const paymentDataRequest = Object.assign({}, baseRequest); - paymentDataRequest.currencyCode = buttonConfig.shop.currencyCode; - paymentDataRequest.total = { + const paymentRequest = Object.assign({}, baseRequest); + paymentRequest.currencyCode = buttonConfig.shop.currencyCode; + paymentRequest.total = { label: buttonConfig.shop.totalLabel, type: "final", amount: this.transactionInfo.totalPrice, } - return paymentDataRequest + return paymentRequest; } refreshContextData() { @@ -334,7 +391,7 @@ class ApplepayButton { // Payment process //------------------------ - onvalidatemerchant(session) { + onValidateMerchant(session) { this.log('onvalidatemerchant', this.buttonConfig.ajax_url); return (applePayValidateMerchantEvent) => { this.log('onvalidatemerchant call'); @@ -368,83 +425,90 @@ class ApplepayButton { validation: false, 'woocommerce-process-checkout-nonce': this.nonce, } - }) + }); this.log('onvalidatemerchant session abort'); session.abort(); }); }; } - onshippingmethodselected(session) { + + onShippingMethodSelected(session) { this.log('onshippingmethodselected', this.buttonConfig.ajax_url); - const ajax_url = this.buttonConfig.ajax_url + const ajax_url = this.buttonConfig.ajax_url; return (event) => { this.log('onshippingmethodselected call'); const data = this.getShippingMethodData(event); + jQuery.ajax({ url: ajax_url, method: 'POST', data: data, success: (applePayShippingMethodUpdate, textStatus, jqXHR) => { this.log('onshippingmethodselected ok'); - let response = applePayShippingMethodUpdate.data + let response = applePayShippingMethodUpdate.data; if (applePayShippingMethodUpdate.success === false) { - response.errors = createAppleErrors(response.errors) + response.errors = createAppleErrors(response.errors); } - this.selectedShippingMethod = event.shippingMethod - //order the response shipping methods, so that the selected shipping method is the first one - let orderedShippingMethods = response.newShippingMethods.sort((a, b) => { + this.selectedShippingMethod = event.shippingMethod; + + // Sort the response shipping methods, so that the selected shipping method is the first one. + response.newShippingMethods = response.newShippingMethods.sort((a, b) => { if (a.label === this.selectedShippingMethod.label) { - return -1 + return -1; } - return 1 - }) - //update the response.newShippingMethods with the ordered shipping methods - response.newShippingMethods = orderedShippingMethods + return 1; + }); + if (applePayShippingMethodUpdate.success === false) { - response.errors = createAppleErrors(response.errors) + response.errors = createAppleErrors(response.errors); } - session.completeShippingMethodSelection(response) + session.completeShippingMethodSelection(response); }, error: (jqXHR, textStatus, errorThrown) => { this.log('onshippingmethodselected error', textStatus); - console.warn(textStatus, errorThrown) - session.abort() + console.warn(textStatus, errorThrown); + session.abort(); }, - }) + }); }; } - onshippingcontactselected(session) { + + onShippingContactSelected(session) { this.log('onshippingcontactselected', this.buttonConfig.ajax_url); - const ajax_url = this.buttonConfig.ajax_url + + const ajax_url = this.buttonConfig.ajax_url; + return (event) => { this.log('onshippingcontactselected call'); const data = this.getShippingContactData(event); + jQuery.ajax({ url: ajax_url, method: 'POST', data: data, success: (applePayShippingContactUpdate, textStatus, jqXHR) => { this.log('onshippingcontactselected ok'); - let response = applePayShippingContactUpdate.data - this.updated_contact_info = event.shippingContact + let response = applePayShippingContactUpdate.data; + this.updatedContactInfo = event.shippingContact; if (applePayShippingContactUpdate.success === false) { - response.errors = createAppleErrors(response.errors) + response.errors = createAppleErrors(response.errors); } if (response.newShippingMethods) { - this.selectedShippingMethod = response.newShippingMethods[0] + this.selectedShippingMethod = response.newShippingMethods[0]; } - session.completeShippingContactSelection(response) + session.completeShippingContactSelection(response); }, error: (jqXHR, textStatus, errorThrown) => { this.log('onshippingcontactselected error', textStatus); - console.warn(textStatus, errorThrown) - session.abort() + console.warn(textStatus, errorThrown); + session.abort(); }, - }) + }); }; } + getShippingContactData(event) { const product_id = this.buttonConfig.product.id; @@ -459,7 +523,7 @@ class ApplepayButton { caller_page: 'productDetail', product_quantity: this.productQuantity, simplified_contact: event.shippingContact, - need_shipping: this.shouldAllowShippingInButton(), + need_shipping: this.shouldRequireShippingInButton(), 'woocommerce-process-checkout-nonce': this.nonce, }; case 'cart': @@ -471,11 +535,12 @@ class ApplepayButton { action: 'ppcp_update_shipping_contact', simplified_contact: event.shippingContact, caller_page: 'cart', - need_shipping: this.shouldAllowShippingInButton(), + need_shipping: this.shouldRequireShippingInButton(), 'woocommerce-process-checkout-nonce': this.nonce, }; } } + getShippingMethodData(event) { const product_id = this.buttonConfig.product.id; @@ -485,11 +550,11 @@ class ApplepayButton { case 'product': return { action: 'ppcp_update_shipping_method', shipping_method: event.shippingMethod, + simplified_contact: this.updatedContactInfo || this.initialPaymentRequest.shippingContact || this.initialPaymentRequest.billingContact, product_id: product_id, products: JSON.stringify(this.products), caller_page: 'productDetail', product_quantity: this.productQuantity, - simplified_contact: this.updated_contact_info, 'woocommerce-process-checkout-nonce': this.nonce, } case 'cart': @@ -500,14 +565,14 @@ class ApplepayButton { return { action: 'ppcp_update_shipping_method', shipping_method: event.shippingMethod, + simplified_contact: this.updatedContactInfo || this.initialPaymentRequest.shippingContact || this.initialPaymentRequest.billingContact, caller_page: 'cart', - simplified_contact: this.updated_contact_info, 'woocommerce-process-checkout-nonce': this.nonce, } } } - onpaymentauthorized(session) { + onPaymentAuthorized(session) { this.log('onpaymentauthorized'); return async (event) => { this.log('onpaymentauthorized call'); @@ -518,8 +583,10 @@ class ApplepayButton { const processInWooAndCapture = async (data) => { return new Promise((resolve, reject) => { try { - const billingContact = data.billing_contact - const shippingContact = data.shipping_contact + const billingContact = data.billing_contact || this.initialPaymentRequest.billingContact; + const shippingContact = data.shipping_contact || this.initialPaymentRequest.shippingContact; + const shippingMethod = this.selectedShippingMethod || (this.initialPaymentRequest.shippingMethods || [])[0]; + let request_data = { action: 'ppcp_create_order', 'caller_page': this.context, @@ -529,7 +596,7 @@ class ApplepayButton { 'shipping_contact': shippingContact, 'billing_contact': billingContact, 'token': event.payment.token, - 'shipping_method': this.selectedShippingMethod, + 'shipping_method': shippingMethod, 'woocommerce-process-checkout-nonce': this.nonce, 'funding_source': 'applepay', '_wp_http_referer': '/?wc-ajax=update_order_review', @@ -547,16 +614,16 @@ class ApplepayButton { }, success: (authorizationResult, textStatus, jqXHR) => { this.log('onpaymentauthorized ok'); - resolve(authorizationResult) + resolve(authorizationResult); }, error: (jqXHR, textStatus, errorThrown) => { this.log('onpaymentauthorized error', textStatus); reject(new Error(errorThrown)); }, - }) + }); } catch (error) { this.log('onpaymentauthorized catch', error); - console.log(error) // handle error + console.log(error); // handle error } }); } @@ -578,8 +645,8 @@ class ApplepayButton { if (confirmOrderResponse.approveApplePayPayment.status === "APPROVED") { try { - if (!this.shouldAllowShippingInButton()) { - // No shipping, expect immediate capture, ex: PayNow. + if (this.shouldCompletePaymentWithContextHandler()) { + // No shipping, expect immediate capture, ex: PayNow, Checkout with form data. let approveFailed = false; await this.contextHandler.approveOrder({ @@ -602,7 +669,7 @@ class ApplepayButton { } else { this.log('onpaymentauthorized approveOrder FAIL'); session.completePayment(ApplePaySession.STATUS_FAILURE); - session.abort() + session.abort(); console.error(error); } @@ -616,17 +683,17 @@ class ApplepayButton { }; let authorizationResult = await processInWooAndCapture(data); if (authorizationResult.result === "success") { - session.completePayment(ApplePaySession.STATUS_SUCCESS) - window.location.href = authorizationResult.redirect + session.completePayment(ApplePaySession.STATUS_SUCCESS); + window.location.href = authorizationResult.redirect; } else { - session.completePayment(ApplePaySession.STATUS_FAILURE) + session.completePayment(ApplePaySession.STATUS_FAILURE); } } } catch (error) { session.completePayment(ApplePaySession.STATUS_FAILURE); - session.abort() + session.abort(); console.error(error); } } else { @@ -640,43 +707,44 @@ class ApplepayButton { } catch (error) { console.error('Error confirming order with applepay token', error); session.completePayment(ApplePaySession.STATUS_FAILURE); - session.abort() + session.abort(); } }; } - fill_billing_contact(form_saved) { + fillBillingContact(data) { return { - givenName: form_saved.billing_first_name ?? '', - familyName: form_saved.billing_last_name ?? '', - emailAddress: form_saved.billing_email ?? '', - phoneNumber: form_saved.billing_phone ?? '', - addressLines: [form_saved.billing_address_1, form_saved.billing_address_2], - locality: form_saved.billing_city ?? '', - postalCode: form_saved.billing_postcode ?? '', - countryCode: form_saved.billing_country ?? '', - administrativeArea: form_saved.billing_state ?? '', - } - } - fill_shipping_contact(form_saved) { - if (form_saved.shipping_first_name === "") { - return this.fill_billing_contact(form_saved) - } - return { - givenName: (form_saved?.shipping_first_name && form_saved.shipping_first_name !== "") ? form_saved.shipping_first_name : form_saved?.billing_first_name, - familyName: (form_saved?.shipping_last_name && form_saved.shipping_last_name !== "") ? form_saved.shipping_last_name : form_saved?.billing_last_name, - emailAddress: (form_saved?.shipping_email && form_saved.shipping_email !== "") ? form_saved.shipping_email : form_saved?.billing_email, - phoneNumber: (form_saved?.shipping_phone && form_saved.shipping_phone !== "") ? form_saved.shipping_phone : form_saved?.billing_phone, - addressLines: [form_saved.shipping_address_1 ?? '', form_saved.shipping_address_2 ?? ''], - locality: (form_saved?.shipping_city && form_saved.shipping_city !== "") ? form_saved.shipping_city : form_saved?.billing_city, - postalCode: (form_saved?.shipping_postcode && form_saved.shipping_postcode !== "") ? form_saved.shipping_postcode : form_saved?.billing_postcode, - countryCode: (form_saved?.shipping_country && form_saved.shipping_country !== "") ? form_saved.shipping_country : form_saved?.billing_country, - administrativeArea: (form_saved?.shipping_state && form_saved.shipping_state !== "") ? form_saved.shipping_state : form_saved?.billing_state, + givenName: data.billing_first_name ?? '', + familyName: data.billing_last_name ?? '', + emailAddress: data.billing_email ?? '', + phoneNumber: data.billing_phone ?? '', + addressLines: [data.billing_address_1, data.billing_address_2], + locality: data.billing_city ?? '', + postalCode: data.billing_postcode ?? '', + countryCode: data.billing_country ?? '', + administrativeArea: data.billing_state ?? '', } } - fill_application_data(form_saved) { - const jsonString = JSON.stringify(form_saved); + fillShippingContact(data) { + if (data.shipping_first_name === "") { + return this.fillBillingContact(data); + } + return { + givenName: (data?.shipping_first_name && data.shipping_first_name !== "") ? data.shipping_first_name : data?.billing_first_name, + familyName: (data?.shipping_last_name && data.shipping_last_name !== "") ? data.shipping_last_name : data?.billing_last_name, + emailAddress: (data?.shipping_email && data.shipping_email !== "") ? data.shipping_email : data?.billing_email, + phoneNumber: (data?.shipping_phone && data.shipping_phone !== "") ? data.shipping_phone : data?.billing_phone, + addressLines: [data.shipping_address_1 ?? '', data.shipping_address_2 ?? ''], + locality: (data?.shipping_city && data.shipping_city !== "") ? data.shipping_city : data?.billing_city, + postalCode: (data?.shipping_postcode && data.shipping_postcode !== "") ? data.shipping_postcode : data?.billing_postcode, + countryCode: (data?.shipping_country && data.shipping_country !== "") ? data.shipping_country : data?.billing_country, + administrativeArea: (data?.shipping_state && data.shipping_state !== "") ? data.shipping_state : data?.billing_state, + } + } + + fillApplicationData(data) { + const jsonString = JSON.stringify(data); let utf8Str = encodeURIComponent(jsonString).replace(/%([0-9A-F]{2})/g, (match, p1) => { return String.fromCharCode('0x' + p1); }); diff --git a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js index 958508de2..6934916d8 100644 --- a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js @@ -15,12 +15,14 @@ class BaseHandler { transactionInfo() { return new Promise((resolve, reject) => { + const endpoint = this.ppcpConfig.ajax.cart_script_params.endpoint; + const separator = (endpoint.indexOf('?') !== -1) ? '&' : '?'; fetch( - this.ppcpConfig.ajax.cart_script_params.endpoint, + endpoint + separator + 'shipping=1', { method: 'GET', - credentials: 'same-origin', + credentials: 'same-origin' } ) .then(result => result.json()) diff --git a/modules/ppcp-applepay/resources/js/Context/CheckoutHandler.js b/modules/ppcp-applepay/resources/js/Context/CheckoutHandler.js index 9d134fc55..67c88162a 100644 --- a/modules/ppcp-applepay/resources/js/Context/CheckoutHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/CheckoutHandler.js @@ -1,28 +1,17 @@ import Spinner from "../../../../ppcp-button/resources/js/modules/Helper/Spinner"; import CheckoutActionHandler from "../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler"; -import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler"; import BaseHandler from "./BaseHandler"; class CheckoutHandler extends BaseHandler { - createOrder() { - const errorHandler = new ErrorHandler( - this.ppcpConfig.labels.error.generic, - document.querySelector('.woocommerce-notices-wrapper') - ); - - const spinner = new Spinner(); - - const actionHandler = new CheckoutActionHandler( + actionHandler() { + return new CheckoutActionHandler( this.ppcpConfig, - errorHandler, - spinner + this.errorHandler(), + new Spinner() ); - - return actionHandler.configuration().createOrder(null, null); } - } export default CheckoutHandler; diff --git a/modules/ppcp-applepay/src/Assets/ApplePayButton.php b/modules/ppcp-applepay/src/Assets/ApplePayButton.php index 701f1a7f8..c34a9fc6c 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayButton.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayButton.php @@ -312,7 +312,6 @@ class ApplePayButton implements ButtonInterface { $user_country, $allowed_shipping_countries ); - $product_need_shipping = $applepay_request_data_object->need_shipping(); if ( ! $is_allowed_selling_country ) { $this->response_templates->response_with_data_errors( @@ -320,7 +319,7 @@ class ApplePayButton implements ButtonInterface { ); return; } - if ( $product_need_shipping && ! $is_allowed_shipping_country ) { + if ( $applepay_request_data_object->need_shipping() && ! $is_allowed_shipping_country ) { $this->response_templates->response_with_data_errors( array( array( 'errorCode' => 'addressUnserviceable' ) ) ); @@ -406,9 +405,6 @@ class ApplePayButton implements ButtonInterface { $applepay_request_data_object = $this->applepay_data_object_http(); //phpcs:disable WordPress.Security.NonceVerification - $this->logger->info('== $_POST =='); - $this->logger->info(print_r($_POST, true)); - $context = wc_clean( wp_unslash( $_POST['caller_page'] ?? '' ) ); if ( ! is_string( $context ) ) { $this->response_templates->response_with_data_errors( @@ -423,9 +419,6 @@ class ApplePayButton implements ButtonInterface { } $applepay_request_data_object->order_data( $context ); - $this->logger->info('== $applepay_request_data_object =='); - $this->logger->info(print_r($applepay_request_data_object, true)); - $this->update_posted_data( $applepay_request_data_object ); if ( $context === 'product' ) { $cart_item_key = $this->prepare_cart( $applepay_request_data_object ); @@ -459,7 +452,7 @@ class ApplePayButton implements ButtonInterface { } ); } - $this->add_addresses_to_order( $applepay_request_data_object ); + WC()->checkout()->process_checkout(); } @@ -773,33 +766,6 @@ class ApplePayButton implements ButtonInterface { return $results; } - /** - * Add address billing and shipping data to order - * - * @param ApplePayDataObjectHttp $applepay_request_data_object ApplePayDataObjectHttp. - */ - protected function add_addresses_to_order( - ApplePayDataObjectHttp $applepay_request_data_object - ): void { - add_action( - 'woocommerce_checkout_create_order', - static function ( WC_Order $order, array $data ) use ( $applepay_request_data_object ) { -$this->logger->info('== HOOK woocommerce_checkout_create_order'); - if ( ! empty( $applepay_request_data_object->shipping_method() ) ) { - $billing_address = $applepay_request_data_object->billing_address(); - $shipping_address = $applepay_request_data_object->shipping_address(); - // apple puts email in shipping_address while we get it from WC's billing_address. - $billing_address['email'] = $shipping_address['email']; - $billing_address['phone'] = $shipping_address['phone']; -$this->logger->info(print_r($billing_address, true)); - $order->set_address( $billing_address, 'billing' ); - $order->set_address( $shipping_address, 'shipping' ); - } - }, - 10, - 2 - ); - } /** * Empty the cart to use for calculations * while saving its contents in a field @@ -881,8 +847,6 @@ $this->logger->info(print_r($billing_address, true)); add_filter( 'woocommerce_checkout_posted_data', function ( array $data ) use ( $applepay_request_data_object ): array { -$this->logger->info('== HOOK woocommerce_checkout_posted_data'); - $data['payment_method'] = 'ppcp-gateway'; $data['shipping_method'] = $applepay_request_data_object->shipping_method(); $data['billing_first_name'] = $applepay_request_data_object->billing_address()['first_name'] ?? ''; @@ -894,10 +858,18 @@ $this->logger->info('== HOOK woocommerce_checkout_posted_data'); $data['billing_city'] = $applepay_request_data_object->billing_address()['city'] ?? ''; $data['billing_state'] = $applepay_request_data_object->billing_address()['state'] ?? ''; $data['billing_postcode'] = $applepay_request_data_object->billing_address()['postcode'] ?? ''; + $data['billing_email'] = $applepay_request_data_object->billing_address()['email'] ?? ''; + $data['billing_phone'] = $applepay_request_data_object->billing_address()['phone'] ?? ''; - if ( ! empty( $applepay_request_data_object->need_shipping() ) ) { - $data['billing_email'] = $applepay_request_data_object->shipping_address()['email'] ?? ''; - $data['billing_phone'] = $applepay_request_data_object->shipping_address()['phone'] ?? ''; + // ApplePay doesn't send us a billing email or phone, use the shipping contacts instead. + if ( ! ( $data['billing_email'] ?? false ) ) { + $data['billing_email'] = $applepay_request_data_object->shipping_address()['email'] ?? ''; + } + if ( ! ( $data['billing_phone'] ?? false ) ) { + $data['billing_phone'] = $applepay_request_data_object->shipping_address()['phone'] ?? ''; + } + + if ( ! empty( $applepay_request_data_object->shipping_method() ) ) { $data['shipping_first_name'] = $applepay_request_data_object->shipping_address()['first_name'] ?? ''; $data['shipping_last_name'] = $applepay_request_data_object->shipping_address()['last_name'] ?? ''; $data['shipping_company'] = $applepay_request_data_object->shipping_address()['company'] ?? ''; @@ -910,7 +882,6 @@ $this->logger->info('== HOOK woocommerce_checkout_posted_data'); $data['shipping_email'] = $applepay_request_data_object->shipping_address()['email'] ?? ''; $data['shipping_phone'] = $applepay_request_data_object->shipping_address()['phone'] ?? ''; } -$this->logger->info(print_r($data, true)); return $data; } diff --git a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php index 1c3891b48..6c29c726d 100644 --- a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php +++ b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php @@ -202,29 +202,29 @@ class DataToAppleButtonScripts { $checkout_data_mode = $this->settings->has( 'applepay_checkout_data_mode' ) ? $this->settings->get( 'applepay_checkout_data_mode' ) : PropertiesDictionary::BILLING_DATA_MODE_DEFAULT; return array( - 'sdk_url' => $this->sdk_url, - 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false, + 'sdk_url' => $this->sdk_url, + 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false, 'preferences' => array( 'checkout_data_mode' => $checkout_data_mode, ), - 'button' => array( + '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' ), - 'nonce' => wp_create_nonce( 'woocommerce-process_checkout' ), + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'woocommerce-process_checkout' ), ); } @@ -250,28 +250,28 @@ class DataToAppleButtonScripts { $checkout_data_mode = $this->settings->has( 'applepay_checkout_data_mode' ) ? $this->settings->get( 'applepay_checkout_data_mode' ) : PropertiesDictionary::BILLING_DATA_MODE_DEFAULT; return array( - 'sdk_url' => $this->sdk_url, - 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false, + 'sdk_url' => $this->sdk_url, + 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false, 'preferences' => array( 'checkout_data_mode' => $checkout_data_mode, ), - 'button' => array( + 'button' => array( 'wrapper' => 'applepay-container', 'mini_cart_wrapper' => 'applepay-container-minicart', 'type' => $type, 'color' => $color, 'lang' => $lang, ), - 'product' => array( + 'product' => array( 'needShipping' => false, 'subtotal' => 0, ), - 'shop' => array( + 'shop' => array( 'countryCode' => $shop_country_code, 'currencyCode' => $currency_code, 'totalLabel' => $total_label, ), - 'ajax_url' => admin_url( 'admin-ajax.php' ), + '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 4cdb3190f..da859e2c2 100644 --- a/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php +++ b/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php @@ -198,8 +198,8 @@ class PropertiesDictionary { */ public static function billing_data_modes(): array { return array( - PropertiesDictionary::BILLING_DATA_MODE_USE_WC => __( 'Use WC checkout form data (do not show shipping address fields)', 'woocommerce-paypal-payments' ), - PropertiesDictionary::BILLING_DATA_MODE_USE_APPLEPAY => __( 'Do not use WC checkout form data (request billing and shipping addresses on Apple Pay)', 'woocommerce-paypal-payments' ), + self::BILLING_DATA_MODE_USE_WC => __( 'Use WC checkout form data (do not show shipping address fields)', 'woocommerce-paypal-payments' ), + self::BILLING_DATA_MODE_USE_APPLEPAY => __( 'Do not use WC checkout form data (request billing and shipping addresses on Apple Pay)', 'woocommerce-paypal-payments' ), ); } } diff --git a/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php b/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php index b8bde1c45..316d2c000 100644 --- a/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php @@ -70,6 +70,8 @@ class CartScriptParamsEndpoint implements EndpointInterface { wc_maybe_define_constant( 'WOOCOMMERCE_CART', true ); } + $include_shipping = (bool) wc_clean( wp_unslash( $_GET['shipping'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $script_data = $this->smart_button->script_data(); $total = (float) WC()->cart->get_total( 'numeric' ); @@ -79,52 +81,23 @@ class CartScriptParamsEndpoint implements EndpointInterface { $shop_country_code = $base_location['country'] ?? ''; $currency_code = get_woocommerce_currency(); + $response = array( + 'url_params' => $script_data['url_params'], + 'button' => $script_data['button'], + 'messages' => $script_data['messages'], + 'amount' => WC()->cart->get_total( 'raw' ), - $calculated_packages = WC()->shipping->calculate_shipping( - WC()->cart->get_shipping_packages() + 'total' => $total, + 'total_str' => ( new Money( $total, $currency_code ) )->value_str(), + 'currency_code' => $currency_code, + 'country_code' => $shop_country_code, ); - $shipping_packages = array(); - - foreach ( $calculated_packages[0]['rates'] as $rate ) { - $rate_cost = $rate->get_cost(); - - /** - * The shipping rate. - * - * @var \WC_Shipping_Rate $rate - */ - $shipping_packages[] = array( - 'id' => $rate->get_id(), - 'label' => $rate->get_label(), - 'cost' => (float) $rate_cost, - 'cost_str' => ( new Money( (float) $rate_cost, $currency_code ) )->value_str(), - 'description' => html_entity_decode( - wp_strip_all_tags( - wc_price( (float) $rate->get_cost(), array( 'currency' => get_woocommerce_currency() ) ) - ) - ), - ); + if ( $include_shipping ) { + $response = $this->append_shipping_data( $response, $currency_code ); } - - wp_send_json_success( - array( - 'url_params' => $script_data['url_params'], - 'button' => $script_data['button'], - 'messages' => $script_data['messages'], - 'amount' => WC()->cart->get_total( 'raw' ), - - 'total' => $total, - 'total_str' => ( new Money( $total, $currency_code ) )->value_str(), - 'currency_code' => $currency_code, - 'country_code' => $shop_country_code, - - 'chosen_shipping_methods' => WC()->session->get( 'chosen_shipping_methods' ), - 'shipping_packages' => $shipping_packages - ) - ); - + wp_send_json_success( $response ); return true; } catch ( Throwable $error ) { $this->logger->error( "CartScriptParamsEndpoint execution failed. {$error->getMessage()} {$error->getFile()}:{$error->getLine()}" ); @@ -133,4 +106,45 @@ class CartScriptParamsEndpoint implements EndpointInterface { return false; } } + + /** + * Appends shipping data to response. + * + * @param array $response The response array. + * @param string $currency_code The currency code. + * @return array + */ + private function append_shipping_data( array $response, string $currency_code ): array { + $calculated_packages = WC()->shipping->calculate_shipping( + WC()->cart->get_shipping_packages() + ); + + $shipping_packages = array(); + + foreach ( $calculated_packages[0]['rates'] as $rate ) { + $rate_cost = $rate->get_cost(); + + /** + * The shipping rate. + * + * @var \WC_Shipping_Rate $rate + */ + $shipping_packages[] = array( + 'id' => $rate->get_id(), + 'label' => $rate->get_label(), + 'cost' => (float) $rate_cost, + 'cost_str' => ( new Money( (float) $rate_cost, $currency_code ) )->value_str(), + 'description' => html_entity_decode( + wp_strip_all_tags( + wc_price( (float) $rate->get_cost(), array( 'currency' => get_woocommerce_currency() ) ) + ) + ), + ); + } + + $response['chosen_shipping_methods'] = WC()->session->get( 'chosen_shipping_methods' ); + $response['shipping_packages'] = $shipping_packages; + + return $response; + } } diff --git a/modules/ppcp-googlepay/extensions.php b/modules/ppcp-googlepay/extensions.php index e2080a8f5..1583e5c6e 100644 --- a/modules/ppcp-googlepay/extensions.php +++ b/modules/ppcp-googlepay/extensions.php @@ -142,7 +142,7 @@ return array( 'This controls the label of the Google Pay button.', 'woocommerce-paypal-payments' ), - 'classes' => array('ppcp-field-indent'), + 'classes' => array( 'ppcp-field-indent' ), 'class' => array(), 'input_class' => array( 'wc-enhanced-select' ), 'default' => 'pay', @@ -161,7 +161,7 @@ return array( ), 'label' => '', 'input_class' => array( 'wc-enhanced-select' ), - 'classes' => array('ppcp-field-indent'), + 'classes' => array( 'ppcp-field-indent' ), 'class' => array(), 'default' => 'black', 'options' => PropertiesDictionary::button_colors(), @@ -177,7 +177,7 @@ return array( 'The language and region used for the displayed Google Pay button. The default value is the current language and region setting in a browser.', 'woocommerce-paypal-payments' ), - 'classes' => array('ppcp-field-indent'), + 'classes' => array( 'ppcp-field-indent' ), 'class' => array(), 'input_class' => array( 'wc-enhanced-select' ), 'default' => 'en', @@ -194,7 +194,7 @@ return array( 'Synchronizes your available shipping options with Google Pay. Enabling this may impact the buyer experience.', 'woocommerce-paypal-payments' ), - 'classes' => array('ppcp-field-indent'), + 'classes' => array( 'ppcp-field-indent' ), 'label' => __( 'Enable Google Pay shipping callback', 'woocommerce-paypal-payments' ), 'default' => 'no', 'screens' => array( State::STATE_ONBOARDED ), From c8d4c12a2d8baf9910b9cbb8d258718bb06a1726 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Mon, 13 Nov 2023 13:55:08 +0000 Subject: [PATCH 07/11] Fix GooglePay and ApplePay loading scripts on admin standard payments page. --- modules/ppcp-applepay/src/ApplepayModule.php | 3 ++- modules/ppcp-googlepay/src/GooglepayModule.php | 3 ++- modules/ppcp-wc-gateway/services.php | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-applepay/src/ApplepayModule.php b/modules/ppcp-applepay/src/ApplepayModule.php index a02e26884..86aaff278 100644 --- a/modules/ppcp-applepay/src/ApplepayModule.php +++ b/modules/ppcp-applepay/src/ApplepayModule.php @@ -164,6 +164,7 @@ class ApplepayModule implements ModuleInterface { assert( $smart_button instanceof SmartButtonInterface ); if ( $smart_button->should_load_ppcp_script() ) { $button->enqueue(); + return; } if ( has_block( 'woocommerce/checkout' ) || has_block( 'woocommerce/cart' ) ) { @@ -196,7 +197,7 @@ class ApplepayModule implements ModuleInterface { add_action( 'admin_enqueue_scripts', static function () use ( $c, $button ) { - if ( ! is_admin() ) { + if ( ! is_admin() || ! $c->get( 'wcgateway.is-ppcp-settings-standard-payments-page' ) ) { return; } diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index 083f8eba7..e478881ba 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -94,6 +94,7 @@ class GooglepayModule implements ModuleInterface { assert( $smart_button instanceof SmartButtonInterface ); if ( $smart_button->should_load_ppcp_script() ) { $button->enqueue(); + return; } if ( has_block( 'woocommerce/checkout' ) || has_block( 'woocommerce/cart' ) ) { @@ -111,7 +112,7 @@ class GooglepayModule implements ModuleInterface { add_action( 'admin_enqueue_scripts', static function () use ( $c, $button ) { - if ( ! is_admin() ) { + if ( ! is_admin() || ! $c->get( 'wcgateway.is-ppcp-settings-standard-payments-page' ) ) { return; } diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 063afa9a3..8c0f4474a 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -182,6 +182,11 @@ return array( ); }, + 'wcgateway.is-ppcp-settings-standard-payments-page' => static function ( ContainerInterface $container ): bool { + return $container->get( 'wcgateway.is-ppcp-settings-page' ) + && $container->get( 'wcgateway.current-ppcp-settings-page-id' ) === PayPalGateway::ID; + }, + 'wcgateway.current-ppcp-settings-page-id' => static function ( ContainerInterface $container ): string { if ( ! $container->get( 'wcgateway.is-ppcp-settings-page' ) ) { return ''; From 24f98cd038fc8f3536172dbcca3c589b1a396fe8 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Mon, 13 Nov 2023 17:36:18 +0000 Subject: [PATCH 08/11] Disabled GooglePay and ApplePay on locations with subsciption products. --- .../ppcp-applepay/resources/js/ApplepayButton.js | 6 +++++- .../resources/js/Context/BaseHandler.js | 7 +++++++ .../resources/js/Context/PayNowHandler.js | 7 +++++++ .../resources/js/Context/SingleProductHandler.js | 7 +++++++ modules/ppcp-button/src/Assets/SmartButton.php | 1 + .../resources/js/Context/BaseHandler.js | 7 +++++++ .../resources/js/Context/PayNowHandler.js | 7 +++++++ .../resources/js/Context/SingleProductHandler.js | 7 +++++++ .../ppcp-googlepay/resources/js/GooglepayButton.js | 4 ++++ .../src/Helper/SubscriptionHelper.php | 13 +++++++++++++ 10 files changed, 65 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 8c35c9eb5..91a2f7f08 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -42,6 +42,10 @@ class ApplepayButton { return; } + if (!this.contextHandler.validateContext()) { + return; + } + this.log('Init', this.context); this.initEventHandlers(); this.isInitialized = true; @@ -263,7 +267,7 @@ class ApplepayButton { switch (this.context) { case 'product': // Refresh product data that makes the price change. - this.productQuantity = document.querySelector('input.qty').value; + this.productQuantity = document.querySelector('input.qty')?.value; this.products = this.contextHandler.products(); this.log('Products updated', this.products); break; diff --git a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js index 3deb4a47e..1201ed084 100644 --- a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js @@ -9,6 +9,13 @@ class BaseHandler { this.ppcpConfig = ppcpConfig; } + validateContext() { + if ( this.ppcpConfig?.locations_with_subscription_product?.cart ) { + return false; + } + return true; + } + shippingAllowed() { return true; } diff --git a/modules/ppcp-applepay/resources/js/Context/PayNowHandler.js b/modules/ppcp-applepay/resources/js/Context/PayNowHandler.js index add275608..a580c4084 100644 --- a/modules/ppcp-applepay/resources/js/Context/PayNowHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/PayNowHandler.js @@ -5,6 +5,13 @@ import CheckoutActionHandler class PayNowHandler extends BaseHandler { + validateContext() { + if ( this.ppcpConfig?.locations_with_subscription_product?.payorder ) { + return false; + } + return true; + } + shippingAllowed() { return false; } diff --git a/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js b/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js index c5d0549d2..5825a1f2c 100644 --- a/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js @@ -7,6 +7,13 @@ import BaseHandler from "./BaseHandler"; class SingleProductHandler extends BaseHandler { + validateContext() { + if ( this.ppcpConfig?.locations_with_subscription_product?.product ) { + return false; + } + return true; + } + transactionInfo() { const errorHandler = new ErrorHandler( this.ppcpConfig.labels.error.generic, diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 049b1ab00..e8e0796ff 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -973,6 +973,7 @@ document.querySelector("#payment").before(document.querySelector("#ppcp-messages 'subscription_plan_id' => $this->subscription_helper->paypal_subscription_id(), 'variable_paypal_subscription_variations' => $this->subscription_helper->variable_paypal_subscription_variations(), 'subscription_product_allowed' => $this->subscription_helper->checkout_subscription_product_allowed(), + 'locations_with_subscription_product' => $this->subscription_helper->locations_with_subscription_product(), 'enforce_vault' => $this->has_subscriptions(), 'can_save_vault_token' => $this->can_save_vault_token(), 'is_free_trial_cart' => $is_free_trial_cart, diff --git a/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js b/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js index 1074c94ec..fe0054f63 100644 --- a/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js @@ -10,6 +10,13 @@ class BaseHandler { this.externalHandler = externalHandler; } + validateContext() { + if ( this.ppcpConfig?.locations_with_subscription_product?.cart ) { + return false; + } + return true; + } + shippingAllowed() { return true; } diff --git a/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js b/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js index add275608..a580c4084 100644 --- a/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js @@ -5,6 +5,13 @@ import CheckoutActionHandler class PayNowHandler extends BaseHandler { + validateContext() { + if ( this.ppcpConfig?.locations_with_subscription_product?.payorder ) { + return false; + } + return true; + } + shippingAllowed() { return false; } diff --git a/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js b/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js index 6760cfb80..60563ded9 100644 --- a/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js @@ -7,6 +7,13 @@ import BaseHandler from "./BaseHandler"; class SingleProductHandler extends BaseHandler { + validateContext() { + if ( this.ppcpConfig?.locations_with_subscription_product?.product ) { + return false; + } + return true; + } + transactionInfo() { const errorHandler = new ErrorHandler( this.ppcpConfig.labels.error.generic, diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 9ecb778d5..61b5451ba 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -40,6 +40,10 @@ class GooglepayButton { return; } + if (!this.contextHandler.validateContext()) { + return; + } + this.googlePayConfig = config; this.allowedPaymentMethods = config.allowedPaymentMethods; this.baseCardPaymentMethod = this.allowedPaymentMethods[0]; diff --git a/modules/ppcp-subscription/src/Helper/SubscriptionHelper.php b/modules/ppcp-subscription/src/Helper/SubscriptionHelper.php index 9f803c5c5..b71156f86 100644 --- a/modules/ppcp-subscription/src/Helper/SubscriptionHelper.php +++ b/modules/ppcp-subscription/src/Helper/SubscriptionHelper.php @@ -271,4 +271,17 @@ class SubscriptionHelper { return true; } + + /** + * Returns the locations on the page which have subscription products. + * + * @return array + */ + public function locations_with_subscription_product(): array { + return array( + 'product' => is_product() && $this->current_product_is_subscription(), + 'payorder' => is_wc_endpoint_url( 'order-pay' ) && $this->order_pay_contains_subscription(), + 'cart' => $this->cart_contains_subscription(), + ); + } } From 6fe60864442c36a8aff819f14dbdb1afe7a468a1 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Tue, 14 Nov 2023 12:37:15 +0100 Subject: [PATCH 09/11] update CI matrix --- .github/workflows/e2e.yml | 2 +- .github/workflows/php.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 0bf80c13b..e47208e50 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: ['7.3', '7.4', '8.1'] + php-versions: ['7.3', '7.4', '8.2'] wc-versions: ['5.9.5', '7.7.2'] name: PHP ${{ matrix.php-versions }} WC ${{ matrix.wc-versions }} diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 186c90837..ed3804b04 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1'] + php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] name: PHP ${{ matrix.php-versions }} steps: From 581f99d187a86e534b152b7bf5768170559eea22 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Tue, 14 Nov 2023 15:48:51 +0100 Subject: [PATCH 10/11] Add 8.3 version in CI --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index ed3804b04..f4967e186 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] + php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] name: PHP ${{ matrix.php-versions }} steps: From 206db7688722a47b4936cb6ccd017a775d7c2850 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Tue, 14 Nov 2023 16:33:19 +0000 Subject: [PATCH 11/11] Fix ApplePay device detection for edge cases. --- modules/ppcp-applepay/services.php | 19 ++++++++++++++++--- .../src/Assets/PropertiesDictionary.php | 11 +++++++++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-applepay/services.php b/modules/ppcp-applepay/services.php index 96a237f03..0f1266006 100644 --- a/modules/ppcp-applepay/services.php +++ b/modules/ppcp-applepay/services.php @@ -98,11 +98,24 @@ return array( return false; } } - foreach ( PropertiesDictionary::ALLOWED_USER_AGENTS as $allowed_agent ) { - if ( strpos( $user_agent, $allowed_agent ) !== false ) { - return true; + + $browser_allowed = false; + foreach ( PropertiesDictionary::ALLOWED_USER_BROWSERS as $allowed_browser ) { + if ( strpos( $user_agent, $allowed_browser ) !== false ) { + $browser_allowed = true; + break; } } + + $device_allowed = false; + foreach ( PropertiesDictionary::ALLOWED_USER_DEVICES as $allowed_devices ) { + if ( strpos( $user_agent, $allowed_devices ) !== false ) { + $device_allowed = true; + break; + } + } + + return $browser_allowed && $device_allowed; } return false; }, diff --git a/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php b/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php index ce4be2a46..9367e2e51 100644 --- a/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php +++ b/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php @@ -13,8 +13,15 @@ namespace WooCommerce\PayPalCommerce\Applepay\Assets; * Class PropertiesDictionary */ class PropertiesDictionary { - public const DISALLOWED_USER_AGENTS = array( 'Chrome' ); - public const ALLOWED_USER_AGENTS = array( 'Safari', 'Macintosh', 'iPhone', 'iPad', 'iPod' ); + public const DISALLOWED_USER_AGENTS = array( + 'Chrome/', + 'CriOS/', // Chrome on iOS. + 'Firefox/', + 'OPR/', // Opera. + 'Edg/', // Edge. + ); + public const ALLOWED_USER_BROWSERS = array( 'Safari' ); + public const ALLOWED_USER_DEVICES = array( 'Macintosh', 'iPhone', 'iPad', 'iPod' ); public const BILLING_CONTACT_INVALID = 'billing Contact Invalid';