mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-06 18:16:38 +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 = {};
|
#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
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
|
@ -125,7 +143,8 @@ class ApplePayButton extends PaymentButton {
|
||||||
externalHandler,
|
externalHandler,
|
||||||
buttonConfig,
|
buttonConfig,
|
||||||
ppcpConfig,
|
ppcpConfig,
|
||||||
contextHandler
|
contextHandler,
|
||||||
|
buttonAttributes
|
||||||
) {
|
) {
|
||||||
// Disable debug output in the browser console:
|
// Disable debug output in the browser console:
|
||||||
// buttonConfig.is_debug = false;
|
// buttonConfig.is_debug = false;
|
||||||
|
@ -135,7 +154,8 @@ class ApplePayButton extends PaymentButton {
|
||||||
externalHandler,
|
externalHandler,
|
||||||
buttonConfig,
|
buttonConfig,
|
||||||
ppcpConfig,
|
ppcpConfig,
|
||||||
contextHandler
|
contextHandler,
|
||||||
|
buttonAttributes
|
||||||
);
|
);
|
||||||
|
|
||||||
this.init = this.init.bind( this );
|
this.init = this.init.bind( this );
|
||||||
|
@ -220,6 +240,20 @@ class ApplePayButton extends PaymentButton {
|
||||||
'No transactionInfo - missing configure() call?'
|
'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(
|
invalidIf(
|
||||||
() => ! this.contextHandler?.validateContext(),
|
() => ! this.contextHandler?.validateContext(),
|
||||||
`Invalid context handler.`
|
`Invalid context handler.`
|
||||||
|
@ -231,10 +265,58 @@ class ApplePayButton extends PaymentButton {
|
||||||
*
|
*
|
||||||
* @param {Object} apiConfig - API configuration.
|
* @param {Object} apiConfig - API configuration.
|
||||||
* @param {TransactionInfo} transactionInfo - Transaction details.
|
* @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.#applePayConfig = apiConfig;
|
||||||
this.#transactionInfo = transactionInfo;
|
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() {
|
init() {
|
||||||
|
@ -321,20 +403,38 @@ class ApplePayButton extends PaymentButton {
|
||||||
applyWrapperStyles() {
|
applyWrapperStyles() {
|
||||||
super.applyWrapperStyles();
|
super.applyWrapperStyles();
|
||||||
|
|
||||||
const { height } = this.style;
|
|
||||||
|
|
||||||
if ( height ) {
|
|
||||||
const wrapper = this.wrapperElement;
|
const wrapper = this.wrapperElement;
|
||||||
|
if ( ! wrapper ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try stored attributes if current ones are missing
|
||||||
|
const attributes =
|
||||||
|
this.buttonAttributes?.height || this.buttonAttributes?.borderRadius
|
||||||
|
? this.buttonAttributes
|
||||||
|
: this.#storedButtonAttributes;
|
||||||
|
|
||||||
|
// Apply height if available
|
||||||
|
if ( attributes?.height ) {
|
||||||
|
const height = parseInt( attributes.height, 10 );
|
||||||
|
if ( ! isNaN( height ) ) {
|
||||||
wrapper.style.setProperty(
|
wrapper.style.setProperty(
|
||||||
'--apple-pay-button-height',
|
'--apple-pay-button-height',
|
||||||
`${ height }px`
|
`${ 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`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the payment button and calls `this.insertButton()` to make the button visible in the
|
* Creates the payment button and calls `this.insertButton()` to make the button visible in the
|
||||||
* correct wrapper.
|
* correct wrapper.
|
||||||
|
@ -342,12 +442,23 @@ class ApplePayButton extends PaymentButton {
|
||||||
addButton() {
|
addButton() {
|
||||||
const { color, type, language } = this.style;
|
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' );
|
const button = document.createElement( 'apple-pay-button' );
|
||||||
button.id = 'apple-' + this.wrapperId;
|
button.id = 'apple-' + this.wrapperId;
|
||||||
|
|
||||||
button.setAttribute( 'buttonstyle', color );
|
button.setAttribute( 'buttonstyle', color );
|
||||||
button.setAttribute( 'type', type );
|
button.setAttribute( 'type', type );
|
||||||
button.setAttribute( 'locale', language );
|
button.setAttribute( 'locale', language );
|
||||||
|
|
||||||
|
button.style.display = 'block';
|
||||||
|
|
||||||
button.addEventListener( 'click', ( evt ) => {
|
button.addEventListener( 'click', ( evt ) => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
this.onButtonClick();
|
this.onButtonClick();
|
||||||
|
|
|
@ -3,65 +3,83 @@ import ApplePayButton from './ApplepayButton';
|
||||||
import ContextHandlerFactory from './Context/ContextHandlerFactory';
|
import ContextHandlerFactory from './Context/ContextHandlerFactory';
|
||||||
|
|
||||||
class ApplePayManager {
|
class ApplePayManager {
|
||||||
#namespace = '';
|
constructor( namespace, buttonConfig, ppcpConfig, buttonAttributes = {} ) {
|
||||||
#buttonConfig = null;
|
this.namespace = namespace;
|
||||||
#ppcpConfig = null;
|
this.buttonConfig = buttonConfig;
|
||||||
#applePayConfig = null;
|
this.ppcpConfig = ppcpConfig;
|
||||||
#contextHandler = null;
|
this.buttonAttributes = buttonAttributes;
|
||||||
#transactionInfo = null;
|
this.applePayConfig = null;
|
||||||
#buttons = [];
|
this.transactionInfo = null;
|
||||||
|
this.contextHandler = null;
|
||||||
|
|
||||||
constructor( namespace, buttonConfig, ppcpConfig ) {
|
this.buttons = [];
|
||||||
this.#namespace = namespace;
|
|
||||||
this.#buttonConfig = buttonConfig;
|
|
||||||
this.#ppcpConfig = ppcpConfig;
|
|
||||||
|
|
||||||
this.onContextBootstrap = this.onContextBootstrap.bind( this );
|
buttonModuleWatcher.watchContextBootstrap( async ( bootstrap ) => {
|
||||||
buttonModuleWatcher.watchContextBootstrap( this.onContextBootstrap );
|
this.contextHandler = ContextHandlerFactory.create(
|
||||||
}
|
|
||||||
|
|
||||||
async onContextBootstrap( bootstrap ) {
|
|
||||||
this.#contextHandler = ContextHandlerFactory.create(
|
|
||||||
bootstrap.context,
|
bootstrap.context,
|
||||||
this.#buttonConfig,
|
buttonConfig,
|
||||||
this.#ppcpConfig,
|
ppcpConfig,
|
||||||
bootstrap.handler
|
bootstrap.handler
|
||||||
);
|
);
|
||||||
|
|
||||||
const button = ApplePayButton.createButton(
|
const button = ApplePayButton.createButton(
|
||||||
bootstrap.context,
|
bootstrap.context,
|
||||||
bootstrap.handler,
|
bootstrap.handler,
|
||||||
this.#buttonConfig,
|
buttonConfig,
|
||||||
this.#ppcpConfig,
|
ppcpConfig,
|
||||||
this.#contextHandler
|
this.contextHandler,
|
||||||
|
this.buttonAttributes
|
||||||
);
|
);
|
||||||
|
|
||||||
this.#buttons.push( button );
|
this.buttons.push( button );
|
||||||
|
const initButton = () => {
|
||||||
|
button.configure(
|
||||||
|
this.applePayConfig,
|
||||||
|
this.transactionInfo,
|
||||||
|
this.buttonAttributes
|
||||||
|
);
|
||||||
|
button.init();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize button only if applePayConfig and transactionInfo are already fetched.
|
||||||
|
if ( this.applePayConfig && this.transactionInfo ) {
|
||||||
|
initButton();
|
||||||
|
} else {
|
||||||
// Ensure ApplePayConfig is loaded before proceeding.
|
// Ensure ApplePayConfig is loaded before proceeding.
|
||||||
await this.init();
|
await this.init();
|
||||||
|
|
||||||
button.configure( this.#applePayConfig, this.#transactionInfo );
|
if ( this.applePayConfig && this.transactionInfo ) {
|
||||||
button.init();
|
initButton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
try {
|
try {
|
||||||
if ( ! this.#applePayConfig ) {
|
if ( ! this.applePayConfig ) {
|
||||||
this.#applePayConfig = await window[ this.#namespace ]
|
// Gets ApplePay configuration of the PayPal merchant.
|
||||||
|
this.applePayConfig = await window[ this.namespace ]
|
||||||
.Applepay()
|
.Applepay()
|
||||||
.config();
|
.config();
|
||||||
|
}
|
||||||
|
|
||||||
if ( ! this.#applePayConfig ) {
|
if ( ! this.transactionInfo ) {
|
||||||
|
this.transactionInfo = await this.fetchTransactionInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! this.applePayConfig ) {
|
||||||
console.error( 'No ApplePayConfig received during init' );
|
console.error( 'No ApplePayConfig received during init' );
|
||||||
}
|
} else if ( ! this.transactionInfo ) {
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! this.#transactionInfo ) {
|
|
||||||
this.#transactionInfo = await this.fetchTransactionInfo();
|
|
||||||
|
|
||||||
if ( ! this.#applePayConfig ) {
|
|
||||||
console.error( 'No transactionInfo found during init' );
|
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 ) {
|
} catch ( error ) {
|
||||||
|
@ -71,10 +89,10 @@ class ApplePayManager {
|
||||||
|
|
||||||
async fetchTransactionInfo() {
|
async fetchTransactionInfo() {
|
||||||
try {
|
try {
|
||||||
if ( ! this.#contextHandler ) {
|
if ( ! this.contextHandler ) {
|
||||||
throw new Error( 'ContextHandler is not initialized' );
|
throw new Error( 'ContextHandler is not initialized' );
|
||||||
}
|
}
|
||||||
return await this.#contextHandler.transactionInfo();
|
return await this.contextHandler.transactionInfo();
|
||||||
} catch ( error ) {
|
} catch ( error ) {
|
||||||
console.error( 'Error fetching transaction info:', error );
|
console.error( 'Error fetching transaction info:', error );
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -82,7 +100,7 @@ class ApplePayManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
reinit() {
|
reinit() {
|
||||||
for ( const button of this.#buttons ) {
|
for ( const button of this.buttons ) {
|
||||||
button.reinit();
|
button.reinit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,13 @@ const ApplePayManagerBlockEditor = ( {
|
||||||
namespace,
|
namespace,
|
||||||
buttonConfig,
|
buttonConfig,
|
||||||
ppcpConfig,
|
ppcpConfig,
|
||||||
|
buttonAttributes,
|
||||||
} ) => (
|
} ) => (
|
||||||
<ApplepayButton
|
<ApplepayButton
|
||||||
namespace={ namespace }
|
namespace={ namespace }
|
||||||
buttonConfig={ buttonConfig }
|
buttonConfig={ buttonConfig }
|
||||||
ppcpConfig={ ppcpConfig }
|
ppcpConfig={ ppcpConfig }
|
||||||
|
buttonAttributes={ buttonAttributes }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,12 @@ import usePayPalScript from '../hooks/usePayPalScript';
|
||||||
import useApplepayScript from '../hooks/useApplepayScript';
|
import useApplepayScript from '../hooks/useApplepayScript';
|
||||||
import useApplepayConfig from '../hooks/useApplepayConfig';
|
import useApplepayConfig from '../hooks/useApplepayConfig';
|
||||||
|
|
||||||
const ApplepayButton = ( { namespace, buttonConfig, ppcpConfig } ) => {
|
const ApplepayButton = ( {
|
||||||
|
namespace,
|
||||||
|
buttonConfig,
|
||||||
|
ppcpConfig,
|
||||||
|
buttonAttributes,
|
||||||
|
} ) => {
|
||||||
const [ buttonHtml, setButtonHtml ] = useState( '' );
|
const [ buttonHtml, setButtonHtml ] = useState( '' );
|
||||||
const [ buttonElement, setButtonElement ] = useState( null );
|
const [ buttonElement, setButtonElement ] = useState( null );
|
||||||
const [ componentFrame, setComponentFrame ] = useState( null );
|
const [ componentFrame, setComponentFrame ] = useState( null );
|
||||||
|
@ -31,19 +36,42 @@ const ApplepayButton = ( { namespace, buttonConfig, ppcpConfig } ) => {
|
||||||
namespace,
|
namespace,
|
||||||
buttonConfig,
|
buttonConfig,
|
||||||
ppcpConfig,
|
ppcpConfig,
|
||||||
applepayConfig
|
applepayConfig,
|
||||||
|
buttonAttributes
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
if ( applepayButton ) {
|
if ( ! applepayButton || ! buttonElement ) {
|
||||||
setButtonHtml( applepayButton.outerHTML );
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ setButtonElement }
|
ref={ setButtonElement }
|
||||||
dangerouslySetInnerHTML={ { __html: buttonHtml } }
|
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;
|
window.PayPalCommerceGateway = ppcpConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ApplePayComponent = ( { isEditing } ) => {
|
const ApplePayComponent = ( { isEditing, buttonAttributes } ) => {
|
||||||
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 );
|
||||||
|
@ -57,8 +57,13 @@ const ApplePayComponent = ( { isEditing } ) => {
|
||||||
|
|
||||||
buttonConfig.reactWrapper = wrapperRef.current;
|
buttonConfig.reactWrapper = wrapperRef.current;
|
||||||
|
|
||||||
new ManagerClass( namespace, buttonConfig, ppcpConfig );
|
new ManagerClass(
|
||||||
}, [ paypalLoaded, applePayLoaded, isEditing ] );
|
namespace,
|
||||||
|
buttonConfig,
|
||||||
|
ppcpConfig,
|
||||||
|
buttonAttributes
|
||||||
|
);
|
||||||
|
}, [ paypalLoaded, applePayLoaded, isEditing, buttonAttributes ] );
|
||||||
|
|
||||||
if ( isEditing ) {
|
if ( isEditing ) {
|
||||||
return (
|
return (
|
||||||
|
@ -66,6 +71,7 @@ const ApplePayComponent = ( { isEditing } ) => {
|
||||||
namespace={ namespace }
|
namespace={ namespace }
|
||||||
buttonConfig={ buttonConfig }
|
buttonConfig={ buttonConfig }
|
||||||
ppcpConfig={ ppcpConfig }
|
ppcpConfig={ ppcpConfig }
|
||||||
|
buttonAttributes={ buttonAttributes }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue