From 118e49cfdef021935c70a0f3ea347300887b7bd8 Mon Sep 17 00:00:00 2001
From: Pedro Silva
Date: Fri, 3 Nov 2023 10:30:31 +0000
Subject: [PATCH] Add ApplePay preview in admin settings.
---
.../ppcp-applepay/resources/css/styles.scss | 10 +-
.../resources/js/ApplepayButton.js | 16 +-
.../js/Context/ContextHandlerFactory.js | 3 +
.../resources/js/Context/PreviewHandler.js | 37 +++++
.../ppcp-applepay/resources/js/boot-admin.js | 144 ++++++++++++++++++
modules/ppcp-applepay/src/ApplepayModule.php | 39 +++++
.../src/Assets/ApplePayButton.php | 45 +++++-
.../src/Assets/DataToAppleButtonScripts.php | 63 ++++++++
modules/ppcp-applepay/webpack.config.js | 1 +
.../resources/js/Context/PreviewHandler.js | 4 +-
10 files changed, 343 insertions(+), 19 deletions(-)
create mode 100644 modules/ppcp-applepay/resources/js/Context/PreviewHandler.js
create mode 100644 modules/ppcp-applepay/resources/js/boot-admin.js
diff --git a/modules/ppcp-applepay/resources/css/styles.scss b/modules/ppcp-applepay/resources/css/styles.scss
index d3f834119..0abd19451 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;
}
diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js
index 19c03c5d2..6cab7d5ca 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);
@@ -274,7 +274,7 @@ class ApplepayButton {
return (applePayValidateMerchantEvent) => {
this.log('onvalidatemerchant call');
- paypal.Applepay().validateMerchant({
+ widgetBuilder.paypal.Applepay().validateMerchant({
validationUrl: applePayValidateMerchantEvent.validationURL
})
.then(validateResult => {
@@ -501,7 +501,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/ContextHandlerFactory.js b/modules/ppcp-applepay/resources/js/Context/ContextHandlerFactory.js
index 72ee4f5fa..d94833daa 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";
class ContextHandlerFactory {
@@ -22,6 +23,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/boot-admin.js b/modules/ppcp-applepay/resources/js/boot-admin.js
new file mode 100644
index 000000000..c2fa7796a
--- /dev/null
+++ b/modules/ppcp-applepay/resources/js/boot-admin.js
@@ -0,0 +1,144 @@
+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);
+ }
+ };
+
+ 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/src/ApplepayModule.php b/modules/ppcp-applepay/src/ApplepayModule.php
index b81329811..1abbf339b 100644
--- a/modules/ppcp-applepay/src/ApplepayModule.php
+++ b/modules/ppcp-applepay/src/ApplepayModule.php
@@ -89,6 +89,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 );
}
);
@@ -180,6 +182,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..3f1063684 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?x=' . time(),
+ 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 0b15fbe78..a530ba95f 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
*
@@ -197,4 +217,47 @@ class DataToAppleButtonScripts {
'ajax_url' => admin_url( 'admin-ajax.php' ),
);
}
+
+ /**
+ * 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/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-googlepay/resources/js/Context/PreviewHandler.js b/modules/ppcp-googlepay/resources/js/Context/PreviewHandler.js
index a637f078d..f4eeee486 100644
--- a/modules/ppcp-googlepay/resources/js/Context/PreviewHandler.js
+++ b/modules/ppcp-googlepay/resources/js/Context/PreviewHandler.js
@@ -1,6 +1,6 @@
import BaseHandler from "./BaseHandler";
-class CartHandler extends BaseHandler {
+class PreviewHandler extends BaseHandler {
constructor(buttonConfig, ppcpConfig, externalHandler) {
super(buttonConfig, ppcpConfig, externalHandler);
@@ -28,4 +28,4 @@ class CartHandler extends BaseHandler {
}
-export default CartHandler;
+export default PreviewHandler;