Add ApplePay preview in admin settings.

This commit is contained in:
Pedro Silva 2023-11-03 10:30:31 +00:00
parent eb956d9db6
commit 118e49cfde
No known key found for this signature in database
GPG key ID: E2EE20C0669D24B3
10 changed files with 343 additions and 19 deletions

View file

@ -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;
}

View file

@ -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,

View file

@ -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);
}
}
}

View file

@ -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;

View file

@ -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 = `<div id="${selector.replace('#', '')}" class="ppcp-button-applepay"></div>`;
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
});

View file

@ -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.
*

View file

@ -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.
*

View file

@ -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' ),
);
}
}

View file

@ -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: {

View file

@ -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;