Closed danielshawellis closed 1 month ago
Here's a breakdown of the source code function calls that lead to this error:
client/blocks/upe/index.js
script calls the getDeferredIntentCreationUPEFields
function while registering a checkout method to WooCommerce blocks (here are docs for the registerPaymentMethod): https://github.com/woocommerce/woocommerce-gateway-stripe/blob/f89dee1338f2c647664fa51fd7180d688fccc745/client/blocks/upe/index.js#L42-L87getDeferredIntentCreationUPEFields
returns the PaymentElements
component to the WooCommerce block registration: https://github.com/woocommerce/woocommerce-gateway-stripe/blob/f89dee1338f2c647664fa51fd7180d688fccc745/client/blocks/upe/upe-deferred-intent-creation/payment-elements.js#L51-L81PaymentElements
React component calls the initializeUPEAppearance
function: https://github.com/woocommerce/woocommerce-gateway-stripe/blob/f89dee1338f2c647664fa51fd7180d688fccc745/client/blocks/upe/upe-deferred-intent-creation/payment-elements.js#L13-L49initializeUPEAppearance
function calls the getStripeServerData
function: https://github.com/woocommerce/woocommerce-gateway-stripe/blob/f89dee1338f2c647664fa51fd7180d688fccc745/client/stripe-utils/utils.js#L461-L485getStripeServerData
function assumes that the wc_stripe_upe_params
variable will be available in the global scope and fails when it is not defined: https://github.com/woocommerce/woocommerce-gateway-stripe/blob/f89dee1338f2c647664fa51fd7180d688fccc745/client/stripe-utils/utils.js#L18-L31This problem can be fixed by configuring the /client/blocks/upe/index.js
script to wait for window.wc_stripe_upe_params
to be defined before registering payment methods.
This is messy, but it may be a step towards a better approach.
Here's a commit demonstrating these edits: https://github.com/danielshawellis/woocommerce-gateway-stripe/commit/6233201a8df1ba6bd92ea89126103216ffa0582c
And here's the code:
const registerPaymentMethods = () => {
const upeMethods = getPaymentMethodsConstants();
Object.entries( getBlocksConfiguration()?.paymentMethodsConfig )
.filter( ( [ upeName ] ) => upeName !== 'link' )
.filter( ( [ upeName ] ) => upeName !== 'giropay' ) // Skip giropay as it was deprecated by Jun, 30th 2024.
.forEach( ( [ upeName, upeConfig ] ) => {
let iconName = upeName;
// Afterpay/Clearpay have different icons for UK merchants.
if ( upeName === 'afterpay_clearpay' ) {
iconName =
getBlocksConfiguration()?.accountCountry === 'GB'
? 'clearpay'
: 'afterpay';
}
const Icon = Icons[ iconName ];
registerPaymentMethod( {
name: upeMethods[ upeName ],
content: getDeferredIntentCreationUPEFields(
upeName,
upeMethods,
api,
upeConfig.description,
upeConfig.testingInstructions,
upeConfig.showSaveOption ?? false
),
edit: getDeferredIntentCreationUPEFields(
upeName,
upeMethods,
api,
upeConfig.description,
upeConfig.testingInstructions,
upeConfig.showSaveOption ?? false
),
savedTokenComponent: <SavedTokenHandler api={ api } />,
canMakePayment: ( cartData ) => {
const billingCountry = cartData.billingAddress.country;
const isRestrictedInAnyCountry = !! upeConfig.countries.length;
const isAvailableInTheCountry =
! isRestrictedInAnyCountry ||
upeConfig.countries.includes( billingCountry );
return isAvailableInTheCountry && !! api.getStripe();
},
// see .wc-block-checkout__payment-method styles in blocks/style.scss
label: (
<>
<span>
{ upeConfig.title }
<Icon alt={ upeConfig.title } />
</span>
</>
),
ariaLabel: 'Stripe',
supports: {
// Use `false` as fallback values in case server provided configuration is missing.
showSavedCards:
getBlocksConfiguration()?.showSavedCards ?? false,
showSaveOption: upeConfig.showSaveOption ?? false,
features: getBlocksConfiguration()?.supports ?? [],
},
} );
} );
};
// If this script is running in the customizer preview pane, wait for wc_stripe_upe_params to be defined on the window before registering payment methods
if ( wp.customize ) {
const upeParamsReady = new Promise( ( resolve ) => {
const interval = setInterval( () => {
if ( window.wc_stripe_upe_params !== undefined ) {
clearInterval( interval );
resolve();
}
}, 10 );
} );
upeParamsReady.then( registerPaymentMethods );
} else {
registerPaymentMethods();
}
A cleaner approach is to wait for wc_stripe_upe_params
to be defined on the window before rendering the payment elements in the /client/blocks/upe/upe-deferred-intent-creation/payment-elements.js
file.
Here's a commit demonstrating these edits: https://github.com/danielshawellis/woocommerce-gateway-stripe/commit/9da0f95d8b42000d1558d430e616994dc9ba0606
And here's the code:
/**
* Renders a Stripe Payment elements component.
*
* @param {*} props Additional props for payment processing.
* @param {WCStripeAPI} props.api Object containing methods for interacting with Stripe.
* @param {string} props.paymentMethodId The ID of the payment method.
*
* @return {JSX.Element} Rendered Payment elements.
*/
const PaymentElements = ( { api, ...props } ) => {
// If this script is running in the customizer preview pane, wait for wc_stripe_upe_params to be defined on the window before rendering
const customizerIsRunning = wp.customize !== undefined;
const [ upeParamsReady, setUpeParamsReady ] = useState(
! customizerIsRunning
);
useEffect( () => {
if ( ! customizerIsRunning ) return;
const ready = new Promise( ( resolve ) => {
const interval = setInterval( () => {
if ( window.wc_stripe_upe_params !== undefined ) {
clearInterval( interval );
resolve();
}
}, 10 );
} );
ready.then( () => setUpeParamsReady( true ) );
}, [ customizerIsRunning ] );
if ( ! upeParamsReady ) return <div />;
const stripe = api.getStripe();
const amount = Number( getBlocksConfiguration()?.cartTotal );
const currency = getBlocksConfiguration()?.currency.toLowerCase();
const appearance = initializeUPEAppearance( api, 'true' );
const options = {
mode: amount < 1 ? 'setup' : 'payment',
amount,
currency,
paymentMethodCreation: 'manual',
paymentMethodTypes: getPaymentMethodTypes( props.paymentMethodId ),
appearance,
};
// If the cart contains a subscription or the payment method supports saving, we need to use off_session setup so Stripe can display appropriate terms and conditions.
if (
getBlocksConfiguration()?.cartContainsSubscription ||
props.showSaveOption
) {
options.setupFutureUsage = 'off_session';
}
return (
<Elements stripe={ stripe } options={ options }>
<PaymentProcessor api={ api } { ...props } />
</Elements>
);
};
Thank you @Mayisha for solving this in #3387!
Describe the bug Whenever a setting with
'transport' => 'refresh'
is changed in the WordPress customizer, WordPress will reload the entire preview iframe. When this happens, the upe_blocks.js script throws the following error:When this happens, the checkout fields will fail to render in the preview iframe:
Here's a video of the issue for clarity:
full-issue-demonstration.webm
To Reproduce Steps to reproduce the behavior:
'transport' => 'refresh'
. Here's the plugin that I used for testing:if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. }
function simple_customizer_checkbox_register( $wp_customize ) { // Add a section to the Customizer $wp_customize->add_section( 'simple_checkbox_section', array( 'title' => __( 'Simple Checkbox Section', 'simple_customizer_checkbox' ), 'priority' => 30, ) );
}
add_action( 'customize_register', 'simple_customizer_checkbox_register' );