🚧 Add block integration (same as GooglePay)

This commit is contained in:
Philipp Stracker 2024-10-11 11:13:15 +02:00
parent 1d46f6154b
commit 50922eb779
No known key found for this signature in database
8 changed files with 254 additions and 71 deletions

View file

@ -1,69 +1,15 @@
import ApplePayButton from './ApplepayButton';
import ContextHandlerFactory from './Context/ContextHandlerFactory';
import ApplepayButton from './Block/components/ApplePayButton';
class ApplePayManagerBlockEditor {
#namespace = '';
#buttonConfig = null;
#ppcpConfig = null;
#applePayConfig = null;
#contextHandler = null;
#transactionInfo = null;
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();
this.#contextHandler = ContextHandlerFactory.create(
this.#ppcpConfig.context,
this.#buttonConfig,
this.#ppcpConfig,
null
const ApplePayManagerBlockEditor = ( {
namespace,
buttonConfig,
ppcpConfig,
} ) => (
<ApplepayButton
namespace={ namespace }
buttonConfig={ buttonConfig }
ppcpConfig={ ppcpConfig }
/>
);
// Fetch transaction information.
this.#transactionInfo = await this.fetchTransactionInfo();
const button = ApplePayButton.createButton(
this.#ppcpConfig.context,
null,
this.#buttonConfig,
this.#ppcpConfig,
this.#contextHandler
);
button.configure( this.#applePayConfig, this.#transactionInfo );
button.init();
} catch ( error ) {
console.error( 'Failed to initialize Apple Pay:', error );
}
}
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;
}
}
}
export default ApplePayManagerBlockEditor;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -19,12 +19,16 @@ if ( typeof window.PayPalCommerceGateway === 'undefined' ) {
window.PayPalCommerceGateway = ppcpConfig;
}
const ApplePayComponent = ( props ) => {
const ApplePayComponent = ( { isEditing } ) => {
const [ paypalLoaded, setPaypalLoaded ] = useState( false );
const [ applePayLoaded, setApplePayLoaded ] = useState( false );
const wrapperRef = useRef( null );
useEffect( () => {
if ( isEditing ) {
return;
}
// Load ApplePay SDK
loadCustomScript( { url: buttonConfig.sdk_url } ).then( () => {
setApplePayLoaded( true );
@ -40,21 +44,31 @@ const ApplePayComponent = ( props ) => {
.catch( ( error ) => {
console.error( 'Failed to load PayPal script: ', error );
} );
}, [] );
}, [ isEditing ] );
useEffect( () => {
if ( ! paypalLoaded || ! applePayLoaded ) {
if ( isEditing || ! paypalLoaded || ! applePayLoaded ) {
return;
}
const ManagerClass = props.isEditing
const ManagerClass = isEditing
? ApplePayManagerBlockEditor
: ApplePayManager;
buttonConfig.reactWrapper = wrapperRef.current;
new ManagerClass( namespace, buttonConfig, ppcpConfig );
}, [ paypalLoaded, applePayLoaded, props.isEditing ] );
}, [ paypalLoaded, applePayLoaded, isEditing ] );
if ( isEditing ) {
return (
<ApplePayManagerBlockEditor
namespace={ namespace }
buttonConfig={ buttonConfig }
ppcpConfig={ ppcpConfig }
/>
);
}
return (
<div