diff --git a/changelog.txt b/changelog.txt index a840377a9..52e9534b1 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,25 @@ *** Changelog *** += 2.4.1 - xxxx-xx-xx = +* Fix - Error "PayPal order ID not found in meta" prevents automations from triggering when buying subscription via third-party payment gateway #1822 +* Fix - Card button subscription support declaration #1796 +* Fix - Pay Later messaging disappears when updating shipping option on cart page #1807 +* Fix - Apple Pay payment from single product may fail after changing shipping options in Apple Pay payment sheet #1810 +* Enhancement - Extend list of supported countries for Advanced Card Processing #1808 +* Enhancement - Extend Apple Pay/Google Pay country eligibility to Italy #1811 +* Enhancement - Override language used to display PayPal buttons #600 +* Enhancement - Apple Pay button preview #1824 +* Enhancement - Add Apple Pay & Google Pay logos on the onboarding page #1823 +* Enhancement - Improve Apple Pay compatibility with variable products on single product page #1803 +* Enhancement - Apple Pay domain registration & browser eligibility check #1821 +* Enhancement - Package Tracking compatibility with WooCommerce Shipping & ShipStation for WooCommerce #1813 +* Enhancement - Fill form when continuation in block #1794 +* Enhancement - Display Shop location Pay Later messaging on product category pages #1809 +* Enhancement - Present apple-developer-merchantid-domain-association file only when Apple Pay is enabled #1818 +* Enhancement - Improve Apple Pay compatibility on Pay for Order page #1815 +* Enhancement - Display Pay Later messages before the payment methods on the Pay for Order page #1814 +* Enhancement - Handle undefined array key warnings on PHP 8.1 #1804 + = 2.4.0 - 2023-10-31 = * Fix - Mini-Cart Bug cause of wrong DOM-Structure in v2.3.1 #1735 * Fix - ACDC disappearing after plugin updates #1751 diff --git a/modules/ppcp-applepay/extensions.php b/modules/ppcp-applepay/extensions.php index 39aa3ed81..bdea5a19f 100644 --- a/modules/ppcp-applepay/extensions.php +++ b/modules/ppcp-applepay/extensions.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Applepay; use WooCommerce\PayPalCommerce\Applepay\Assets\PropertiesDictionary; +use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager; @@ -37,6 +38,34 @@ return array( $display_manager = $container->get( 'wcgateway.display-manager' ); assert( $display_manager instanceof DisplayManager ); + // Domain registration. + $env = $container->get( 'onboarding.environment' ); + assert( $env instanceof Environment ); + + $domain_registration_url = 'https://www.paypal.com/uccservicing/apm/applepay'; + if ( $env->current_environment_is( Environment::SANDBOX ) ) { + $domain_registration_url = 'https://www.sandbox.paypal.com/uccservicing/apm/applepay'; + } + + // Domain validation. + $domain_validation_text = __( 'Status: Domain validation failed ❌', 'woocommerce-paypal-payments' ); + if ( $container->get( 'applepay.is_validated' ) ) { + $domain_validation_text = __( 'Status: Domain successfully validated ✔️', 'woocommerce-paypal-payments' ); + } + + // Device eligibility. + $device_eligibility_text = __( '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' ), + '', + '' + ); + if ( $container->get( 'applepay.is_browser_supported' ) ) { + $device_eligibility_text = __( 'Your browser/device supports 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' ); + } + // Connection tab fields. $fields = $insert_after( $fields, @@ -100,7 +129,7 @@ return array( $fields, 'allow_card_button_gateway', array( - 'applepay_button_enabled' => array( + 'applepay_button_enabled' => array( 'title' => __( 'Apple Pay Button', 'woocommerce-paypal-payments' ), 'type' => 'checkbox', 'label' => __( 'Enable Apple Pay button', 'woocommerce-paypal-payments' ) @@ -122,6 +151,9 @@ return array( $display_manager ->rule() ->condition_element( 'applepay_button_enabled', '1' ) + ->action_visible( 'applepay_button_domain_registration' ) + ->action_visible( 'applepay_button_domain_validation' ) + ->action_visible( 'applepay_button_device_eligibility' ) ->action_visible( 'applepay_button_color' ) ->action_visible( 'applepay_button_type' ) ->action_visible( 'applepay_button_language' ) @@ -130,7 +162,66 @@ return array( ), ), ), - 'applepay_button_type' => array( + 'applepay_button_domain_registration' => array( + 'title' => str_repeat( ' ', 6 ) . __( 'Domain Registration', 'woocommerce-paypal-payments' ), + 'type' => 'ppcp-text', + 'text' => + '' + . __( 'Manage Domain Registration', 'woocommerce-paypal-payments' ) + . '' + . '
' + . __( 'Any (sub)domain names showing an Apple Pay button must be registered on the PayPal website. If the domain displaying the Apple Pay button isn\'t registered, the payment method won\'t work.', 'woocommerce-paypal-payments' ) + . '
', + 'desc_tip' => true, + 'description' => __( + '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' + ), + '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' ), + 'type' => 'ppcp-text', + 'text' => $domain_validation_text + . '' + . sprintf( + // translators: %1$s and %2$s are the opening and closing of HTML tag. + __( 'Note: PayPal Payments automatically presents the %1$sdomain association file%2$s for Apple to validate your registered domain.', 'woocommerce-paypal-payments' ), + '', + '' + ) + . '
', + 'desc_tip' => true, + 'description' => __( + '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' + ), + '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' ), + 'type' => 'ppcp-text', + 'text' => $device_eligibility_text + . '' + . $device_eligibility_notes + . '
', + 'desc_tip' => true, + 'description' => __( + 'Apple Pay demands certain Apple devices for secure payment execution. This helps determine if your current device is compliant.', + 'woocommerce-paypal-payments' + ), + 'class' => array(), + 'screens' => array( State::STATE_ONBOARDED ), + 'gateway' => 'paypal', + 'requirements' => array(), + ), + 'applepay_button_type' => array( 'title' => str_repeat( ' ', 6 ) . __( 'Button Label', 'woocommerce-paypal-payments' ), 'type' => 'select', 'desc_tip' => true, @@ -146,7 +237,7 @@ return array( 'gateway' => 'paypal', 'requirements' => array(), ), - 'applepay_button_color' => array( + 'applepay_button_color' => array( 'title' => str_repeat( ' ', 6 ) . __( 'Button Color', 'woocommerce-paypal-payments' ), 'type' => 'select', 'desc_tip' => true, @@ -163,7 +254,7 @@ return array( 'gateway' => 'paypal', 'requirements' => array(), ), - 'applepay_button_language' => array( + 'applepay_button_language' => array( 'title' => str_repeat( ' ', 6 ) . __( 'Button Language', 'woocommerce-paypal-payments' ), 'type' => 'select', 'desc_tip' => true, diff --git a/modules/ppcp-applepay/resources/css/styles.scss b/modules/ppcp-applepay/resources/css/styles.scss index d3f834119..5eabe5640 100644 --- a/modules/ppcp-applepay/resources/css/styles.scss +++ b/modules/ppcp-applepay/resources/css/styles.scss @@ -1,11 +1,11 @@ -#applepay-container { +#applepay-container, .ppcp-button-applepay { --apple-pay-button-height: 45px; --apple-pay-button-min-height: 40px; --apple-pay-button-width: 100%; --apple-pay-button-max-width: 750px; --apple-pay-button-border-radius: 4px; --apple-pay-button-overflow: hidden; - --apple-pay-button-margin:7px 0; + margin:7px 0; &.ppcp-button-pill { --apple-pay-button-border-radius: 50px; } @@ -17,7 +17,7 @@ } .woocommerce-checkout { - #applepay-container { + #applepay-container, .ppcp-button-applepay { margin-top: 0.5em; --apple-pay-button-border-radius: 4px; --apple-pay-button-height: 45px; @@ -30,7 +30,7 @@ .ppcp-has-applepay-block { .wp-block-woocommerce-checkout { - #applepay-container { + #applepay-container, .ppcp-button-applepay { --apple-pay-button-margin: 0; --apple-pay-button-height: 40px; &.ppcp-button-pill { @@ -40,7 +40,7 @@ } .wp-block-woocommerce-cart { - #applepay-container { + #applepay-container, .ppcp-button-applepay { --apple-pay-button-margin: 0; --apple-pay-button-height: 40px; } @@ -52,5 +52,17 @@ } } } - +} + +.wp-admin { + .ppcp-button-applepay { + pointer-events: none; + } + &.ppcp-non-ios-device { + .ppcp-button-applepay { + apple-pay-button { + display: block; + } + } + } } diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index f9c188741..8c35c9eb5 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -4,6 +4,7 @@ import {setVisible} from '../../../ppcp-button/resources/js/modules/Helper/Hidin import {setEnabled} from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler'; import FormValidator from "../../../ppcp-button/resources/js/modules/Helper/FormValidator"; import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler'; +import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder"; class ApplepayButton { @@ -48,7 +49,7 @@ class ApplepayButton { const isEligible = this.applePayConfig.isEligible; if (isEligible) { this.fetchTransactionInfo().then(() => { - const isSubscriptionProduct = this.ppcpConfig.data_client_id.has_subscriptions === true; + const isSubscriptionProduct = this.ppcpConfig?.data_client_id?.has_subscriptions === true; if (isSubscriptionProduct) { return; } @@ -145,13 +146,12 @@ class ApplepayButton { return session; } - - - /** * Add a Apple Pay purchase button */ addButton() { + this.log('addButton', this.context); + const wrapper = (this.context === 'mini-cart') ? this.buttonConfig.button.mini_cart_wrapper @@ -160,7 +160,7 @@ class ApplepayButton { (this.context === 'mini-cart') ? this.ppcpConfig.button.mini_cart_style.shape : this.ppcpConfig.button.style.shape; - const appleContainer = this.context === 'mini-cart' ? document.getElementById("applepay-container-minicart") : document.getElementById("applepay-container"); + const appleContainer = document.getElementById(wrapper); const type = this.buttonConfig.button.type; const language = this.buttonConfig.button.lang; const color = this.buttonConfig.button.color; @@ -195,7 +195,7 @@ class ApplepayButton { try { 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 line should be reviewed, the widgetBuilder.paypal.Applepay().confirmOrder fails if we add it. //this.update_request_data_with_form(paymentDataRequest); } catch (error) { console.error(error); @@ -279,7 +279,7 @@ class ApplepayButton { return (applePayValidateMerchantEvent) => { this.log('onvalidatemerchant call'); - paypal.Applepay().validateMerchant({ + widgetBuilder.paypal.Applepay().validateMerchant({ validationUrl: applePayValidateMerchantEvent.validationURL }) .then(validateResult => { @@ -506,7 +506,7 @@ class ApplepayButton { this.log('onpaymentauthorized paypal order ID', id, event.payment.token, event.payment.billingContact); try { - const confirmOrderResponse = await paypal.Applepay().confirmOrder({ + const confirmOrderResponse = await widgetBuilder.paypal.Applepay().confirmOrder({ orderId: id, token: event.payment.token, billingContact: event.payment.billingContact, diff --git a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js index f08c2d6a1..3deb4a47e 100644 --- a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js @@ -65,6 +65,13 @@ class BaseHandler { ); } + errorHandler() { + return new ErrorHandler( + this.ppcpConfig.labels.error.generic, + document.querySelector('.woocommerce-notices-wrapper') + ); + } + } export default BaseHandler; diff --git a/modules/ppcp-applepay/resources/js/Context/ContextHandlerFactory.js b/modules/ppcp-applepay/resources/js/Context/ContextHandlerFactory.js index 04338354b..bea8e837b 100644 --- a/modules/ppcp-applepay/resources/js/Context/ContextHandlerFactory.js +++ b/modules/ppcp-applepay/resources/js/Context/ContextHandlerFactory.js @@ -4,6 +4,7 @@ import CheckoutHandler from "./CheckoutHandler"; import CartBlockHandler from "./CartBlockHandler"; import CheckoutBlockHandler from "./CheckoutBlockHandler"; import MiniCartHandler from "./MiniCartHandler"; +import PreviewHandler from "./PreviewHandler"; import PayNowHandler from "./PayNowHandler"; class ContextHandlerFactory { @@ -24,6 +25,8 @@ class ContextHandlerFactory { return new CartBlockHandler(buttonConfig, ppcpConfig); case 'checkout-block': return new CheckoutBlockHandler(buttonConfig, ppcpConfig); + case 'preview': + return new PreviewHandler(buttonConfig, ppcpConfig); } } } diff --git a/modules/ppcp-applepay/resources/js/Context/PreviewHandler.js b/modules/ppcp-applepay/resources/js/Context/PreviewHandler.js new file mode 100644 index 000000000..6e0da8852 --- /dev/null +++ b/modules/ppcp-applepay/resources/js/Context/PreviewHandler.js @@ -0,0 +1,37 @@ +import BaseHandler from "./BaseHandler"; + +class PreviewHandler extends BaseHandler { + + constructor(buttonConfig, ppcpConfig, externalHandler) { + super(buttonConfig, ppcpConfig, externalHandler); + } + + transactionInfo() { + // We need to return something as ApplePay button initialization expects valid data. + return { + countryCode: "US", + currencyCode: "USD", + totalPrice: "10.00", + totalPriceStatus: "FINAL" + } + } + + createOrder() { + throw new Error('Create order fail. This is just a preview.'); + } + + approveOrder(data, actions) { + throw new Error('Approve order fail. This is just a preview.'); + } + + actionHandler() { + throw new Error('Action handler fail. This is just a preview.'); + } + + errorHandler() { + throw new Error('Error handler fail. This is just a preview.'); + } + +} + +export default PreviewHandler; diff --git a/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js b/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js index 309694a77..c5d0549d2 100644 --- a/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js @@ -48,6 +48,14 @@ class SingleProductHandler extends BaseHandler { }); } + createOrder() { + return this.actionHandler().configuration().createOrder(null, null, { + 'updateCartOptions': { + 'keepShipping': true + } + }); + } + actionHandler() { return new SingleProductActionHandler( this.ppcpConfig, @@ -60,12 +68,9 @@ class SingleProductHandler extends BaseHandler { ); } - products() { return this.actionHandler().getProducts(); } - - } export default SingleProductHandler; diff --git a/modules/ppcp-applepay/resources/js/boot-admin.js b/modules/ppcp-applepay/resources/js/boot-admin.js new file mode 100644 index 000000000..30032f56b --- /dev/null +++ b/modules/ppcp-applepay/resources/js/boot-admin.js @@ -0,0 +1,148 @@ +import {loadCustomScript} from "@paypal/paypal-js"; +import ApplepayButton from "./ApplepayButton"; +import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder"; + +(function ({ + buttonConfig, + jQuery +}) { + + let applePayConfig; + let buttonQueue = []; + let activeButtons = {}; + let bootstrapped = false; + + // React to PayPal config changes. + jQuery(document).on('ppcp_paypal_render_preview', (ev, ppcpConfig) => { + if (bootstrapped) { + createButton(ppcpConfig); + } else { + buttonQueue.push({ + ppcpConfig: JSON.parse(JSON.stringify(ppcpConfig)) + }); + } + }); + + // React to ApplePay config changes. + jQuery([ + '#ppcp-applepay_button_enabled', + '#ppcp-applepay_button_type', + '#ppcp-applepay_button_color', + '#ppcp-applepay_button_language' + ].join(',')).on('change', () => { + for (const [selector, ppcpConfig] of Object.entries(activeButtons)) { + createButton(ppcpConfig); + } + }); + + // Maybe we can find a more elegant reload method when transitioning from styling modes. + jQuery([ + '#ppcp-smart_button_enable_styling_per_location' + ].join(',')).on('change', () => { + setTimeout(() => { + for (const [selector, ppcpConfig] of Object.entries(activeButtons)) { + createButton(ppcpConfig); + } + }, 100); + }); + + const applyConfigOptions = function (buttonConfig) { + buttonConfig.button = buttonConfig.button || {}; + buttonConfig.button.type = jQuery('#ppcp-applepay_button_type').val(); + buttonConfig.button.color = jQuery('#ppcp-applepay_button_color').val(); + buttonConfig.button.lang = jQuery('#ppcp-applepay_button_language').val(); + } + + const createButton = function (ppcpConfig) { + const selector = ppcpConfig.button.wrapper + 'ApplePay'; + + if (!jQuery('#ppcp-applepay_button_enabled').is(':checked')) { + jQuery(selector).remove(); + return; + } + + buttonConfig = JSON.parse(JSON.stringify(buttonConfig)); + buttonConfig.button.wrapper = selector.replace('#', ''); + applyConfigOptions(buttonConfig); + + const wrapperElement = ``; + + if (!jQuery(selector).length) { + jQuery(ppcpConfig.button.wrapper).after(wrapperElement); + } else { + jQuery(selector).replaceWith(wrapperElement); + } + + const button = new ApplepayButton( + 'preview', + null, + buttonConfig, + ppcpConfig, + ); + + button.init(applePayConfig); + + activeButtons[selector] = ppcpConfig; + } + + const bootstrap = async function () { + if (!widgetBuilder.paypal) { + return; + } + + applePayConfig = await widgetBuilder.paypal.Applepay().config(); + + // We need to set bootstrapped here otherwise applePayConfig may not be set. + bootstrapped = true; + + let options; + while (options = buttonQueue.pop()) { + createButton(options.ppcpConfig); + } + + if (!window.ApplePaySession) { + jQuery('body').addClass('ppcp-non-ios-device') + } + }; + + document.addEventListener( + 'DOMContentLoaded', + () => { + + if (typeof (buttonConfig) === 'undefined') { + console.error('PayPal button could not be configured.'); + return; + } + + let paypalLoaded = false; + let applePayLoaded = false; + + const tryToBoot = () => { + if (!bootstrapped && paypalLoaded && applePayLoaded) { + bootstrap(); + } + } + + // Load ApplePay SDK + loadCustomScript({ url: buttonConfig.sdk_url }).then(() => { + applePayLoaded = true; + tryToBoot(); + }); + + // Wait for PayPal to be loaded externally + if (typeof widgetBuilder.paypal !== 'undefined') { + paypalLoaded = true; + tryToBoot(); + } + + jQuery(document).on('ppcp-paypal-loaded', () => { + paypalLoaded = true; + tryToBoot(); + }); + }, + ); + +})({ + buttonConfig: window.wc_ppcp_applepay_admin, + jQuery: window.jQuery +}); diff --git a/modules/ppcp-applepay/services.php b/modules/ppcp-applepay/services.php index f345409e6..fce1eddda 100644 --- a/modules/ppcp-applepay/services.php +++ b/modules/ppcp-applepay/services.php @@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\Applepay\Assets\ApplePayButton; use WooCommerce\PayPalCommerce\Applepay\Assets\AppleProductStatus; use WooCommerce\PayPalCommerce\Applepay\Assets\DataToAppleButtonScripts; use WooCommerce\PayPalCommerce\Applepay\Assets\BlocksPaymentMethod; +use WooCommerce\PayPalCommerce\Applepay\Assets\PropertiesDictionary; use WooCommerce\PayPalCommerce\Applepay\Helper\ApmApplies; use WooCommerce\PayPalCommerce\Applepay\Helper\AvailabilityNotice; use WooCommerce\PayPalCommerce\Onboarding\Environment; @@ -56,11 +57,16 @@ return array( $container->get( 'wcgateway.is-ppcp-settings-page' ), $container->get( 'applepay.available' ) || ( ! $container->get( 'applepay.is_referral' ) ), $container->get( 'applepay.server_supported' ), - $settings->has( 'applepay_validated' ) ? $settings->get( 'applepay_validated' ) === true : false, + $container->get( 'applepay.is_validated' ), $container->get( 'applepay.button' ) ); }, + 'applepay.is_validated' => static function ( ContainerInterface $container ): bool { + $settings = $container->get( 'wcgateway.settings' ); + return $settings->has( 'applepay_validated' ) ? $settings->get( 'applepay_validated' ) === true : false; + }, + 'applepay.apple-product-status' => static function( ContainerInterface $container ): AppleProductStatus { return new AppleProductStatus( $container->get( 'wcgateway.settings' ), @@ -83,6 +89,18 @@ return array( 'applepay.server_supported' => static function ( ContainerInterface $container ): bool { return ! empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off'; }, + 'applepay.is_browser_supported' => static function ( ContainerInterface $container ): bool { + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $user_agent = wp_unslash( $_SERVER['HTTP_USER_AGENT'] ?? '' ); + if ( $user_agent ) { + foreach ( PropertiesDictionary::ALLOWED_USER_AGENTS as $allowed_agent ) { + if ( strpos( $user_agent, $allowed_agent ) !== false ) { + return true; + } + } + } + return false; + }, 'applepay.url' => static function ( ContainerInterface $container ): string { $path = realpath( __FILE__ ); if ( false === $path ) { diff --git a/modules/ppcp-applepay/src/ApplepayModule.php b/modules/ppcp-applepay/src/ApplepayModule.php index d29bcc240..a02e26884 100644 --- a/modules/ppcp-applepay/src/ApplepayModule.php +++ b/modules/ppcp-applepay/src/ApplepayModule.php @@ -90,6 +90,8 @@ class ApplepayModule implements ModuleInterface { $module->render_buttons( $c, $apple_payment_method ); $apple_payment_method->bootstrap_ajax_request(); } + + $module->load_admin_assets( $c, $apple_payment_method ); }, 1 ); @@ -182,6 +184,43 @@ class ApplepayModule implements ModuleInterface { ); } + /** + * Registers and enqueues the assets. + * + * @param ContainerInterface $c The container. + * @param ApplePayButton $button The button. + * @return void + */ + public function load_admin_assets( ContainerInterface $c, ApplePayButton $button ): void { + // Enqueue backend scripts. + add_action( + 'admin_enqueue_scripts', + static function () use ( $c, $button ) { + if ( ! is_admin() ) { + return; + } + + /** + * Should add this to the ButtonInterface. + * + * @psalm-suppress UndefinedInterfaceMethod + */ + $button->enqueue_admin(); + } + ); + + // Adds ApplePay component to the backend button preview settings. + add_action( + 'woocommerce_paypal_payments_admin_gateway_settings', + function( array $settings ) use ( $c ): array { + if ( is_array( $settings['components'] ) ) { + $settings['components'][] = 'applepay'; + } + return $settings; + } + ); + } + /** * Renders the Apple Pay buttons in the enabled places. * diff --git a/modules/ppcp-applepay/src/Assets/ApplePayButton.php b/modules/ppcp-applepay/src/Assets/ApplePayButton.php index fe7313a85..da909f677 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayButton.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayButton.php @@ -942,7 +942,7 @@ class ApplePayButton implements ButtonInterface { } if ( $button_enabled_cart ) { $default_hook_name = 'woocommerce_paypal_payments_cart_button_render'; - $render_placeholder = apply_filters( 'woocommerce_paypal_payments_googlepay_cart_button_render_hook', $default_hook_name ); + $render_placeholder = apply_filters( 'woocommerce_paypal_payments_applepay_cart_button_render_hook', $default_hook_name ); $render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hook_name; add_action( $render_placeholder, @@ -954,7 +954,7 @@ class ApplePayButton implements ButtonInterface { if ( $button_enabled_checkout ) { $default_hook_name = 'woocommerce_paypal_payments_checkout_button_render'; - $render_placeholder = apply_filters( 'woocommerce_paypal_payments_googlepay_checkout_button_render_hook', $default_hook_name ); + $render_placeholder = apply_filters( 'woocommerce_paypal_payments_applepay_checkout_button_render_hook', $default_hook_name ); $render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hook_name; add_action( $render_placeholder, @@ -966,7 +966,7 @@ class ApplePayButton implements ButtonInterface { } if ( $button_enabled_payorder ) { $default_hook_name = 'woocommerce_paypal_payments_payorder_button_render'; - $render_placeholder = apply_filters( 'woocommerce_paypal_payments_googlepay_payorder_button_render_hook', $default_hook_name ); + $render_placeholder = apply_filters( 'woocommerce_paypal_payments_applepay_payorder_button_render_hook', $default_hook_name ); $render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hook_name; add_action( $render_placeholder, @@ -979,7 +979,7 @@ class ApplePayButton implements ButtonInterface { if ( $button_enabled_minicart ) { $default_hook_name = 'woocommerce_paypal_payments_minicart_button_render'; - $render_placeholder = apply_filters( 'woocommerce_paypal_payments_googlepay_minicart_button_render_hook', $default_hook_name ); + $render_placeholder = apply_filters( 'woocommerce_paypal_payments_applepay_minicart_button_render_hook', $default_hook_name ); $render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hook_name; add_action( $render_placeholder, @@ -1059,6 +1059,34 @@ class ApplePayButton implements ButtonInterface { wp_enqueue_style( 'wc-ppcp-applepay' ); } + /** + * Enqueues scripts/styles for admin. + */ + public function enqueue_admin(): void { + wp_register_style( + 'wc-ppcp-applepay-admin', + untrailingslashit( $this->module_url ) . '/assets/css/styles.css', + array(), + $this->version + ); + wp_enqueue_style( 'wc-ppcp-applepay-admin' ); + + wp_register_script( + 'wc-ppcp-applepay-admin', + untrailingslashit( $this->module_url ) . '/assets/js/boot-admin.js', + array(), + $this->version, + true + ); + wp_enqueue_script( 'wc-ppcp-applepay-admin' ); + + wp_localize_script( + 'wc-ppcp-applepay-admin', + 'wc_ppcp_applepay_admin', + $this->script_data_for_admin() + ); + } + /** * Returns the script data. * @@ -1068,6 +1096,15 @@ class ApplePayButton implements ButtonInterface { return $this->script_data->apple_pay_script_data(); } + /** + * Returns the admin script data. + * + * @return array + */ + public function script_data_for_admin(): array { + return $this->script_data->apple_pay_script_data_for_admin(); + } + /** * Returns true if the module is enabled. * diff --git a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php index 882776cf1..afe0126ef 100644 --- a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php +++ b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php @@ -69,6 +69,26 @@ class DataToAppleButtonScripts { ); } + + /** + * Returns the appropriate admin data to send to ApplePay script + * + * @return array + * @throws NotFoundException When the setting is not found. + */ + public function apple_pay_script_data_for_admin(): array { + $base_location = wc_get_base_location(); + $shop_country_code = $base_location['country']; + $currency_code = get_woocommerce_currency(); + $total_label = get_bloginfo( 'name' ); + + return $this->data_for_admin_page( + $shop_country_code, + $currency_code, + $total_label + ); + } + /** * Check if the product needs shipping * @@ -199,4 +219,47 @@ class DataToAppleButtonScripts { 'nonce' => wp_create_nonce( 'woocommerce-process_checkout' ), ); } + + /** + * Prepares the data for the cart page. + * Consider refactoring this method along with data_for_cart_page() and data_for_product_page() methods. + * + * @param string $shop_country_code The shop country code. + * @param string $currency_code The currency code. + * @param string $total_label The label for the total amount. + * + * @return array + */ + protected function data_for_admin_page( + $shop_country_code, + $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 ); + + return array( + 'sdk_url' => $this->sdk_url, + 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false, + 'button' => array( + 'wrapper' => 'applepay-container', + 'mini_cart_wrapper' => 'applepay-container-minicart', + 'type' => $type, + 'color' => $color, + 'lang' => $lang, + ), + 'product' => array( + 'needShipping' => false, + 'subtotal' => 0, + ), + 'shop' => array( + 'countryCode' => $shop_country_code, + 'currencyCode' => $currency_code, + 'totalLabel' => $total_label, + ), + '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 25227e11b..d1c388ffd 100644 --- a/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php +++ b/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php @@ -13,6 +13,7 @@ namespace WooCommerce\PayPalCommerce\Applepay\Assets; * Class PropertiesDictionary */ class PropertiesDictionary { + public const ALLOWED_USER_AGENTS = array( 'Safari', 'Macintosh', 'iPhone', 'iPad', 'iPod' ); public const BILLING_CONTACT_INVALID = 'billing Contact Invalid'; diff --git a/modules/ppcp-applepay/webpack.config.js b/modules/ppcp-applepay/webpack.config.js index 63834c8a6..94e7bfd8b 100644 --- a/modules/ppcp-applepay/webpack.config.js +++ b/modules/ppcp-applepay/webpack.config.js @@ -11,6 +11,7 @@ module.exports = { entry: { 'boot': path.resolve('./resources/js/boot.js'), 'boot-block': path.resolve('./resources/js/boot-block.js'), + 'boot-admin': path.resolve('./resources/js/boot-admin.js'), "styles": path.resolve('./resources/css/styles.scss') }, output: { diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js index 42f746b5f..9590e9a57 100644 --- a/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js +++ b/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js @@ -143,7 +143,7 @@ class SingleProductActionHandler { { this.cartHelper = null; - return (data, actions) => { + return (data, actions, options = {}) => { this.errorHandler.clear(); const onResolve = (purchase_units) => { @@ -178,7 +178,7 @@ class SingleProductActionHandler { }); }; - return this.updateCart.update(onResolve, this.getProducts()); + return this.updateCart.update(onResolve, this.getProducts(), options.updateCartOptions || {}); }; } diff --git a/modules/ppcp-button/resources/js/modules/Helper/UpdateCart.js b/modules/ppcp-button/resources/js/modules/Helper/UpdateCart.js index 4c5b08355..f79f8613b 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/UpdateCart.js +++ b/modules/ppcp-button/resources/js/modules/Helper/UpdateCart.js @@ -11,9 +11,10 @@ class UpdateCart { * * @param onResolve * @param {Product[]} products + * @param {Object} options * @returns {Promise