mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-06 18:16:38 +08:00
🚧 Add block integration (same as GooglePay)
This commit is contained in:
parent
1d46f6154b
commit
50922eb779
8 changed files with 254 additions and 71 deletions
|
@ -1,69 +1,15 @@
|
||||||
import ApplePayButton from './ApplepayButton';
|
import ApplepayButton from './Block/components/ApplePayButton';
|
||||||
import ContextHandlerFactory from './Context/ContextHandlerFactory';
|
|
||||||
|
|
||||||
class ApplePayManagerBlockEditor {
|
const ApplePayManagerBlockEditor = ( {
|
||||||
#namespace = '';
|
namespace,
|
||||||
#buttonConfig = null;
|
buttonConfig,
|
||||||
#ppcpConfig = null;
|
ppcpConfig,
|
||||||
#applePayConfig = null;
|
} ) => (
|
||||||
#contextHandler = null;
|
<ApplepayButton
|
||||||
#transactionInfo = null;
|
namespace={ namespace }
|
||||||
|
buttonConfig={ buttonConfig }
|
||||||
constructor( namespace, buttonConfig, ppcpConfig ) {
|
ppcpConfig={ 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
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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;
|
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;
|
|
@ -19,12 +19,16 @@ if ( typeof window.PayPalCommerceGateway === 'undefined' ) {
|
||||||
window.PayPalCommerceGateway = ppcpConfig;
|
window.PayPalCommerceGateway = ppcpConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ApplePayComponent = ( props ) => {
|
const ApplePayComponent = ( { isEditing } ) => {
|
||||||
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 wrapperRef = useRef( null );
|
||||||
|
|
||||||
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 );
|
||||||
|
@ -40,21 +44,31 @@ 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 ( ! paypalLoaded || ! applePayLoaded ) {
|
if ( isEditing || ! paypalLoaded || ! applePayLoaded ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ManagerClass = props.isEditing
|
const ManagerClass = isEditing
|
||||||
? ApplePayManagerBlockEditor
|
? ApplePayManagerBlockEditor
|
||||||
: ApplePayManager;
|
: ApplePayManager;
|
||||||
|
|
||||||
buttonConfig.reactWrapper = wrapperRef.current;
|
buttonConfig.reactWrapper = wrapperRef.current;
|
||||||
|
|
||||||
new ManagerClass( namespace, buttonConfig, ppcpConfig );
|
new ManagerClass( namespace, buttonConfig, ppcpConfig );
|
||||||
}, [ paypalLoaded, applePayLoaded, props.isEditing ] );
|
}, [ paypalLoaded, applePayLoaded, isEditing ] );
|
||||||
|
|
||||||
|
if ( isEditing ) {
|
||||||
|
return (
|
||||||
|
<ApplePayManagerBlockEditor
|
||||||
|
namespace={ namespace }
|
||||||
|
buttonConfig={ buttonConfig }
|
||||||
|
ppcpConfig={ ppcpConfig }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue