mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-04 08:47:23 +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 buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher';
|
||||||
import ApplePayButton from './ApplepayButton';
|
import ApplePayButton from './ApplepayButton';
|
||||||
|
import ContextHandlerFactory from './Context/ContextHandlerFactory';
|
||||||
|
|
||||||
class ApplePayManager {
|
class ApplePayManager {
|
||||||
|
#namespace = '';
|
||||||
|
#buttonConfig = null;
|
||||||
|
#ppcpConfig = null;
|
||||||
|
#applePayConfig = null;
|
||||||
|
#contextHandler = null;
|
||||||
|
#transactionInfo = null;
|
||||||
|
#buttons = [];
|
||||||
|
|
||||||
constructor( namespace, buttonConfig, ppcpConfig ) {
|
constructor( namespace, buttonConfig, ppcpConfig ) {
|
||||||
this.namespace = namespace;
|
this.#namespace = namespace;
|
||||||
this.buttonConfig = buttonConfig;
|
this.#buttonConfig = buttonConfig;
|
||||||
this.ppcpConfig = ppcpConfig;
|
this.#ppcpConfig = ppcpConfig;
|
||||||
this.ApplePayConfig = null;
|
|
||||||
this.buttons = [];
|
|
||||||
|
|
||||||
buttonModuleWatcher.watchContextBootstrap( ( bootstrap ) => {
|
this.onContextBootstrap = this.onContextBootstrap.bind( this );
|
||||||
const button = new ApplePayButton(
|
buttonModuleWatcher.watchContextBootstrap( this.onContextBootstrap );
|
||||||
bootstrap.context,
|
|
||||||
bootstrap.handler,
|
|
||||||
buttonConfig,
|
|
||||||
ppcpConfig
|
|
||||||
);
|
|
||||||
|
|
||||||
this.buttons.push( button );
|
|
||||||
|
|
||||||
if ( this.ApplePayConfig ) {
|
|
||||||
button.init( this.ApplePayConfig );
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
async onContextBootstrap( bootstrap ) {
|
||||||
( async () => {
|
this.#contextHandler = ContextHandlerFactory.create(
|
||||||
await this.config();
|
bootstrap.context,
|
||||||
for ( const button of this.buttons ) {
|
this.#buttonConfig,
|
||||||
button.init( this.ApplePayConfig );
|
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() {
|
async init() {
|
||||||
for ( const button of this.buttons ) {
|
try {
|
||||||
button.reinit();
|
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 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async fetchTransactionInfo() {
|
||||||
* Gets Apple Pay configuration of the PayPal merchant.
|
try {
|
||||||
*/
|
if ( ! this.#contextHandler ) {
|
||||||
async config() {
|
throw new Error( 'ContextHandler is not initialized' );
|
||||||
this.ApplePayConfig = await window[ this.namespace ]
|
}
|
||||||
.Applepay()
|
return await this.#contextHandler.transactionInfo();
|
||||||
.config();
|
} 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 {
|
const ApplePayManagerBlockEditor = ( {
|
||||||
constructor( namespace, buttonConfig, ppcpConfig ) {
|
namespace,
|
||||||
this.namespace = namespace;
|
buttonConfig,
|
||||||
this.buttonConfig = buttonConfig;
|
ppcpConfig,
|
||||||
this.ppcpConfig = ppcpConfig;
|
} ) => (
|
||||||
|
<ApplepayButton
|
||||||
/*
|
namespace={ namespace }
|
||||||
* On the front-end, the init method is called when a new button context was detected
|
buttonConfig={ buttonConfig }
|
||||||
* via `buttonModuleWatcher`. In the block editor, we do not need to wait for the
|
ppcpConfig={ ppcpConfig }
|
||||||
* 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 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ApplePayManagerBlockEditor;
|
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.
|
* A single Apple Pay preview button instance.
|
||||||
*/
|
*/
|
||||||
export default class ApplePayPreviewButton extends PreviewButton {
|
export default class ApplePayPreviewButton extends PreviewButton {
|
||||||
|
/**
|
||||||
|
* @type {?PaymentButton}
|
||||||
|
*/
|
||||||
|
#button = null;
|
||||||
|
|
||||||
constructor( args ) {
|
constructor( args ) {
|
||||||
super( args );
|
super( args );
|
||||||
|
|
||||||
|
@ -19,14 +24,18 @@ export default class ApplePayPreviewButton extends PreviewButton {
|
||||||
}
|
}
|
||||||
|
|
||||||
createButton( buttonConfig ) {
|
createButton( buttonConfig ) {
|
||||||
const button = new ApplepayButton(
|
if ( ! this.#button ) {
|
||||||
'preview',
|
this.#button = new ApplepayButton(
|
||||||
null,
|
'preview',
|
||||||
buttonConfig,
|
null,
|
||||||
this.ppcpConfig
|
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 { registerExpressPaymentMethod } from '@woocommerce/blocks-registry';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
import { loadPayPalScript } from '../../../ppcp-button/resources/js/modules/Helper/PayPalScriptLoading';
|
import { loadPayPalScript } from '../../../ppcp-button/resources/js/modules/Helper/PayPalScriptLoading';
|
||||||
import { cartHasSubscriptionProducts } from '../../../ppcp-blocks/resources/js/Helper/Subscription';
|
import { cartHasSubscriptionProducts } from '../../../ppcp-blocks/resources/js/Helper/Subscription';
|
||||||
import { loadCustomScript } from '@paypal/paypal-js';
|
import { loadCustomScript } from '@paypal/paypal-js';
|
||||||
|
@ -18,20 +19,16 @@ if ( typeof window.PayPalCommerceGateway === 'undefined' ) {
|
||||||
window.PayPalCommerceGateway = ppcpConfig;
|
window.PayPalCommerceGateway = ppcpConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ApplePayComponent = ( props ) => {
|
const ApplePayComponent = ( { isEditing } ) => {
|
||||||
const [ bootstrapped, setBootstrapped ] = useState( false );
|
|
||||||
const [ paypalLoaded, setPaypalLoaded ] = useState( false );
|
const [ paypalLoaded, setPaypalLoaded ] = useState( false );
|
||||||
const [ applePayLoaded, setApplePayLoaded ] = useState( false );
|
const [ applePayLoaded, setApplePayLoaded ] = useState( false );
|
||||||
|
const wrapperRef = useRef( null );
|
||||||
const bootstrap = function () {
|
|
||||||
const ManagerClass = props.isEditing
|
|
||||||
? ApplePayManagerBlockEditor
|
|
||||||
: ApplePayManager;
|
|
||||||
const manager = new ManagerClass( namespace, buttonConfig, ppcpConfig );
|
|
||||||
manager.init();
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
|
if ( isEditing ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Load ApplePay SDK
|
// Load ApplePay SDK
|
||||||
loadCustomScript( { url: buttonConfig.sdk_url } ).then( () => {
|
loadCustomScript( { url: buttonConfig.sdk_url } ).then( () => {
|
||||||
setApplePayLoaded( true );
|
setApplePayLoaded( true );
|
||||||
|
@ -47,17 +44,35 @@ const ApplePayComponent = ( props ) => {
|
||||||
.catch( ( error ) => {
|
.catch( ( error ) => {
|
||||||
console.error( 'Failed to load PayPal script: ', error );
|
console.error( 'Failed to load PayPal script: ', error );
|
||||||
} );
|
} );
|
||||||
}, [] );
|
}, [ isEditing ] );
|
||||||
|
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
if ( ! bootstrapped && paypalLoaded && applePayLoaded ) {
|
if ( isEditing || ! paypalLoaded || ! applePayLoaded ) {
|
||||||
setBootstrapped( true );
|
return;
|
||||||
bootstrap();
|
|
||||||
}
|
}
|
||||||
}, [ 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 (
|
return (
|
||||||
<div
|
<div
|
||||||
|
ref={ wrapperRef }
|
||||||
id={ buttonConfig.button.wrapper.replace( '#', '' ) }
|
id={ buttonConfig.button.wrapper.replace( '#', '' ) }
|
||||||
className="ppcp-button-apm ppcp-button-applepay"
|
className="ppcp-button-apm ppcp-button-applepay"
|
||||||
></div>
|
></div>
|
||||||
|
@ -75,6 +90,11 @@ if (
|
||||||
|
|
||||||
registerExpressPaymentMethod( {
|
registerExpressPaymentMethod( {
|
||||||
name: buttonData.id,
|
name: buttonData.id,
|
||||||
|
title: `PayPal - ${ buttonData.title }`,
|
||||||
|
description: __(
|
||||||
|
'Eligible users will see the PayPal button.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
label: <div dangerouslySetInnerHTML={ { __html: buttonData.title } } />,
|
label: <div dangerouslySetInnerHTML={ { __html: buttonData.title } } />,
|
||||||
content: <ApplePayComponent isEditing={ false } />,
|
content: <ApplePayComponent isEditing={ false } />,
|
||||||
edit: <ApplePayComponent isEditing={ true } />,
|
edit: <ApplePayComponent isEditing={ true } />,
|
||||||
|
|
|
@ -3,33 +3,49 @@ import { loadPayPalScript } from '../../../ppcp-button/resources/js/modules/Help
|
||||||
import ApplePayManager from './ApplepayManager';
|
import ApplePayManager from './ApplepayManager';
|
||||||
import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper';
|
import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper';
|
||||||
|
|
||||||
( function ( { buttonConfig, ppcpConfig, jQuery } ) {
|
( function ( { buttonConfig, ppcpConfig } ) {
|
||||||
const namespace = 'ppcpPaypalApplepay';
|
const namespace = 'ppcpPaypalApplepay';
|
||||||
let manager;
|
|
||||||
|
|
||||||
const bootstrap = function () {
|
function bootstrapPayButton() {
|
||||||
manager = new ApplePayManager( namespace, buttonConfig, ppcpConfig );
|
if ( ! buttonConfig || ! ppcpConfig ) {
|
||||||
manager.init();
|
|
||||||
};
|
|
||||||
|
|
||||||
setupButtonEvents( function () {
|
|
||||||
if ( manager ) {
|
|
||||||
manager.reinit();
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
|
|
||||||
document.addEventListener( 'DOMContentLoaded', () => {
|
|
||||||
if (
|
|
||||||
typeof buttonConfig === 'undefined' ||
|
|
||||||
typeof ppcpConfig === 'undefined'
|
|
||||||
) {
|
|
||||||
return;
|
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.
|
// If button wrapper is not present then there is no need to load the scripts.
|
||||||
// minicart loads later?
|
// minicart loads later?
|
||||||
if ( ! isMiniCart && ! isButton ) {
|
if ( ! usedInMiniCart && ! pageHasButton ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,5 +79,4 @@ import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Hel
|
||||||
} )( {
|
} )( {
|
||||||
buttonConfig: window.wc_ppcp_applepay,
|
buttonConfig: window.wc_ppcp_applepay,
|
||||||
ppcpConfig: window.PayPalCommerceGateway,
|
ppcpConfig: window.PayPalCommerceGateway,
|
||||||
jQuery: window.jQuery,
|
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -1008,7 +1008,7 @@ class ApplePayButton implements ButtonInterface {
|
||||||
*/
|
*/
|
||||||
protected function hide_gateway_until_eligible(): void {
|
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
|
<?php
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ import {
|
||||||
dispatchButtonEvent,
|
dispatchButtonEvent,
|
||||||
observeButtonEvent,
|
observeButtonEvent,
|
||||||
} from '../Helper/PaymentButtonHelpers';
|
} from '../Helper/PaymentButtonHelpers';
|
||||||
|
import { isVisible } from '../Helper/Hiding';
|
||||||
|
import { isDisabled, setEnabled } from '../Helper/ButtonDisabler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collection of all available styling options for this button.
|
* Collection of all available styling options for this button.
|
||||||
|
@ -184,6 +186,13 @@ export default class PaymentButton {
|
||||||
*/
|
*/
|
||||||
#isVisible = true;
|
#isVisible = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this button is enabled (can be clicked).
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
#isEnabled = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The currently visible payment button.
|
* The currently visible payment button.
|
||||||
*
|
*
|
||||||
|
@ -192,6 +201,13 @@ export default class PaymentButton {
|
||||||
*/
|
*/
|
||||||
#button = null;
|
#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.
|
* 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.applyButtonStyles( this.#buttonConfig );
|
||||||
|
|
||||||
|
this.registerValidationRules(
|
||||||
|
this.#assertIsInvalid.bind( this ),
|
||||||
|
this.#assertIsValid.bind( this )
|
||||||
|
);
|
||||||
|
|
||||||
apmButtonsInit( this.#ppcpConfig );
|
apmButtonsInit( this.#ppcpConfig );
|
||||||
this.initEventListeners();
|
this.initEventListeners();
|
||||||
}
|
}
|
||||||
|
@ -559,6 +580,29 @@ export default class PaymentButton {
|
||||||
this.triggerRedraw();
|
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
|
* Returns the HTML element that wraps the current button
|
||||||
*
|
*
|
||||||
|
@ -569,6 +613,23 @@ export default class PaymentButton {
|
||||||
return document.getElementById( this.wrapperId );
|
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.
|
* Checks whether the main button-wrapper is present in the current DOM.
|
||||||
*
|
*
|
||||||
|
@ -634,16 +695,75 @@ export default class PaymentButton {
|
||||||
this.#logger.group( label );
|
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.
|
* 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.
|
* 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.
|
* @param {boolean} [silent=false] - Set to true to suppress console errors.
|
||||||
* @return {boolean} True indicates the config is valid and initialization can continue.
|
* @return {boolean} True indicates the config is valid and initialization can continue.
|
||||||
*/
|
*/
|
||||||
validateConfiguration( silent = false ) {
|
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;
|
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() {
|
initEventListeners() {
|
||||||
// Refresh the button - this might show, hide or re-create the payment button.
|
// 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 ),
|
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.
|
// Apply the wrapper visibility.
|
||||||
wrapper.style.display = this.isVisible ? 'block' : 'none';
|
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
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
validateConfiguration( silent = false ) {
|
registerValidationRules( invalidIf, validIf ) {
|
||||||
const validEnvs = [ 'PRODUCTION', 'TEST' ];
|
invalidIf(
|
||||||
|
() =>
|
||||||
|
! [ 'TEST', 'PRODUCTION' ].includes(
|
||||||
|
this.buttonConfig.environment
|
||||||
|
),
|
||||||
|
`Invalid environment: ${ this.buttonConfig.environment }`
|
||||||
|
);
|
||||||
|
|
||||||
const isInvalid = ( ...args ) => {
|
validIf( () => this.isPreview );
|
||||||
if ( ! silent ) {
|
|
||||||
this.error( ...args );
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
if ( ! validEnvs.includes( this.buttonConfig.environment ) ) {
|
invalidIf(
|
||||||
return isInvalid(
|
() => ! this.googlePayConfig,
|
||||||
'Invalid environment:',
|
'No API configuration - missing configure() call?'
|
||||||
this.buttonConfig.environment
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preview buttons only need a valid environment.
|
invalidIf(
|
||||||
if ( this.isPreview ) {
|
() => ! this.transactionInfo,
|
||||||
return true;
|
'No transactionInfo - missing configure() call?'
|
||||||
}
|
);
|
||||||
|
|
||||||
if ( ! this.googlePayConfig ) {
|
invalidIf(
|
||||||
return isInvalid(
|
() => ! this.contextHandler?.validateContext(),
|
||||||
'No API configuration - missing configure() call?'
|
`Invalid context handler.`
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! this.transactionInfo ) {
|
|
||||||
return isInvalid(
|
|
||||||
'No transactionInfo - missing configure() call?'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! typeof this.contextHandler?.validateContext() ) {
|
|
||||||
return isInvalid( 'Invalid context handler.', this.contextHandler );
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue