Skip to content

Commit 2a894d5

Browse files
committed
fix: resolve infinite render loop and tokenization checkbox issues
- Fix infinite render loop in monei-cc-component by memoizing moneiData and config objects - Fix dependency arrays to use specific properties instead of objects - Fix tokenization checkbox validation using FILTER_VALIDATE_BOOLEAN instead of isset() - Add proper URL construction for Bizum/PayPal/Apple Pay redirect flows - Add onCheckoutSuccess handler for Apple/Google Pay component mode - Add user login check before saving payment tokens - Remove debug logging from component and redirect hooks - Reorder settings fields to group style options at bottom Resolves issues with: - React components rendering 70,000+ times - Cards being saved when checkbox unchecked - Redirect URLs missing query parameters
1 parent 8d3f062 commit 2a894d5

11 files changed

+208
-83
lines changed

assets/js/components/monei-apple-google-component.js

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const createAppleGoogleLabel = ( moneiData ) => {
3737
*/
3838
export const MoneiAppleGoogleContent = ( props ) => {
3939
const { useEffect, useRef } = wp.element;
40-
const { onPaymentSetup } = props.eventRegistration;
40+
const { onPaymentSetup, onCheckoutSuccess } = props.eventRegistration;
4141
const { activePaymentMethod } = props;
4242
const moneiData =
4343
props.moneiData ||
@@ -126,6 +126,65 @@ export const MoneiAppleGoogleContent = ( props ) => {
126126
return () => unsubscribe();
127127
}, [ onPaymentSetup ] );
128128

129+
// Setup checkout success hook
130+
useEffect( () => {
131+
const unsubscribe = onCheckoutSuccess(
132+
( { processingResponse } ) => {
133+
const { paymentDetails } = processingResponse;
134+
135+
// If no paymentId, backend handles everything (redirect flow)
136+
if ( ! paymentDetails?.paymentId ) {
137+
return false;
138+
}
139+
140+
// Component mode: confirm payment with token
141+
const paymentId = paymentDetails.paymentId;
142+
const tokenValue = paymentDetails.token;
143+
// eslint-disable-next-line no-undef
144+
monei
145+
.confirmPayment( {
146+
paymentId,
147+
paymentToken: tokenValue,
148+
} )
149+
.then( ( result ) => {
150+
if (
151+
result.nextAction &&
152+
result.nextAction.mustRedirect
153+
) {
154+
window.location.assign(
155+
result.nextAction.redirectUrl
156+
);
157+
}
158+
if ( result.status === 'FAILED' ) {
159+
const failUrl = new URL( paymentDetails.failUrl );
160+
failUrl.searchParams.set( 'status', 'FAILED' );
161+
window.location.href = failUrl.toString();
162+
} else {
163+
// Always include payment ID in redirect URL for order verification
164+
const { orderId, paymentId } = paymentDetails;
165+
const url = new URL( paymentDetails.completeUrl );
166+
url.searchParams.set( 'id', paymentId );
167+
url.searchParams.set( 'orderId', orderId );
168+
url.searchParams.set( 'status', result.status );
169+
170+
window.location.href = url.toString();
171+
}
172+
} )
173+
.catch( ( error ) => {
174+
console.error(
175+
'Error during payment confirmation:',
176+
error
177+
);
178+
window.location.href = paymentDetails.failUrl;
179+
} );
180+
181+
return true;
182+
}
183+
);
184+
185+
return () => unsubscribe();
186+
}, [ onCheckoutSuccess ] );
187+
129188
return (
130189
<fieldset className="monei-fieldset monei-payment-request-fieldset">
131190
<div

assets/js/components/monei-cc-component.js

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
useFormErrors,
55
} from '../helpers/monei-card-input-hooks';
66

7-
const { useEffect, useState, useRef, useCallback } = wp.element;
7+
const { useEffect, useState, useRef, useCallback, useMemo } = wp.element;
88

99
/**
1010
* MONEI Credit Card Content Component
@@ -15,8 +15,13 @@ export const MoneiCCContent = ( props ) => {
1515
const { responseTypes } = props.emitResponse;
1616
const { onPaymentSetup, onCheckoutValidation, onCheckoutSuccess } =
1717
props.eventRegistration;
18-
const moneiData =
19-
props.moneiData || wc.wcSettings.getSetting( 'monei_data' );
18+
19+
// Memoize moneiData to prevent infinite re-renders from wc.wcSettings.getSetting() returning new object
20+
const moneiData = useMemo(
21+
() => props.moneiData || wc.wcSettings.getSetting( 'monei_data' ),
22+
[ props.moneiData ]
23+
);
24+
2025
const isHostedWorkflow = moneiData.redirect === 'yes';
2126
const shouldSavePayment = props.shouldSavePayment;
2227
// State management
@@ -26,19 +31,35 @@ export const MoneiCCContent = ( props ) => {
2631
// Form error management
2732
const formErrors = useFormErrors();
2833

34+
// Memoize config objects to prevent infinite re-renders
35+
const cardholderNameConfig = useMemo(
36+
() => ( {
37+
errorMessage: moneiData.nameErrorString,
38+
pattern: /^[A-Za-zÀ-ú\s-]{5,50}$/,
39+
} ),
40+
[ moneiData.nameErrorString ]
41+
);
42+
43+
const cardInputConfig = useMemo(
44+
() => ( {
45+
accountId: moneiData.accountId,
46+
sessionId: moneiData.sessionId,
47+
language: moneiData.language,
48+
style: moneiData.cardInputStyle,
49+
} ),
50+
[
51+
moneiData.accountId,
52+
moneiData.sessionId,
53+
moneiData.language,
54+
moneiData.cardInputStyle,
55+
]
56+
);
57+
2958
// Cardholder name management
30-
const cardholderName = useCardholderName( {
31-
errorMessage: moneiData.nameErrorString,
32-
pattern: /^[A-Za-zÀ-ú\s-]{5,50}$/,
33-
} );
59+
const cardholderName = useCardholderName( cardholderNameConfig );
3460

3561
// Card input management
36-
const cardInput = useMoneiCardInput( {
37-
accountId: moneiData.accountId,
38-
sessionId: moneiData.sessionId,
39-
language: moneiData.language,
40-
style: moneiData.cardInputStyle,
41-
} );
62+
const cardInput = useMoneiCardInput( cardInputConfig );
4263
// If hosted workflow, show redirect message
4364
if ( isHostedWorkflow ) {
4465
return (
@@ -71,7 +92,7 @@ export const MoneiCCContent = ( props ) => {
7192
} );
7293

7394
return tokenPromiseRef.current;
74-
}, [ cardInput ] );
95+
}, [ cardInput.createToken ] );
7596

7697
/**
7798
* Validate form
@@ -94,9 +115,10 @@ export const MoneiCCContent = ( props ) => {
94115

95116
return isValid;
96117
}, [
97-
cardholderName,
118+
cardholderName.validate,
98119
cardInput.isValid,
99-
formErrors,
120+
formErrors.setError,
121+
formErrors.clearError,
100122
moneiData.cardErrorString,
101123
] );
102124

@@ -140,8 +162,11 @@ export const MoneiCCContent = ( props ) => {
140162
return unsubscribe;
141163
}, [
142164
onCheckoutValidation,
143-
cardholderName,
144-
cardInput,
165+
cardholderName.validate,
166+
cardholderName.error,
167+
cardInput.error,
168+
cardInput.isValid,
169+
cardInput.token,
145170
createPaymentToken,
146171
moneiData.cardErrorString,
147172
moneiData.tokenErrorString,
@@ -166,14 +191,21 @@ export const MoneiCCContent = ( props ) => {
166191
};
167192
}
168193

194+
const paymentData = {
195+
monei_payment_token: paymentToken,
196+
monei_cardholder_name: cardholderName.value,
197+
monei_is_block_checkout: 'yes',
198+
};
199+
200+
// Only include save payment method flag if checkbox is checked
201+
if ( shouldSavePayment ) {
202+
paymentData[ 'wc-monei-new-payment-method' ] = true;
203+
}
204+
169205
return {
170206
type: responseTypes.SUCCESS,
171207
meta: {
172-
paymentMethodData: {
173-
monei_payment_token: paymentToken,
174-
monei_cardholder_name: cardholderName.value,
175-
monei_is_block_checkout: 'yes',
176-
},
208+
paymentMethodData: paymentData,
177209
},
178210
};
179211
} finally {

assets/js/monei-block-checkout-bizum.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,10 +271,25 @@
271271
);
272272
}
273273
if ( result.status === 'FAILED' ) {
274-
window.location.href = `${ paymentDetails.failUrl }&status=FAILED`;
274+
const failUrl = new URL(
275+
paymentDetails.failUrl
276+
);
277+
failUrl.searchParams.set( 'status', 'FAILED' );
278+
window.location.href = failUrl.toString();
275279
} else {
276-
window.location.href =
277-
paymentDetails.completeUrl;
280+
// Always include payment ID in redirect URL for order verification
281+
const { orderId, paymentId } = paymentDetails;
282+
const url = new URL(
283+
paymentDetails.completeUrl
284+
);
285+
url.searchParams.set( 'id', paymentId );
286+
url.searchParams.set( 'orderId', orderId );
287+
url.searchParams.set(
288+
'status',
289+
result.status
290+
);
291+
292+
window.location.href = url.toString();
278293
}
279294
} )
280295
.catch( ( error ) => {

assets/js/monei-block-checkout-paypal.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,25 @@
169169
);
170170
}
171171
if ( result.status === 'FAILED' ) {
172-
window.location.href = `${ paymentDetails.failUrl }&status=FAILED`;
172+
const failUrl = new URL(
173+
paymentDetails.failUrl
174+
);
175+
failUrl.searchParams.set( 'status', 'FAILED' );
176+
window.location.href = failUrl.toString();
173177
} else {
174-
window.location.href =
175-
paymentDetails.completeUrl;
178+
// Always include payment ID in redirect URL for order verification
179+
const { orderId, paymentId } = paymentDetails;
180+
const url = new URL(
181+
paymentDetails.completeUrl
182+
);
183+
url.searchParams.set( 'id', paymentId );
184+
url.searchParams.set( 'orderId', orderId );
185+
url.searchParams.set(
186+
'status',
187+
result.status
188+
);
189+
190+
window.location.href = url.toString();
176191
}
177192
} )
178193
.catch( ( error ) => {

includes/admin/monei-apple-google-settings.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,6 @@
3131
'label' => __( 'Enable Apple Pay and Google Pay by MONEI', 'monei' ),
3232
'default' => 'no',
3333
),
34-
'payment_request_style' => array(
35-
'title' => __( 'Apple Pay / Google Pay Style', 'monei' ),
36-
'type' => 'textarea',
37-
'description' => __( 'Configure in JSON format the style of the Apple Pay / Google Pay component. Documentation: ', 'monei' ) . '<a href="https://docs.monei.com/docs/monei-js/reference/#paymentrequest-options" target="_blank">MONEI Payment Request Style</a>',
38-
'default' => '{"height": "50px"}',
39-
'css' => 'min-height: 80px;',
40-
),
4134
'title' => array(
4235
'title' => __( 'Title', 'monei' ),
4336
'type' => 'text',
@@ -61,5 +54,12 @@
6154
'description' => __( 'Hide payment method logo in the checkout.', 'monei' ),
6255
'desc_tip' => true,
6356
),
57+
'payment_request_style' => array(
58+
'title' => __( 'Apple Pay / Google Pay Style', 'monei' ),
59+
'type' => 'textarea',
60+
'description' => __( 'Configure in JSON format the style of the Apple Pay / Google Pay component. Documentation: ', 'monei' ) . '<a href="https://docs.monei.com/docs/monei-js/reference/#paymentrequest-options" target="_blank">MONEI Payment Request Style</a>',
61+
'default' => '{"height": "50px"}',
62+
'css' => 'min-height: 80px;',
63+
),
6464
)
6565
);

includes/admin/monei-bizum-settings.php

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,6 @@
3838
'default' => 'no',
3939
'description' => sprintf( __( 'If disabled the Bizum button will be rendered directly on the checkout page. It is recommended to enable redirection in cases where Bizum payments do not function correctly.', 'monei' ) ),
4040
),
41-
'description' => array(
42-
'title' => __( 'Description', 'monei' ),
43-
'type' => 'textarea',
44-
'description' => __( 'This description is only displayed when using redirect mode. It will be shown to customers before they are redirected to the payment page.', 'monei' ),
45-
'default' => __( 'You will be redirected to Bizum to complete the payment. Powered by MONEI.', 'monei' ),
46-
'class' => 'monei-bizum-description-field',
47-
),
48-
'bizum_style' => array(
49-
'title' => __( 'Bizum Style', 'monei' ),
50-
'type' => 'textarea',
51-
'description' => __( 'Configure in JSON format the style of the Bizum component. Documentation: ', 'monei' ) . '<a href="https://docs.monei.com/docs/monei-js/reference/#bizum-options" target="_blank">MONEI Bizum Style</a>',
52-
'default' => '{"height": "50px"}',
53-
'css' => 'min-height: 80px;',
54-
),
5541
'title' => array(
5642
'title' => __( 'Title', 'monei' ),
5743
'type' => 'text',
@@ -75,5 +61,19 @@
7561
'description' => __( 'Hide payment method logo in the checkout.', 'monei' ),
7662
'desc_tip' => true,
7763
),
64+
'description' => array(
65+
'title' => __( 'Description', 'monei' ),
66+
'type' => 'textarea',
67+
'description' => __( 'This description is only displayed when using redirect mode. It will be shown to customers before they are redirected to the payment page.', 'monei' ),
68+
'default' => __( 'You will be redirected to Bizum to complete the payment. Powered by MONEI.', 'monei' ),
69+
'class' => 'monei-bizum-description-field',
70+
),
71+
'bizum_style' => array(
72+
'title' => __( 'Bizum Style', 'monei' ),
73+
'type' => 'textarea',
74+
'description' => __( 'Configure in JSON format the style of the Bizum component. Documentation: ', 'monei' ) . '<a href="https://docs.monei.com/docs/monei-js/reference/#bizum-options" target="_blank">MONEI Bizum Style</a>',
75+
'default' => '{"height": "50px"}',
76+
'css' => 'min-height: 80px;',
77+
),
7878
)
7979
);

includes/admin/monei-cc-settings.php

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,6 @@
3838
'default' => 'no',
3939
'description' => sprintf( __( 'If disabled the credit card input will be rendered directly on the checkout page.', 'monei' ) ),
4040
),
41-
'description' => array(
42-
'title' => __( 'Description', 'monei' ),
43-
'type' => 'textarea',
44-
'description' => __( 'This description is only displayed when using redirect mode. It will be shown to customers before they are redirected to the payment page.', 'monei' ),
45-
'default' => __( 'You will be redirected to the payment page to complete the payment. Powered by MONEI.', 'monei' ),
46-
'class' => 'monei-cc-description-field',
47-
),
48-
'card_input_style' => array(
49-
'title' => __( 'Card Input Style', 'monei' ),
50-
'type' => 'textarea',
51-
'description' => __( 'Configure in JSON format the style of the Card Input component. Documentation: ', 'monei' ) . '<a href="https://docs.monei.com/docs/monei-js/reference/#cardinput-style-object" target="_blank">MONEI Card Input Style</a>',
52-
'default' => '{"base": {"height": "50px"}, "input": {"background": "none"}}',
53-
'css' => 'min-height: 80px;',
54-
),
5541
'title' => array(
5642
'title' => __( 'Title', 'monei' ),
5743
'type' => 'text',
@@ -75,6 +61,20 @@
7561
'description' => __( 'Hide payment method logo in the checkout.', 'monei' ),
7662
'desc_tip' => true,
7763
),
64+
'description' => array(
65+
'title' => __( 'Description', 'monei' ),
66+
'type' => 'textarea',
67+
'description' => __( 'This description is only displayed when using redirect mode. It will be shown to customers before they are redirected to the payment page.', 'monei' ),
68+
'default' => __( 'You will be redirected to the payment page to complete the payment. Powered by MONEI.', 'monei' ),
69+
'class' => 'monei-cc-description-field',
70+
),
71+
'card_input_style' => array(
72+
'title' => __( 'Card Input Style', 'monei' ),
73+
'type' => 'textarea',
74+
'description' => __( 'Configure in JSON format the style of the Card Input component. Documentation: ', 'monei' ) . '<a href="https://docs.monei.com/docs/monei-js/reference/#cardinput-style-object" target="_blank">MONEI Card Input Style</a>',
75+
'default' => '{"base": {"height": "50px"}, "input": {"background": "none"}}',
76+
'css' => 'min-height: 80px;',
77+
),
7878
'tokenization' => array(
7979
'title' => __( 'Saved cards', 'monei' ),
8080
'type' => 'checkbox',

0 commit comments

Comments
 (0)