diff --git a/modules/ppcp-api-client/src/Endpoint/PaymentMethodTokensEndpoint.php b/modules/ppcp-api-client/src/Endpoint/PaymentMethodTokensEndpoint.php
index 538ca224d..e2c2a91e8 100644
--- a/modules/ppcp-api-client/src/Endpoint/PaymentMethodTokensEndpoint.php
+++ b/modules/ppcp-api-client/src/Endpoint/PaymentMethodTokensEndpoint.php
@@ -61,19 +61,24 @@ class PaymentMethodTokensEndpoint {
* Creates a setup token.
*
* @param PaymentSource $payment_source The payment source.
+ * @param string $customer_id PayPal customer ID.
*
* @return stdClass
*
* @throws RuntimeException When something when wrong with the request.
* @throws PayPalApiException When something when wrong setting up the token.
*/
- public function setup_tokens( PaymentSource $payment_source ): stdClass {
+ public function setup_tokens( PaymentSource $payment_source, string $customer_id = '' ): stdClass {
$data = array(
'payment_source' => array(
$payment_source->name() => $payment_source->properties(),
),
);
+ if ( $customer_id ) {
+ $data['customer']['id'] = $customer_id;
+ }
+
$bearer = $this->bearer->bearer();
$url = trailingslashit( $this->host ) . 'v3/vault/setup-tokens';
@@ -109,19 +114,24 @@ class PaymentMethodTokensEndpoint {
* Creates a payment token for the given payment source.
*
* @param PaymentSource $payment_source The payment source.
+ * @param string $customer_id PayPal customer ID.
*
* @return stdClass
*
* @throws RuntimeException When something when wrong with the request.
* @throws PayPalApiException When something when wrong setting up the token.
*/
- public function create_payment_token( PaymentSource $payment_source ): stdClass {
+ public function create_payment_token( PaymentSource $payment_source, string $customer_id = '' ): stdClass {
$data = array(
'payment_source' => array(
$payment_source->name() => $payment_source->properties(),
),
);
+ if ( $customer_id ) {
+ $data['customer']['id'] = $customer_id;
+ }
+
$bearer = $this->bearer->bearer();
$url = trailingslashit( $this->host ) . 'v3/vault/payment-tokens';
diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js
index 482557327..155ab5d7b 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,43 @@ 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;
+ 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`;
+ }
+
+ const borderRadius = attributes?.borderRadius
+ ? parseInt( attributes.borderRadius, 10 )
+ : defaultBorderRadius;
+ if ( ! isNaN( borderRadius ) ) {
+ wrapper.style.borderRadius = `${ borderRadius }px`;
}
}
@@ -342,12 +450,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,
} ) => (