mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-01 07:02:48 +08:00
🔀 Merge branch 'PCP-3644’
# Conflicts: # modules/ppcp-applepay/resources/js/ApplepayButton.js
This commit is contained in:
commit
73cc6b177e
16 changed files with 910 additions and 720 deletions
|
@ -57,7 +57,3 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ppc-button-ppcp-applepay {
|
||||
display: none;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,54 +1,90 @@
|
|||
import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher';
|
||||
import ApplePayButton from './ApplepayButton';
|
||||
import ContextHandlerFactory from './Context/ContextHandlerFactory';
|
||||
|
||||
class ApplePayManager {
|
||||
#namespace = '';
|
||||
#buttonConfig = null;
|
||||
#ppcpConfig = null;
|
||||
#applePayConfig = null;
|
||||
#contextHandler = null;
|
||||
#transactionInfo = null;
|
||||
#buttons = [];
|
||||
|
||||
constructor( namespace, buttonConfig, ppcpConfig ) {
|
||||
this.namespace = namespace;
|
||||
this.buttonConfig = buttonConfig;
|
||||
this.ppcpConfig = ppcpConfig;
|
||||
this.ApplePayConfig = null;
|
||||
this.buttons = [];
|
||||
this.#namespace = namespace;
|
||||
this.#buttonConfig = buttonConfig;
|
||||
this.#ppcpConfig = ppcpConfig;
|
||||
|
||||
buttonModuleWatcher.watchContextBootstrap( ( bootstrap ) => {
|
||||
const button = new ApplePayButton(
|
||||
bootstrap.context,
|
||||
bootstrap.handler,
|
||||
buttonConfig,
|
||||
ppcpConfig
|
||||
);
|
||||
|
||||
this.buttons.push( button );
|
||||
|
||||
if ( this.ApplePayConfig ) {
|
||||
button.init( this.ApplePayConfig );
|
||||
}
|
||||
} );
|
||||
this.onContextBootstrap = this.onContextBootstrap.bind( this );
|
||||
buttonModuleWatcher.watchContextBootstrap( this.onContextBootstrap );
|
||||
}
|
||||
|
||||
init() {
|
||||
( async () => {
|
||||
await this.config();
|
||||
for ( const button of this.buttons ) {
|
||||
button.init( this.ApplePayConfig );
|
||||
}
|
||||
} )();
|
||||
async onContextBootstrap( bootstrap ) {
|
||||
this.#contextHandler = ContextHandlerFactory.create(
|
||||
bootstrap.context,
|
||||
this.#buttonConfig,
|
||||
this.#ppcpConfig,
|
||||
bootstrap.handler
|
||||
);
|
||||
|
||||
const button = ApplePayButton.createButton(
|
||||
bootstrap.context,
|
||||
bootstrap.handler,
|
||||
this.#buttonConfig,
|
||||
this.#ppcpConfig,
|
||||
this.#contextHandler
|
||||
);
|
||||
|
||||
this.#buttons.push( button );
|
||||
|
||||
// Ensure ApplePayConfig is loaded before proceeding.
|
||||
await this.init();
|
||||
|
||||
button.configure( this.#applePayConfig, this.#transactionInfo );
|
||||
button.init();
|
||||
}
|
||||
|
||||
reinit() {
|
||||
for ( const button of this.buttons ) {
|
||||
button.reinit();
|
||||
async init() {
|
||||
try {
|
||||
if ( ! this.#applePayConfig ) {
|
||||
this.#applePayConfig = await window[ this.#namespace ]
|
||||
.Applepay()
|
||||
.config();
|
||||
|
||||
if ( ! this.#applePayConfig ) {
|
||||
console.error( 'No ApplePayConfig received during init' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! this.#transactionInfo ) {
|
||||
this.#transactionInfo = await this.fetchTransactionInfo();
|
||||
|
||||
if ( ! this.#applePayConfig ) {
|
||||
console.error( 'No transactionInfo found during init' );
|
||||
}
|
||||
}
|
||||
} catch ( error ) {
|
||||
console.error( 'Error during initialization:', error );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Apple Pay configuration of the PayPal merchant.
|
||||
*/
|
||||
async config() {
|
||||
this.ApplePayConfig = await window[ this.namespace ]
|
||||
.Applepay()
|
||||
.config();
|
||||
async fetchTransactionInfo() {
|
||||
try {
|
||||
if ( ! this.#contextHandler ) {
|
||||
throw new Error( 'ContextHandler is not initialized' );
|
||||
}
|
||||
return await this.#contextHandler.transactionInfo();
|
||||
} catch ( error ) {
|
||||
console.error( 'Error fetching transaction info:', error );
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return this.ApplePayConfig;
|
||||
reinit() {
|
||||
for ( const button of this.#buttons ) {
|
||||
button.reinit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,37 +1,15 @@
|
|||
import ApplePayButton from './ApplepayButton';
|
||||
import ApplepayButton from './Block/components/ApplePayButton';
|
||||
|
||||
class ApplePayManagerBlockEditor {
|
||||
constructor( namespace, buttonConfig, ppcpConfig ) {
|
||||
this.namespace = namespace;
|
||||
this.buttonConfig = buttonConfig;
|
||||
this.ppcpConfig = ppcpConfig;
|
||||
|
||||
/*
|
||||
* On the front-end, the init method is called when a new button context was detected
|
||||
* via `buttonModuleWatcher`. In the block editor, we do not need to wait for the
|
||||
* context, but can initialize the button in the next event loop.
|
||||
*/
|
||||
setTimeout( () => this.init() );
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
this.applePayConfig = await window[ this.namespace ]
|
||||
.Applepay()
|
||||
.config();
|
||||
|
||||
const button = new ApplePayButton(
|
||||
this.ppcpConfig.context,
|
||||
null,
|
||||
this.buttonConfig,
|
||||
this.ppcpConfig
|
||||
);
|
||||
|
||||
button.init( this.applePayConfig );
|
||||
} catch ( error ) {
|
||||
console.error( 'Failed to initialize Apple Pay:', error );
|
||||
}
|
||||
}
|
||||
}
|
||||
const ApplePayManagerBlockEditor = ( {
|
||||
namespace,
|
||||
buttonConfig,
|
||||
ppcpConfig,
|
||||
} ) => (
|
||||
<ApplepayButton
|
||||
namespace={ namespace }
|
||||
buttonConfig={ buttonConfig }
|
||||
ppcpConfig={ ppcpConfig }
|
||||
/>
|
||||
);
|
||||
|
||||
export default ApplePayManagerBlockEditor;
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import { useState, useEffect } from '@wordpress/element';
|
||||
import useApiToGenerateButton from '../hooks/useApiToGenerateButton';
|
||||
import usePayPalScript from '../hooks/usePayPalScript';
|
||||
import useApplepayScript from '../hooks/useApplepayScript';
|
||||
import useApplepayConfig from '../hooks/useApplepayConfig';
|
||||
|
||||
const ApplepayButton = ( { namespace, buttonConfig, ppcpConfig } ) => {
|
||||
const [ buttonHtml, setButtonHtml ] = useState( '' );
|
||||
const [ buttonElement, setButtonElement ] = useState( null );
|
||||
const [ componentFrame, setComponentFrame ] = useState( null );
|
||||
const isPayPalLoaded = usePayPalScript( namespace, ppcpConfig );
|
||||
|
||||
const isApplepayLoaded = useApplepayScript(
|
||||
componentFrame,
|
||||
buttonConfig,
|
||||
isPayPalLoaded
|
||||
);
|
||||
|
||||
const applepayConfig = useApplepayConfig( namespace, isApplepayLoaded );
|
||||
|
||||
useEffect( () => {
|
||||
if ( ! buttonElement ) {
|
||||
return;
|
||||
}
|
||||
|
||||
setComponentFrame( buttonElement.ownerDocument );
|
||||
}, [ buttonElement ] );
|
||||
|
||||
const applepayButton = useApiToGenerateButton(
|
||||
componentFrame,
|
||||
namespace,
|
||||
buttonConfig,
|
||||
ppcpConfig,
|
||||
applepayConfig
|
||||
);
|
||||
|
||||
useEffect( () => {
|
||||
if ( applepayButton ) {
|
||||
setButtonHtml( applepayButton.outerHTML );
|
||||
}
|
||||
}, [ applepayButton ] );
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ setButtonElement }
|
||||
dangerouslySetInnerHTML={ { __html: buttonHtml } }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApplepayButton;
|
|
@ -0,0 +1,37 @@
|
|||
import { useEffect, useState } from '@wordpress/element';
|
||||
import useButtonStyles from './useButtonStyles';
|
||||
|
||||
const useApiToGenerateButton = (
|
||||
componentDocument,
|
||||
namespace,
|
||||
buttonConfig,
|
||||
ppcpConfig,
|
||||
applepayConfig
|
||||
) => {
|
||||
const [ applepayButton, setApplepayButton ] = useState( null );
|
||||
const buttonStyles = useButtonStyles( buttonConfig, ppcpConfig );
|
||||
|
||||
useEffect( () => {
|
||||
if ( ! buttonConfig || ! applepayConfig ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const button = document.createElement( 'apple-pay-button' );
|
||||
button.setAttribute(
|
||||
'buttonstyle',
|
||||
buttonConfig.buttonColor || 'black'
|
||||
);
|
||||
button.setAttribute( 'type', buttonConfig.buttonType || 'pay' );
|
||||
button.setAttribute( 'locale', buttonConfig.buttonLocale || 'en' );
|
||||
|
||||
setApplepayButton( button );
|
||||
|
||||
return () => {
|
||||
setApplepayButton( null );
|
||||
};
|
||||
}, [ namespace, buttonConfig, ppcpConfig, applepayConfig, buttonStyles ] );
|
||||
|
||||
return applepayButton;
|
||||
};
|
||||
|
||||
export default useApiToGenerateButton;
|
|
@ -0,0 +1,26 @@
|
|||
import { useState, useEffect } from '@wordpress/element';
|
||||
|
||||
const useApplepayConfig = ( namespace, isApplepayLoaded ) => {
|
||||
const [ applePayConfig, setApplePayConfig ] = useState( null );
|
||||
|
||||
useEffect( () => {
|
||||
const fetchConfig = async () => {
|
||||
if ( ! isApplepayLoaded ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const config = await window[ namespace ].Applepay().config();
|
||||
setApplePayConfig( config );
|
||||
} catch ( error ) {
|
||||
console.error( 'Failed to fetch Apple Pay config:', error );
|
||||
}
|
||||
};
|
||||
|
||||
fetchConfig();
|
||||
}, [ namespace, isApplepayLoaded ] );
|
||||
|
||||
return applePayConfig;
|
||||
};
|
||||
|
||||
export default useApplepayConfig;
|
|
@ -0,0 +1,65 @@
|
|||
import { useState, useEffect } from '@wordpress/element';
|
||||
import { loadCustomScript } from '@paypal/paypal-js';
|
||||
|
||||
const useApplepayScript = (
|
||||
componentDocument,
|
||||
buttonConfig,
|
||||
isPayPalLoaded
|
||||
) => {
|
||||
const [ isApplepayLoaded, setIsApplepayLoaded ] = useState( false );
|
||||
|
||||
useEffect( () => {
|
||||
if ( ! componentDocument ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const injectScriptToFrame = ( scriptSrc ) => {
|
||||
if ( document === componentDocument ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const script = document.querySelector(
|
||||
`script[src^="${ scriptSrc }"]`
|
||||
);
|
||||
|
||||
if ( script ) {
|
||||
const newScript = componentDocument.createElement( 'script' );
|
||||
newScript.src = script.src;
|
||||
newScript.async = script.async;
|
||||
newScript.type = script.type;
|
||||
|
||||
componentDocument.head.appendChild( newScript );
|
||||
} else {
|
||||
console.error( 'Script not found in the document:', scriptSrc );
|
||||
}
|
||||
};
|
||||
|
||||
const loadApplepayScript = async () => {
|
||||
if ( ! isPayPalLoaded ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! buttonConfig || ! buttonConfig.sdk_url ) {
|
||||
console.error( 'Invalid buttonConfig or missing sdk_url' );
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await loadCustomScript( { url: buttonConfig.sdk_url } ).then(
|
||||
() => {
|
||||
injectScriptToFrame( buttonConfig.sdk_url );
|
||||
}
|
||||
);
|
||||
setIsApplepayLoaded( true );
|
||||
} catch ( error ) {
|
||||
console.error( 'Failed to load Applepay script:', error );
|
||||
}
|
||||
};
|
||||
|
||||
loadApplepayScript();
|
||||
}, [ componentDocument, buttonConfig, isPayPalLoaded ] );
|
||||
|
||||
return isApplepayLoaded;
|
||||
};
|
||||
|
||||
export default useApplepayScript;
|
|
@ -0,0 +1,19 @@
|
|||
import { useMemo } from '@wordpress/element';
|
||||
import { combineStyles } from '../../../../../ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers';
|
||||
|
||||
const useButtonStyles = ( buttonConfig, ppcpConfig ) => {
|
||||
return useMemo( () => {
|
||||
const styles = combineStyles(
|
||||
ppcpConfig?.button || {},
|
||||
buttonConfig?.button || {}
|
||||
);
|
||||
|
||||
if ( styles.MiniCart && styles.MiniCart.type === 'buy' ) {
|
||||
styles.MiniCart.type = 'pay';
|
||||
}
|
||||
|
||||
return styles;
|
||||
}, [ buttonConfig, ppcpConfig ] );
|
||||
};
|
||||
|
||||
export default useButtonStyles;
|
|
@ -0,0 +1,25 @@
|
|||
import { useState, useEffect } from '@wordpress/element';
|
||||
import { loadPayPalScript } from '../../../../../ppcp-button/resources/js/modules/Helper/PayPalScriptLoading';
|
||||
|
||||
const usePayPalScript = ( namespace, ppcpConfig ) => {
|
||||
const [ isPayPalLoaded, setIsPayPalLoaded ] = useState( false );
|
||||
|
||||
ppcpConfig.url_params.components += ',applepay';
|
||||
|
||||
useEffect( () => {
|
||||
const loadScript = async () => {
|
||||
try {
|
||||
await loadPayPalScript( namespace, ppcpConfig );
|
||||
setIsPayPalLoaded( true );
|
||||
} catch ( error ) {
|
||||
console.error( `Error loading PayPal script: ${ error }` );
|
||||
}
|
||||
};
|
||||
|
||||
loadScript();
|
||||
}, [ namespace, ppcpConfig ] );
|
||||
|
||||
return isPayPalLoaded;
|
||||
};
|
||||
|
||||
export default usePayPalScript;
|
|
@ -5,6 +5,11 @@ import PreviewButton from '../../../../ppcp-button/resources/js/modules/Preview/
|
|||
* A single Apple Pay preview button instance.
|
||||
*/
|
||||
export default class ApplePayPreviewButton extends PreviewButton {
|
||||
/**
|
||||
* @type {?PaymentButton}
|
||||
*/
|
||||
#button = null;
|
||||
|
||||
constructor( args ) {
|
||||
super( args );
|
||||
|
||||
|
@ -19,14 +24,18 @@ export default class ApplePayPreviewButton extends PreviewButton {
|
|||
}
|
||||
|
||||
createButton( buttonConfig ) {
|
||||
const button = new ApplepayButton(
|
||||
'preview',
|
||||
null,
|
||||
buttonConfig,
|
||||
this.ppcpConfig
|
||||
);
|
||||
if ( ! this.#button ) {
|
||||
this.#button = new ApplepayButton(
|
||||
'preview',
|
||||
null,
|
||||
buttonConfig,
|
||||
this.ppcpConfig
|
||||
);
|
||||
}
|
||||
|
||||
button.init( this.apiConfig );
|
||||
this.#button.configure( this.apiConfig, null );
|
||||
this.#button.applyButtonStyles( buttonConfig, this.ppcpConfig );
|
||||
this.#button.reinit();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { useEffect, useState } from '@wordpress/element';
|
||||
import { useEffect, useRef, useState } from '@wordpress/element';
|
||||
import { registerExpressPaymentMethod } from '@woocommerce/blocks-registry';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { loadPayPalScript } from '../../../ppcp-button/resources/js/modules/Helper/PayPalScriptLoading';
|
||||
import { cartHasSubscriptionProducts } from '../../../ppcp-blocks/resources/js/Helper/Subscription';
|
||||
import { loadCustomScript } from '@paypal/paypal-js';
|
||||
|
@ -18,20 +19,16 @@ if ( typeof window.PayPalCommerceGateway === 'undefined' ) {
|
|||
window.PayPalCommerceGateway = ppcpConfig;
|
||||
}
|
||||
|
||||
const ApplePayComponent = ( props ) => {
|
||||
const [ bootstrapped, setBootstrapped ] = useState( false );
|
||||
const ApplePayComponent = ( { isEditing } ) => {
|
||||
const [ paypalLoaded, setPaypalLoaded ] = useState( false );
|
||||
const [ applePayLoaded, setApplePayLoaded ] = useState( false );
|
||||
|
||||
const bootstrap = function () {
|
||||
const ManagerClass = props.isEditing
|
||||
? ApplePayManagerBlockEditor
|
||||
: ApplePayManager;
|
||||
const manager = new ManagerClass( namespace, buttonConfig, ppcpConfig );
|
||||
manager.init();
|
||||
};
|
||||
const wrapperRef = useRef( null );
|
||||
|
||||
useEffect( () => {
|
||||
if ( isEditing ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load ApplePay SDK
|
||||
loadCustomScript( { url: buttonConfig.sdk_url } ).then( () => {
|
||||
setApplePayLoaded( true );
|
||||
|
@ -47,17 +44,35 @@ const ApplePayComponent = ( props ) => {
|
|||
.catch( ( error ) => {
|
||||
console.error( 'Failed to load PayPal script: ', error );
|
||||
} );
|
||||
}, [] );
|
||||
}, [ isEditing ] );
|
||||
|
||||
useEffect( () => {
|
||||
if ( ! bootstrapped && paypalLoaded && applePayLoaded ) {
|
||||
setBootstrapped( true );
|
||||
bootstrap();
|
||||
if ( isEditing || ! paypalLoaded || ! applePayLoaded ) {
|
||||
return;
|
||||
}
|
||||
}, [ paypalLoaded, applePayLoaded ] );
|
||||
|
||||
const ManagerClass = isEditing
|
||||
? ApplePayManagerBlockEditor
|
||||
: ApplePayManager;
|
||||
|
||||
buttonConfig.reactWrapper = wrapperRef.current;
|
||||
|
||||
new ManagerClass( namespace, buttonConfig, ppcpConfig );
|
||||
}, [ paypalLoaded, applePayLoaded, isEditing ] );
|
||||
|
||||
if ( isEditing ) {
|
||||
return (
|
||||
<ApplePayManagerBlockEditor
|
||||
namespace={ namespace }
|
||||
buttonConfig={ buttonConfig }
|
||||
ppcpConfig={ ppcpConfig }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ wrapperRef }
|
||||
id={ buttonConfig.button.wrapper.replace( '#', '' ) }
|
||||
className="ppcp-button-apm ppcp-button-applepay"
|
||||
></div>
|
||||
|
@ -75,6 +90,11 @@ if (
|
|||
|
||||
registerExpressPaymentMethod( {
|
||||
name: buttonData.id,
|
||||
title: `PayPal - ${ buttonData.title }`,
|
||||
description: __(
|
||||
'Eligible users will see the PayPal button.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
label: <div dangerouslySetInnerHTML={ { __html: buttonData.title } } />,
|
||||
content: <ApplePayComponent isEditing={ false } />,
|
||||
edit: <ApplePayComponent isEditing={ true } />,
|
||||
|
|
|
@ -3,33 +3,49 @@ import { loadPayPalScript } from '../../../ppcp-button/resources/js/modules/Help
|
|||
import ApplePayManager from './ApplepayManager';
|
||||
import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper';
|
||||
|
||||
( function ( { buttonConfig, ppcpConfig, jQuery } ) {
|
||||
( function ( { buttonConfig, ppcpConfig } ) {
|
||||
const namespace = 'ppcpPaypalApplepay';
|
||||
let manager;
|
||||
|
||||
const bootstrap = function () {
|
||||
manager = new ApplePayManager( namespace, buttonConfig, ppcpConfig );
|
||||
manager.init();
|
||||
};
|
||||
|
||||
setupButtonEvents( function () {
|
||||
if ( manager ) {
|
||||
manager.reinit();
|
||||
}
|
||||
} );
|
||||
|
||||
document.addEventListener( 'DOMContentLoaded', () => {
|
||||
if (
|
||||
typeof buttonConfig === 'undefined' ||
|
||||
typeof ppcpConfig === 'undefined'
|
||||
) {
|
||||
function bootstrapPayButton() {
|
||||
if ( ! buttonConfig || ! ppcpConfig ) {
|
||||
return;
|
||||
}
|
||||
const isMiniCart = ppcpConfig.mini_cart_buttons_enabled;
|
||||
const isButton = jQuery( '#' + buttonConfig.button.wrapper ).length > 0;
|
||||
|
||||
const manager = new ApplePayManager(
|
||||
namespace,
|
||||
buttonConfig,
|
||||
ppcpConfig
|
||||
);
|
||||
|
||||
setupButtonEvents( function () {
|
||||
manager.reinit();
|
||||
} );
|
||||
}
|
||||
|
||||
function bootstrap() {
|
||||
bootstrapPayButton();
|
||||
// Other Apple Pay bootstrapping could happen here.
|
||||
}
|
||||
|
||||
document.addEventListener( 'DOMContentLoaded', () => {
|
||||
if ( ! buttonConfig || ! ppcpConfig ) {
|
||||
/*
|
||||
* No PayPal buttons present on this page, but maybe a bootstrap module needs to be
|
||||
* initialized. Skip loading the SDK or gateway configuration, and directly initialize
|
||||
* the module.
|
||||
*/
|
||||
bootstrap();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const usedInMiniCart = ppcpConfig.mini_cart_buttons_enabled;
|
||||
const pageHasButton =
|
||||
null !== document.getElementById( buttonConfig.button.wrapper );
|
||||
|
||||
// If button wrapper is not present then there is no need to load the scripts.
|
||||
// minicart loads later?
|
||||
if ( ! isMiniCart && ! isButton ) {
|
||||
if ( ! usedInMiniCart && ! pageHasButton ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -63,5 +79,4 @@ import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Hel
|
|||
} )( {
|
||||
buttonConfig: window.wc_ppcp_applepay,
|
||||
ppcpConfig: window.PayPalCommerceGateway,
|
||||
jQuery: window.jQuery,
|
||||
} );
|
||||
|
|
|
@ -1008,7 +1008,7 @@ class ApplePayButton implements ButtonInterface {
|
|||
*/
|
||||
protected function hide_gateway_until_eligible(): void {
|
||||
?>
|
||||
<style id="ppcp-hide-apple-pay">.wc_payment_method.payment_method_ppcp-applepay{display:none}</style>
|
||||
<style data-hide-gateway="ppcp-applepay">.wc_payment_method.payment_method_ppcp-applepay{display:none}</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ import {
|
|||
dispatchButtonEvent,
|
||||
observeButtonEvent,
|
||||
} from '../Helper/PaymentButtonHelpers';
|
||||
import { isVisible } from '../Helper/Hiding';
|
||||
import { isDisabled, setEnabled } from '../Helper/ButtonDisabler';
|
||||
|
||||
/**
|
||||
* Collection of all available styling options for this button.
|
||||
|
@ -184,6 +186,13 @@ export default class PaymentButton {
|
|||
*/
|
||||
#isVisible = true;
|
||||
|
||||
/**
|
||||
* Whether this button is enabled (can be clicked).
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
#isEnabled = true;
|
||||
|
||||
/**
|
||||
* The currently visible payment button.
|
||||
*
|
||||
|
@ -192,6 +201,13 @@ export default class PaymentButton {
|
|||
*/
|
||||
#button = null;
|
||||
|
||||
/**
|
||||
* List of checks to perform to verify the PaymentButton has is configured correctly.
|
||||
*
|
||||
* @type {{check, errorMessage, shouldPass}[]}
|
||||
*/
|
||||
#validationChecks = [];
|
||||
|
||||
/**
|
||||
* Factory method to create a new PaymentButton while limiting a single instance per context.
|
||||
*
|
||||
|
@ -305,6 +321,11 @@ export default class PaymentButton {
|
|||
);
|
||||
this.applyButtonStyles( this.#buttonConfig );
|
||||
|
||||
this.registerValidationRules(
|
||||
this.#assertIsInvalid.bind( this ),
|
||||
this.#assertIsValid.bind( this )
|
||||
);
|
||||
|
||||
apmButtonsInit( this.#ppcpConfig );
|
||||
this.initEventListeners();
|
||||
}
|
||||
|
@ -559,6 +580,29 @@ export default class PaymentButton {
|
|||
this.triggerRedraw();
|
||||
}
|
||||
|
||||
/**
|
||||
* The enabled/disabled state of the button (whether it can be clicked).
|
||||
*
|
||||
* @return {boolean} True indicates, that the button is enabled.
|
||||
*/
|
||||
get isEnabled() {
|
||||
return this.#isEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the enabled/disabled state of the button.
|
||||
*
|
||||
* @param {boolean} newState Whether the button is enabled.
|
||||
*/
|
||||
set isEnabled( newState ) {
|
||||
if ( this.#isEnabled === newState ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#isEnabled = newState;
|
||||
this.triggerRedraw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTML element that wraps the current button
|
||||
*
|
||||
|
@ -569,6 +613,23 @@ export default class PaymentButton {
|
|||
return document.getElementById( this.wrapperId );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the standard PayPal smart button selector for the current context.
|
||||
*
|
||||
* @return {string | null} The selector, or null if not available.
|
||||
*/
|
||||
get ppcpButtonWrapperSelector() {
|
||||
if ( PaymentContext.Blocks.includes( this.context ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( this.context === PaymentContext.MiniCart ) {
|
||||
return this.ppcpConfig?.button?.mini_cart_wrapper;
|
||||
}
|
||||
|
||||
return this.ppcpConfig?.button?.wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the main button-wrapper is present in the current DOM.
|
||||
*
|
||||
|
@ -634,16 +695,75 @@ export default class PaymentButton {
|
|||
this.#logger.group( label );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a validation check that marks the configuration as invalid when passed.
|
||||
*
|
||||
* @param {Function} check - A function that returns a truthy value if the check passes.
|
||||
* @param {string} errorMessage - The error message to display if the check fails.
|
||||
*/
|
||||
#assertIsInvalid( check, errorMessage ) {
|
||||
this.#validationChecks.push( {
|
||||
check,
|
||||
errorMessage,
|
||||
shouldPass: false,
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a validation check that instantly marks the configuration as valid when passed.
|
||||
*
|
||||
* @param {Function} check - A function that returns a truthy value if the check passes.
|
||||
*/
|
||||
#assertIsValid( check ) {
|
||||
this.#validationChecks.push( { check, shouldPass: true } );
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a series of validation steps to ensure the payment button is configured correctly.
|
||||
*
|
||||
* Each validation step is executed in the order they are defined within this method.
|
||||
*
|
||||
* If a validation step using `invalidIf` returns true, the configuration is immediately considered
|
||||
* invalid, and an error message is logged. Conversely, if a validation step using `validIf`
|
||||
* returns true, the configuration is immediately considered valid.
|
||||
*
|
||||
* If no validation step returns true, the configuration is assumed to be valid by default.
|
||||
*
|
||||
* @param {(condition: () => boolean, errorMessage: string) => void} invalidIf - Registers a validation step that fails if the condition returns true.
|
||||
* @param {(condition: () => boolean) => void} validIf - Registers a validation step that passes if the condition returns true.
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
registerValidationRules( invalidIf, validIf ) {}
|
||||
|
||||
/**
|
||||
* Determines if the current button instance has valid and complete configuration details.
|
||||
* Used during initialization to decide if the button can be initialized or should be skipped.
|
||||
*
|
||||
* Can be implemented by the derived class.
|
||||
* All required validation steps must be registered in the constructor of the derived class
|
||||
* using `this.addValidationFailure()` or `this.addValidationSuccess()`.
|
||||
*
|
||||
* @param {boolean} [silent=false] - Set to true to suppress console errors.
|
||||
* @return {boolean} True indicates the config is valid and initialization can continue.
|
||||
*/
|
||||
validateConfiguration( silent = false ) {
|
||||
for ( const step of this.#validationChecks ) {
|
||||
const result = step.check();
|
||||
|
||||
if ( step.shouldPass && result ) {
|
||||
// If a success check passes, mark as valid immediately.
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! step.shouldPass && result ) {
|
||||
// If a failure check passes, mark as invalid.
|
||||
if ( ! silent && step.errorMessage ) {
|
||||
this.error( step.errorMessage );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -697,7 +817,23 @@ export default class PaymentButton {
|
|||
}
|
||||
|
||||
/**
|
||||
* Attaches event listeners to show or hide the payment button when needed.
|
||||
* Applies the visibility and enabled state from the PayPal button.
|
||||
* Intended for the product page, may not work correctly on the checkout page.
|
||||
*/
|
||||
syncProductButtonsState() {
|
||||
const ppcpButton = document.querySelector(
|
||||
this.ppcpButtonWrapperSelector
|
||||
);
|
||||
if ( ! ppcpButton ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isVisible = isVisible( ppcpButton );
|
||||
this.isEnabled = ! isDisabled( ppcpButton );
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches event listeners to show/hide or enable/disable the payment button when needed.
|
||||
*/
|
||||
initEventListeners() {
|
||||
// Refresh the button - this might show, hide or re-create the payment button.
|
||||
|
@ -726,6 +862,24 @@ export default class PaymentButton {
|
|||
callback: () => ( this.isVisible = true ),
|
||||
} );
|
||||
}
|
||||
|
||||
// On the product page, copy the visibility and enabled state from the PayPal button.
|
||||
if ( this.context === PaymentContext.Product ) {
|
||||
jQuery( document ).on(
|
||||
'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled',
|
||||
( ev, data ) => {
|
||||
if (
|
||||
! jQuery( data.selector ).is(
|
||||
this.ppcpButtonWrapperSelector
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.syncProductButtonsState();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -812,6 +966,12 @@ export default class PaymentButton {
|
|||
|
||||
// Apply the wrapper visibility.
|
||||
wrapper.style.display = this.isVisible ? 'block' : 'none';
|
||||
|
||||
// Apply the enabled/disabled state.
|
||||
// On the product page, use the form to display error messages if clicked while disabled.
|
||||
const form =
|
||||
this.context === PaymentContext.Product ? 'form.cart' : null;
|
||||
setEnabled( wrapper, this.isEnabled, form );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -215,45 +215,31 @@ class GooglepayButton extends PaymentButton {
|
|||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
validateConfiguration( silent = false ) {
|
||||
const validEnvs = [ 'PRODUCTION', 'TEST' ];
|
||||
registerValidationRules( invalidIf, validIf ) {
|
||||
invalidIf(
|
||||
() =>
|
||||
! [ 'TEST', 'PRODUCTION' ].includes(
|
||||
this.buttonConfig.environment
|
||||
),
|
||||
`Invalid environment: ${ this.buttonConfig.environment }`
|
||||
);
|
||||
|
||||
const isInvalid = ( ...args ) => {
|
||||
if ( ! silent ) {
|
||||
this.error( ...args );
|
||||
}
|
||||
return false;
|
||||
};
|
||||
validIf( () => this.isPreview );
|
||||
|
||||
if ( ! validEnvs.includes( this.buttonConfig.environment ) ) {
|
||||
return isInvalid(
|
||||
'Invalid environment:',
|
||||
this.buttonConfig.environment
|
||||
);
|
||||
}
|
||||
invalidIf(
|
||||
() => ! this.googlePayConfig,
|
||||
'No API configuration - missing configure() call?'
|
||||
);
|
||||
|
||||
// Preview buttons only need a valid environment.
|
||||
if ( this.isPreview ) {
|
||||
return true;
|
||||
}
|
||||
invalidIf(
|
||||
() => ! this.transactionInfo,
|
||||
'No transactionInfo - missing configure() call?'
|
||||
);
|
||||
|
||||
if ( ! this.googlePayConfig ) {
|
||||
return isInvalid(
|
||||
'No API configuration - missing configure() call?'
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! this.transactionInfo ) {
|
||||
return isInvalid(
|
||||
'No transactionInfo - missing configure() call?'
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! typeof this.contextHandler?.validateContext() ) {
|
||||
return isInvalid( 'Invalid context handler.', this.contextHandler );
|
||||
}
|
||||
|
||||
return true;
|
||||
invalidIf(
|
||||
() => ! this.contextHandler?.validateContext(),
|
||||
`Invalid context handler.`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue