From 088d17e927d83bafd71bc95484ffcce82851cb85 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 14 Nov 2024 09:25:09 +0100 Subject: [PATCH 1/3] Apple Pay: Add support for Button Options in the Block Checkout --- .../resources/js/ApplepayButton.js | 137 ++++++++++++++++-- .../resources/js/ApplepayManager.js | 112 ++++++++------ .../js/ApplepayManagerBlockEditor.js | 2 + .../js/Block/components/ApplePayButton.js | 38 ++++- .../ppcp-applepay/resources/js/boot-block.js | 12 +- 5 files changed, 233 insertions(+), 68 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 482557327..04a2894e5 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -92,6 +92,24 @@ class ApplePayButton extends PaymentButton { */ #product = {}; + /** + * The start time of the configuration process. + * @type {number} + */ + #configureStartTime = 0; + + /** + * The maximum time to wait for buttonAttributes before proceeding with initialization. + * @type {number} + */ + #maxWaitTime = 1000; + + /** + * The stored button attributes. + * @type {Object|null} + */ + #storedButtonAttributes = null; + /** * @inheritDoc */ @@ -125,7 +143,8 @@ class ApplePayButton extends PaymentButton { externalHandler, buttonConfig, ppcpConfig, - contextHandler + contextHandler, + buttonAttributes ) { // Disable debug output in the browser console: // buttonConfig.is_debug = false; @@ -135,7 +154,8 @@ class ApplePayButton extends PaymentButton { externalHandler, buttonConfig, ppcpConfig, - contextHandler + contextHandler, + buttonAttributes ); this.init = this.init.bind( this ); @@ -220,6 +240,20 @@ class ApplePayButton extends PaymentButton { 'No transactionInfo - missing configure() call?' ); + invalidIf( + () => + this.buttonAttributes?.height && + isNaN( parseInt( this.buttonAttributes.height ) ), + 'Invalid height in buttonAttributes' + ); + + invalidIf( + () => + this.buttonAttributes?.borderRadius && + isNaN( parseInt( this.buttonAttributes.borderRadius ) ), + 'Invalid borderRadius in buttonAttributes' + ); + invalidIf( () => ! this.contextHandler?.validateContext(), `Invalid context handler.` @@ -229,12 +263,60 @@ class ApplePayButton extends PaymentButton { /** * Configures the button instance. Must be called before the initial `init()`. * - * @param {Object} apiConfig - API configuration. - * @param {TransactionInfo} transactionInfo - Transaction details. + * @param {Object} apiConfig - API configuration. + * @param {TransactionInfo} transactionInfo - Transaction details. + * @param {Object} buttonAttributes - Button attributes. */ - configure( apiConfig, transactionInfo ) { + configure( apiConfig, transactionInfo, buttonAttributes = {} ) { + // Start timing on first configure call + if ( ! this.#configureStartTime ) { + this.#configureStartTime = Date.now(); + } + + // If valid buttonAttributes, store them + if ( buttonAttributes?.height && buttonAttributes?.borderRadius ) { + this.#storedButtonAttributes = { ...buttonAttributes }; + } + + // Use stored attributes if current ones are missing + const attributes = buttonAttributes?.height + ? buttonAttributes + : this.#storedButtonAttributes; + + // Check if we've exceeded wait time + const timeWaited = Date.now() - this.#configureStartTime; + if ( timeWaited > this.#maxWaitTime ) { + this.log( + 'ApplePay: Timeout waiting for buttonAttributes - proceeding with initialization' + ); + this.#applePayConfig = apiConfig; + this.#transactionInfo = transactionInfo; + this.buttonAttributes = attributes || buttonAttributes; + this.init(); + return; + } + + // Block any initialization until we have valid buttonAttributes + if ( ! attributes?.height || ! attributes?.borderRadius ) { + setTimeout( + () => + this.configure( + apiConfig, + transactionInfo, + buttonAttributes + ), + 100 + ); + return; + } + + // Reset timer for future configure calls + this.#configureStartTime = 0; + this.#applePayConfig = apiConfig; this.#transactionInfo = transactionInfo; + this.buttonAttributes = attributes; + this.init(); } init() { @@ -321,17 +403,35 @@ class ApplePayButton extends PaymentButton { applyWrapperStyles() { super.applyWrapperStyles(); - const { height } = this.style; + const wrapper = this.wrapperElement; + if ( ! wrapper ) { + return; + } - if ( height ) { - const wrapper = this.wrapperElement; + // Try stored attributes if current ones are missing + const attributes = + this.buttonAttributes?.height || this.buttonAttributes?.borderRadius + ? this.buttonAttributes + : this.#storedButtonAttributes; - wrapper.style.setProperty( - '--apple-pay-button-height', - `${ height }px` - ); + // Apply height if available + if ( attributes?.height ) { + const height = parseInt( attributes.height, 10 ); + if ( ! isNaN( height ) ) { + wrapper.style.setProperty( + '--apple-pay-button-height', + `${ height }px` + ); + wrapper.style.height = `${ height }px`; + } + } - wrapper.style.height = `${ height }px`; + // Apply border radius if available + if ( attributes?.borderRadius ) { + const borderRadius = parseInt( attributes.borderRadius, 10 ); + if ( ! isNaN( borderRadius ) ) { + wrapper.style.borderRadius = `${ borderRadius }px`; + } } } @@ -342,12 +442,23 @@ class ApplePayButton extends PaymentButton { addButton() { const { color, type, language } = this.style; + // If current buttonAttributes are missing, try to use stored ones + if ( + ! this.buttonAttributes?.height && + this.#storedButtonAttributes?.height + ) { + this.buttonAttributes = { ...this.#storedButtonAttributes }; + } + const button = document.createElement( 'apple-pay-button' ); button.id = 'apple-' + this.wrapperId; + button.setAttribute( 'buttonstyle', color ); button.setAttribute( 'type', type ); button.setAttribute( 'locale', language ); + button.style.display = 'block'; + button.addEventListener( 'click', ( evt ) => { evt.preventDefault(); this.onButtonClick(); diff --git a/modules/ppcp-applepay/resources/js/ApplepayManager.js b/modules/ppcp-applepay/resources/js/ApplepayManager.js index 673078121..794464f70 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayManager.js +++ b/modules/ppcp-applepay/resources/js/ApplepayManager.js @@ -3,65 +3,83 @@ 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, buttonAttributes = {} ) { + this.namespace = namespace; + this.buttonConfig = buttonConfig; + this.ppcpConfig = ppcpConfig; + this.buttonAttributes = buttonAttributes; + this.applePayConfig = null; + this.transactionInfo = null; + this.contextHandler = null; - constructor( namespace, buttonConfig, ppcpConfig ) { - this.#namespace = namespace; - this.#buttonConfig = buttonConfig; - this.#ppcpConfig = ppcpConfig; + this.buttons = []; - this.onContextBootstrap = this.onContextBootstrap.bind( this ); - buttonModuleWatcher.watchContextBootstrap( this.onContextBootstrap ); - } + buttonModuleWatcher.watchContextBootstrap( async ( bootstrap ) => { + this.contextHandler = ContextHandlerFactory.create( + bootstrap.context, + buttonConfig, + ppcpConfig, + bootstrap.handler + ); - async onContextBootstrap( bootstrap ) { - this.#contextHandler = ContextHandlerFactory.create( - bootstrap.context, - this.#buttonConfig, - this.#ppcpConfig, - bootstrap.handler - ); + const button = ApplePayButton.createButton( + bootstrap.context, + bootstrap.handler, + buttonConfig, + ppcpConfig, + this.contextHandler, + this.buttonAttributes + ); - const button = ApplePayButton.createButton( - bootstrap.context, - bootstrap.handler, - this.#buttonConfig, - this.#ppcpConfig, - this.#contextHandler - ); + this.buttons.push( button ); + const initButton = () => { + button.configure( + this.applePayConfig, + this.transactionInfo, + this.buttonAttributes + ); + button.init(); + }; - this.#buttons.push( button ); + // Initialize button only if applePayConfig and transactionInfo are already fetched. + if ( this.applePayConfig && this.transactionInfo ) { + initButton(); + } else { + // Ensure ApplePayConfig is loaded before proceeding. + await this.init(); - // Ensure ApplePayConfig is loaded before proceeding. - await this.init(); - - button.configure( this.#applePayConfig, this.#transactionInfo ); - button.init(); + if ( this.applePayConfig && this.transactionInfo ) { + initButton(); + } + } + } ); } async init() { try { - if ( ! this.#applePayConfig ) { - this.#applePayConfig = await window[ this.#namespace ] + if ( ! this.applePayConfig ) { + // Gets ApplePay configuration of the PayPal merchant. + 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.transactionInfo ) { + this.transactionInfo = await this.fetchTransactionInfo(); + } - if ( ! this.#applePayConfig ) { - console.error( 'No transactionInfo found during init' ); + if ( ! this.applePayConfig ) { + console.error( 'No ApplePayConfig received during init' ); + } else if ( ! this.transactionInfo ) { + console.error( 'No transactionInfo found during init' ); + } else { + for ( const button of this.buttons ) { + button.configure( + this.applePayConfig, + this.transactionInfo, + this.buttonAttributes + ); + button.init(); } } } catch ( error ) { @@ -71,10 +89,10 @@ class ApplePayManager { async fetchTransactionInfo() { try { - if ( ! this.#contextHandler ) { + if ( ! this.contextHandler ) { throw new Error( 'ContextHandler is not initialized' ); } - return await this.#contextHandler.transactionInfo(); + return await this.contextHandler.transactionInfo(); } catch ( error ) { console.error( 'Error fetching transaction info:', error ); throw error; @@ -82,7 +100,7 @@ class ApplePayManager { } reinit() { - for ( const button of this.#buttons ) { + for ( const button of this.buttons ) { button.reinit(); } } diff --git a/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js b/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js index 0d60929e4..3381baf93 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js +++ b/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js @@ -4,11 +4,13 @@ const ApplePayManagerBlockEditor = ( { namespace, buttonConfig, ppcpConfig, + buttonAttributes, } ) => ( ); diff --git a/modules/ppcp-applepay/resources/js/Block/components/ApplePayButton.js b/modules/ppcp-applepay/resources/js/Block/components/ApplePayButton.js index c01f04320..079c8f661 100644 --- a/modules/ppcp-applepay/resources/js/Block/components/ApplePayButton.js +++ b/modules/ppcp-applepay/resources/js/Block/components/ApplePayButton.js @@ -4,7 +4,12 @@ import usePayPalScript from '../hooks/usePayPalScript'; import useApplepayScript from '../hooks/useApplepayScript'; import useApplepayConfig from '../hooks/useApplepayConfig'; -const ApplepayButton = ( { namespace, buttonConfig, ppcpConfig } ) => { +const ApplepayButton = ( { + namespace, + buttonConfig, + ppcpConfig, + buttonAttributes, +} ) => { const [ buttonHtml, setButtonHtml ] = useState( '' ); const [ buttonElement, setButtonElement ] = useState( null ); const [ componentFrame, setComponentFrame ] = useState( null ); @@ -31,19 +36,42 @@ const ApplepayButton = ( { namespace, buttonConfig, ppcpConfig } ) => { namespace, buttonConfig, ppcpConfig, - applepayConfig + applepayConfig, + buttonAttributes ); useEffect( () => { - if ( applepayButton ) { - setButtonHtml( applepayButton.outerHTML ); + if ( ! applepayButton || ! buttonElement ) { + return; } - }, [ applepayButton ] ); + + setButtonHtml( applepayButton.outerHTML ); + + // Add timeout to ensure button is displayed after render + setTimeout( () => { + const button = buttonElement.querySelector( 'apple-pay-button' ); + if ( button ) { + button.style.display = 'block'; + } + }, 100 ); // Add a small delay to ensure DOM is ready + }, [ applepayButton, buttonElement ] ); return (
); }; diff --git a/modules/ppcp-applepay/resources/js/boot-block.js b/modules/ppcp-applepay/resources/js/boot-block.js index 8013177d0..27d036b35 100644 --- a/modules/ppcp-applepay/resources/js/boot-block.js +++ b/modules/ppcp-applepay/resources/js/boot-block.js @@ -19,7 +19,7 @@ if ( typeof window.PayPalCommerceGateway === 'undefined' ) { window.PayPalCommerceGateway = ppcpConfig; } -const ApplePayComponent = ( { isEditing } ) => { +const ApplePayComponent = ( { isEditing, buttonAttributes } ) => { const [ paypalLoaded, setPaypalLoaded ] = useState( false ); const [ applePayLoaded, setApplePayLoaded ] = useState( false ); const wrapperRef = useRef( null ); @@ -57,8 +57,13 @@ const ApplePayComponent = ( { isEditing } ) => { buttonConfig.reactWrapper = wrapperRef.current; - new ManagerClass( namespace, buttonConfig, ppcpConfig ); - }, [ paypalLoaded, applePayLoaded, isEditing ] ); + new ManagerClass( + namespace, + buttonConfig, + ppcpConfig, + buttonAttributes + ); + }, [ paypalLoaded, applePayLoaded, isEditing, buttonAttributes ] ); if ( isEditing ) { return ( @@ -66,6 +71,7 @@ const ApplePayComponent = ( { isEditing } ) => { namespace={ namespace } buttonConfig={ buttonConfig } ppcpConfig={ ppcpConfig } + buttonAttributes={ buttonAttributes } /> ); } From 9766ade13d33df56453556d40aa29b2278e09d79 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 14 Nov 2024 21:38:06 +0100 Subject: [PATCH 2/3] Apple Pay: Register support for the Button Options --- modules/ppcp-applepay/resources/js/boot-block.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ppcp-applepay/resources/js/boot-block.js b/modules/ppcp-applepay/resources/js/boot-block.js index 27d036b35..eff026251 100644 --- a/modules/ppcp-applepay/resources/js/boot-block.js +++ b/modules/ppcp-applepay/resources/js/boot-block.js @@ -108,5 +108,6 @@ registerExpressPaymentMethod( { canMakePayment: () => buttonData.enabled, supports: { features, + style: [ 'height', 'borderRadius' ], }, } ); From b63b1e7c39aa16e9e695207c2421eff654130b6b Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Fri, 15 Nov 2024 09:36:27 +0100 Subject: [PATCH 3/3] Apple Pay: Fix default height and border radius --- .../resources/js/ApplepayButton.js | 40 +++++++++++-------- .../js/Block/components/ApplePayButton.js | 4 +- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 04a2894e5..155ab5d7b 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -414,24 +414,32 @@ class ApplePayButton extends PaymentButton { ? this.buttonAttributes : this.#storedButtonAttributes; - // Apply height if available - if ( attributes?.height ) { - const height = parseInt( attributes.height, 10 ); - if ( ! isNaN( height ) ) { - wrapper.style.setProperty( - '--apple-pay-button-height', - `${ height }px` - ); - wrapper.style.height = `${ height }px`; - } + const defaultHeight = 48; + const defaultBorderRadius = 4; + + const height = attributes?.height + ? parseInt( attributes.height, 10 ) + : defaultHeight; + + if ( ! isNaN( height ) ) { + wrapper.style.setProperty( + '--apple-pay-button-height', + `${ height }px` + ); + wrapper.style.height = `${ height }px`; + } else { + wrapper.style.setProperty( + '--apple-pay-button-height', + `${ defaultHeight }px` + ); + wrapper.style.height = `${ defaultHeight }px`; } - // Apply border radius if available - if ( attributes?.borderRadius ) { - const borderRadius = parseInt( attributes.borderRadius, 10 ); - if ( ! isNaN( borderRadius ) ) { - wrapper.style.borderRadius = `${ borderRadius }px`; - } + const borderRadius = attributes?.borderRadius + ? parseInt( attributes.borderRadius, 10 ) + : defaultBorderRadius; + if ( ! isNaN( borderRadius ) ) { + wrapper.style.borderRadius = `${ borderRadius }px`; } } diff --git a/modules/ppcp-applepay/resources/js/Block/components/ApplePayButton.js b/modules/ppcp-applepay/resources/js/Block/components/ApplePayButton.js index 079c8f661..8b4985a1f 100644 --- a/modules/ppcp-applepay/resources/js/Block/components/ApplePayButton.js +++ b/modules/ppcp-applepay/resources/js/Block/components/ApplePayButton.js @@ -63,10 +63,10 @@ const ApplepayButton = ( { style={ { height: buttonAttributes?.height ? `${ buttonAttributes.height }px` - : undefined, + : '48px', '--apple-pay-button-height': buttonAttributes?.height ? `${ buttonAttributes.height }px` - : undefined, + : '48px', borderRadius: buttonAttributes?.borderRadius ? `${ buttonAttributes.borderRadius }px` : undefined,