diff --git a/modules/ppcp-googlepay/resources/js/Block/components/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/Block/components/GooglepayButton.js new file mode 100644 index 000000000..a5caab59d --- /dev/null +++ b/modules/ppcp-googlepay/resources/js/Block/components/GooglepayButton.js @@ -0,0 +1,53 @@ +import { useState, useEffect } from '@wordpress/element'; +import useGooglepayApiToGenerateButton from '../hooks/useGooglepayApiToGenerateButton'; +import usePayPalScript from '../hooks/usePayPalScript'; +import useGooglepayScript from '../hooks/useGooglepayScript'; +import useGooglepayConfig from '../hooks/useGooglepayConfig'; + +const GooglepayButton = ( { namespace, buttonConfig, ppcpConfig } ) => { + const [ buttonHtml, setButtonHtml ] = useState( '' ); + const [ buttonElement, setButtonElement ] = useState( null ); + const [ componentFrame, setComponentFrame ] = useState( null ); + const isPayPalLoaded = usePayPalScript( namespace, ppcpConfig ); + + const isGooglepayLoaded = useGooglepayScript( + componentFrame, + buttonConfig, + isPayPalLoaded + ); + + const googlepayConfig = useGooglepayConfig( namespace, isGooglepayLoaded ); + + useEffect( () => { + if ( ! buttonElement ) { + return; + } + + setComponentFrame( buttonElement.ownerDocument ); + }, [ buttonElement ] ); + + const googlepayButton = useGooglepayApiToGenerateButton( + componentFrame, + namespace, + buttonConfig, + ppcpConfig, + googlepayConfig + ); + + useEffect( () => { + if ( googlepayButton ) { + const hideLoader = + ''; + setButtonHtml( googlepayButton.outerHTML + hideLoader ); + } + }, [ googlepayButton ] ); + + return ( +
+ ); +}; + +export default GooglepayButton; diff --git a/modules/ppcp-googlepay/resources/js/Block/hooks/useButtonStyles.js b/modules/ppcp-googlepay/resources/js/Block/hooks/useButtonStyles.js new file mode 100644 index 000000000..6e214acb2 --- /dev/null +++ b/modules/ppcp-googlepay/resources/js/Block/hooks/useButtonStyles.js @@ -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; diff --git a/modules/ppcp-googlepay/resources/js/Block/hooks/useGooglepayApiToGenerateButton.js b/modules/ppcp-googlepay/resources/js/Block/hooks/useGooglepayApiToGenerateButton.js new file mode 100644 index 000000000..fe5b342a7 --- /dev/null +++ b/modules/ppcp-googlepay/resources/js/Block/hooks/useGooglepayApiToGenerateButton.js @@ -0,0 +1,57 @@ +import { useEffect, useState } from '@wordpress/element'; +import useButtonStyles from './useButtonStyles'; + +const useGooglepayApiToGenerateButton = ( + componentDocument, + namespace, + buttonConfig, + ppcpConfig, + googlepayConfig +) => { + const [ googlepayButton, setGooglepayButton ] = useState( null ); + const buttonStyles = useButtonStyles( buttonConfig, ppcpConfig ); + + useEffect( () => { + if ( + ! componentDocument?.defaultView || + ! buttonConfig || + ! googlepayConfig + ) { + return; + } + + const api = componentDocument.defaultView.google?.payments?.api; + if ( ! api ) { + return; + } + + const paymentsClient = new api.PaymentsClient( { + environment: 'TEST', + } ); + + const googlePayButtonOptions = { + allowedPaymentMethods: googlepayConfig.allowedPaymentMethods, + buttonColor: buttonConfig.buttonColor || 'black', + buttonType: buttonConfig.buttonType || 'pay', + buttonLocale: buttonConfig.buttonLocale || 'en', + buttonSizeMode: 'fill', + }; + + const button = paymentsClient.createButton( { + ...googlePayButtonOptions, + onClick: ( event ) => { + event.preventDefault(); + }, + } ); + + setGooglepayButton( button ); + + return () => { + setGooglepayButton( null ); + }; + }, [ namespace, buttonConfig, ppcpConfig, googlepayConfig, buttonStyles ] ); + + return googlepayButton; +}; + +export default useGooglepayApiToGenerateButton; diff --git a/modules/ppcp-googlepay/resources/js/Block/hooks/useGooglepayConfig.js b/modules/ppcp-googlepay/resources/js/Block/hooks/useGooglepayConfig.js new file mode 100644 index 000000000..21971e871 --- /dev/null +++ b/modules/ppcp-googlepay/resources/js/Block/hooks/useGooglepayConfig.js @@ -0,0 +1,26 @@ +import { useState, useEffect } from '@wordpress/element'; + +const useGooglepayConfig = ( namespace, isGooglepayLoaded ) => { + const [ googlePayConfig, setGooglePayConfig ] = useState( null ); + + useEffect( () => { + const fetchConfig = async () => { + if ( ! isGooglepayLoaded ) { + return; + } + + try { + const config = await window[ namespace ].Googlepay().config(); + setGooglePayConfig( config ); + } catch ( error ) { + console.error( 'Failed to fetch Google Pay config:', error ); + } + }; + + fetchConfig(); + }, [ namespace, isGooglepayLoaded ] ); + + return googlePayConfig; +}; + +export default useGooglepayConfig; diff --git a/modules/ppcp-googlepay/resources/js/Block/hooks/useGooglepayScript.js b/modules/ppcp-googlepay/resources/js/Block/hooks/useGooglepayScript.js new file mode 100644 index 000000000..3128f4bc4 --- /dev/null +++ b/modules/ppcp-googlepay/resources/js/Block/hooks/useGooglepayScript.js @@ -0,0 +1,65 @@ +import { useState, useEffect } from '@wordpress/element'; +import { loadCustomScript } from '@paypal/paypal-js'; + +const useGooglepayScript = ( + componentDocument, + buttonConfig, + isPayPalLoaded +) => { + const [ isGooglepayLoaded, setIsGooglepayLoaded ] = 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 loadGooglepayScript = 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 ); + } + ); + setIsGooglepayLoaded( true ); + } catch ( error ) { + console.error( 'Failed to load Googlepay script:', error ); + } + }; + + loadGooglepayScript(); + }, [ componentDocument, buttonConfig, isPayPalLoaded ] ); + + return isGooglepayLoaded; +}; + +export default useGooglepayScript; diff --git a/modules/ppcp-googlepay/resources/js/Block/hooks/usePayPalScript.js b/modules/ppcp-googlepay/resources/js/Block/hooks/usePayPalScript.js new file mode 100644 index 000000000..fb2e0005d --- /dev/null +++ b/modules/ppcp-googlepay/resources/js/Block/hooks/usePayPalScript.js @@ -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 += ',googlepay'; + + 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; diff --git a/modules/ppcp-googlepay/resources/js/GooglepayManagerBlockEditor.js b/modules/ppcp-googlepay/resources/js/GooglepayManagerBlockEditor.js index 2bf5a55c3..1e3bd6ebb 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayManagerBlockEditor.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayManagerBlockEditor.js @@ -1,62 +1,15 @@ -import GooglepayButton from './GooglepayButton'; -import ContextHandlerFactory from './Context/ContextHandlerFactory'; +import GooglepayButton from './Block/components/GooglepayButton'; -class GooglepayManagerBlockEditor { - constructor( namespace, buttonConfig, ppcpConfig ) { - this.namespace = namespace; - this.buttonConfig = buttonConfig; - this.ppcpConfig = ppcpConfig; - this.googlePayConfig = null; - this.transactionInfo = null; - this.contextHandler = null; - } - - init() { - ( async () => { - await this.config(); - } )(); - } - - async config() { - try { - // Gets GooglePay configuration of the PayPal merchant. - this.googlePayConfig = await window[ this.namespace ] - .Googlepay() - .config(); - - // Fetch transaction information. - this.transactionInfo = await this.fetchTransactionInfo(); - - const button = new GooglepayButton( - this.ppcpConfig.context, - null, - this.buttonConfig, - this.ppcpConfig, - this.contextHandler - ); - - button.init( this.googlePayConfig, this.transactionInfo ); - } catch ( error ) { - console.error( 'Failed to initialize Google Pay:', error ); - } - } - - async fetchTransactionInfo() { - try { - if ( ! this.contextHandler ) { - this.contextHandler = ContextHandlerFactory.create( - this.ppcpConfig.context, - this.buttonConfig, - this.ppcpConfig, - null - ); - } - return null; - } catch ( error ) { - console.error( 'Error fetching transaction info:', error ); - throw error; - } - } -} +const GooglepayManagerBlockEditor = ( { + namespace, + buttonConfig, + ppcpConfig, +} ) => ( +