mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-06 10:47:21 +08:00
Apple Pay: Add support for Button Options in the Block Checkout
This commit is contained in:
parent
f51c41d222
commit
088d17e927
5 changed files with 233 additions and 68 deletions
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,13 @@ const ApplePayManagerBlockEditor = ( {
|
|||
namespace,
|
||||
buttonConfig,
|
||||
ppcpConfig,
|
||||
buttonAttributes,
|
||||
} ) => (
|
||||
<ApplepayButton
|
||||
namespace={ namespace }
|
||||
buttonConfig={ buttonConfig }
|
||||
ppcpConfig={ ppcpConfig }
|
||||
buttonAttributes={ buttonAttributes }
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<div
|
||||
ref={ setButtonElement }
|
||||
dangerouslySetInnerHTML={ { __html: buttonHtml } }
|
||||
style={ {
|
||||
height: buttonAttributes?.height
|
||||
? `${ buttonAttributes.height }px`
|
||||
: undefined,
|
||||
'--apple-pay-button-height': buttonAttributes?.height
|
||||
? `${ buttonAttributes.height }px`
|
||||
: undefined,
|
||||
borderRadius: buttonAttributes?.borderRadius
|
||||
? `${ buttonAttributes.borderRadius }px`
|
||||
: undefined,
|
||||
overflow: 'hidden',
|
||||
} }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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 }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue