From 8b0e12c06d4be992b0ac4e99b3b29872637d382f Mon Sep 17 00:00:00 2001
From: Philipp Stracker <p.stracker@syde.com>
Date: Mon, 30 Sep 2024 18:29:02 +0200
Subject: [PATCH 1/9] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Minor=20code=20cleanup?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- Define class properties
- Change visibility of properties
- Add some comments
---
 .../resources/js/Components/FormFieldGroup.js | 71 ++++++++++++-------
 1 file changed, 46 insertions(+), 25 deletions(-)

diff --git a/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js b/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
index b4318a43d..77ca2317b 100644
--- a/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
+++ b/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
@@ -1,30 +1,36 @@
 class FormFieldGroup {
-	constructor( config ) {
-		this.data = {};
+	#stored;
+	#data = {};
+	#active = false;
+	#baseSelector;
+	#contentSelector;
+	#fields = {};
+	#template;
 
-		this.baseSelector = config.baseSelector;
-		this.contentSelector = config.contentSelector;
-		this.fields = config.fields || {};
-		this.template = config.template;
+	constructor( config ) {
+		this.#baseSelector = config.baseSelector;
+		this.#contentSelector = config.contentSelector;
+		this.#fields = config.fields || {};
+		this.#template = config.template;
+		this.#stored = new Map();
 
-		this.active = false;
 	}
 
 	setData( data ) {
-		this.data = data;
+		this.#data = data;
 		this.refresh();
 	}
 
 	dataValue( fieldKey ) {
-		if ( ! fieldKey || ! this.fields[ fieldKey ] ) {
+		if ( ! fieldKey || ! this.#fields[ fieldKey ] ) {
 			return '';
 		}
 
-		if ( typeof this.fields[ fieldKey ].valueCallback === 'function' ) {
-			return this.fields[ fieldKey ].valueCallback( this.data );
+		if ( typeof this.#fields[ fieldKey ].valueCallback === 'function' ) {
+			return this.#fields[ fieldKey ].valueCallback( this.#data );
 		}
 
-		const path = this.fields[ fieldKey ].valuePath;
+		const path = this.#fields[ fieldKey ].valuePath;
 
 		if ( ! path ) {
 			return '';
@@ -35,27 +41,42 @@ class FormFieldGroup {
 			.reduce(
 				( acc, key ) =>
 					acc && acc[ key ] !== undefined ? acc[ key ] : undefined,
-				this.data
+				this.#data
 			);
 		return value ? value : '';
 	}
 
+	/**
+	 * Activate form group: Render a custom Fastlane UI to replace the WooCommerce form.
+	 *
+	 * Indicates: Ryan flow.
+	 */
 	activate() {
-		this.active = true;
+		this.#active = true;
 		this.refresh();
 	}
 
+	/**
+	 * Deactivate form group: Remove the custom Fastlane UI - either display the default
+	 * WooCommerce checkout form or no form at all (when no email was provided yet).
+	 *
+	 * Indicates: Gary flow / no email provided / not using Fastlane.
+	 */
 	deactivate() {
-		this.active = false;
+		this.#active = false;
 		this.refresh();
 	}
 
 	toggle() {
-		this.active ? this.deactivate() : this.activate();
+		if ( this.#active ) {
+			this.deactivate();
+		} else {
+			this.activate();
+		}
 	}
 
 	refresh() {
-		const content = document.querySelector( this.contentSelector );
+		const content = document.querySelector( this.#contentSelector );
 
 		if ( ! content ) {
 			return;
@@ -63,10 +84,10 @@ class FormFieldGroup {
 
 		content.innerHTML = '';
 
-		if ( ! this.active ) {
-			this.hideField( this.contentSelector );
+		if ( ! this.#active ) {
+			this.hideField( this.#contentSelector );
 		} else {
-			this.showField( this.contentSelector );
+			this.showField( this.#contentSelector );
 		}
 
 		Object.keys( this.fields ).forEach( ( key ) => {
@@ -79,8 +100,8 @@ class FormFieldGroup {
 			}
 		} );
 
-		if ( typeof this.template === 'function' ) {
-			content.innerHTML = this.template( {
+		if ( typeof this.#template === 'function' ) {
+			content.innerHTML = this.#template( {
 				value: ( fieldKey ) => {
 					return this.dataValue( fieldKey );
 				},
@@ -100,7 +121,7 @@ class FormFieldGroup {
 
 	showField( selector ) {
 		const field = document.querySelector(
-			this.baseSelector + ' ' + selector
+			this.#baseSelector + ' ' + selector
 		);
 		if ( field ) {
 			field.classList.remove( 'ppcp-axo-field-hidden' );
@@ -109,7 +130,7 @@ class FormFieldGroup {
 
 	hideField( selector ) {
 		const field = document.querySelector(
-			this.baseSelector + ' ' + selector
+			this.#baseSelector + ' ' + selector
 		);
 		if ( field ) {
 			field.classList.add( 'ppcp-axo-field-hidden' );
@@ -117,7 +138,7 @@ class FormFieldGroup {
 	}
 
 	inputElement( name ) {
-		const baseSelector = this.fields[ name ].selector;
+		const baseSelector = this.#fields[ name ].selector;
 
 		const select = document.querySelector( baseSelector + ' select' );
 		if ( select ) {

From 89fa0f1fa7768a343b435563eef3e55570df783c Mon Sep 17 00:00:00 2001
From: Philipp Stracker <p.stracker@syde.com>
Date: Mon, 30 Sep 2024 18:57:00 +0200
Subject: [PATCH 2/9] =?UTF-8?q?=E2=9C=A8=20New=20utility=20function=20to?=
 =?UTF-8?q?=20loop=20all=20group-fields?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../resources/js/Components/FormFieldGroup.js | 37 ++++++++++++++-----
 1 file changed, 27 insertions(+), 10 deletions(-)

diff --git a/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js b/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
index 77ca2317b..e7394e731 100644
--- a/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
+++ b/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
@@ -90,13 +90,11 @@ class FormFieldGroup {
 			this.showField( this.#contentSelector );
 		}
 
-		Object.keys( this.fields ).forEach( ( key ) => {
-			const field = this.fields[ key ];
-
-			if ( this.active && ! field.showInput ) {
-				this.hideField( field.selector );
+		this.loopFields( ( { selector } ) => {
+			if ( this.#active /* && ! field.showInput */ ) {
+				this.hideField( selector );
 			} else {
-				this.showField( field.selector );
+				this.showField( selector );
 			}
 		} );
 
@@ -107,18 +105,39 @@ class FormFieldGroup {
 				},
 				isEmpty: () => {
 					let isEmpty = true;
-					Object.keys( this.fields ).forEach( ( fieldKey ) => {
+
+					this.loopFields( ( field, fieldKey ) => {
 						if ( this.dataValue( fieldKey ) ) {
 							isEmpty = false;
 							return false;
 						}
 					} );
+
 					return isEmpty;
 				},
 			} );
 		}
 	}
 
+	/**
+	 * Invoke a callback on every field in the current group.
+	 *
+	 * @param {(field: object, key: string) => void} callback
+	 */
+	loopFields( callback ) {
+		Object.keys( this.#fields ).forEach( ( key ) => {
+			const field = this.#fields[ key ];
+			const fieldSelector = `${ this.#baseSelector } ${ field.selector }`;
+
+			callback(
+				{
+					...field,
+				},
+				key
+			);
+		} );
+	}
+
 	showField( selector ) {
 		const field = document.querySelector(
 			this.#baseSelector + ' ' + selector
@@ -159,9 +178,7 @@ class FormFieldGroup {
 	}
 
 	toSubmitData( data ) {
-		Object.keys( this.fields ).forEach( ( fieldKey ) => {
-			const field = this.fields[ fieldKey ];
-
+		this.loopFields( ( field, fieldKey ) => {
 			if ( ! field.valuePath || ! field.selector ) {
 				return true;
 			}

From 0abb2e0f8dc16df6f175b185a5d3f997cfaa4504 Mon Sep 17 00:00:00 2001
From: Philipp Stracker <p.stracker@syde.com>
Date: Mon, 30 Sep 2024 18:59:13 +0200
Subject: [PATCH 3/9] =?UTF-8?q?=E2=9C=A8=20Store=20&=20restore=20the=20Woo?=
 =?UTF-8?q?=20form=20as=20needed?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When Ryan flow starts, the values of the Woo-form are saved in an internal Map, and restored once the Ryan flow is disabled again (change email or select other payment gateway)
---
 .../resources/js/Components/FormFieldGroup.js | 55 +++++++++++++++++++
 .../resources/js/Views/BillingView.js         | 10 ++++
 .../resources/js/Views/ShippingView.js        | 10 ++++
 3 files changed, 75 insertions(+)

diff --git a/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js b/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
index e7394e731..ec7f69736 100644
--- a/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
+++ b/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
@@ -53,6 +53,7 @@ class FormFieldGroup {
 	 */
 	activate() {
 		this.#active = true;
+		this.storeFormData();
 		this.refresh();
 	}
 
@@ -64,6 +65,7 @@ class FormFieldGroup {
 	 */
 	deactivate() {
 		this.#active = false;
+		this.restoreFormData();
 		this.refresh();
 	}
 
@@ -131,6 +133,9 @@ class FormFieldGroup {
 
 			callback(
 				{
+					inputSelector: field.inputName
+						? `${ fieldSelector } [name="${ field.inputName }"]`
+						: '',
 					...field,
 				},
 				key
@@ -138,6 +143,56 @@ class FormFieldGroup {
 		} );
 	}
 
+	/**
+	 * Stores the current form data in an internal storage.
+	 * This allows the original form to be restored later.
+	 */
+	storeFormData() {
+		const storeValue = ( field, name ) => {
+			if ( 'checkbox' === field.type || 'radio' === field.type ) {
+				this.#stored.set( name, field.checked );
+			} else {
+				this.#stored.set( name, field.value );
+			}
+		};
+
+		this.loopFields( ( { inputSelector }, fieldKey ) => {
+			if ( inputSelector && ! this.#stored.has( fieldKey ) ) {
+				const elInput = document.querySelector( inputSelector );
+
+				if ( elInput ) {
+					storeValue( elInput, fieldKey );
+				}
+			}
+		} );
+	}
+
+	/**
+	 * Restores the form data to its initial state before the form group was activated.
+	 * This function iterates through the stored form fields and resets their values or states.
+	 */
+	restoreFormData() {
+		const restoreValue = ( field, name ) => {
+			if ( 'checkbox' === field.type || 'radio' === field.type ) {
+				field.checked = this.#stored.get( name );
+			} else {
+				field.value = this.#stored.get( name );
+			}
+		};
+
+		this.loopFields( ( { inputSelector }, fieldKey ) => {
+			if ( inputSelector && this.#stored.has( fieldKey ) ) {
+				const elInput = document.querySelector( inputSelector );
+
+				if ( elInput ) {
+					restoreValue( elInput, fieldKey );
+				}
+
+				this.#stored.delete( fieldKey );
+			}
+		} );
+	}
+
 	showField( selector ) {
 		const field = document.querySelector(
 			this.#baseSelector + ' ' + selector
diff --git a/modules/ppcp-axo/resources/js/Views/BillingView.js b/modules/ppcp-axo/resources/js/Views/BillingView.js
index c9047f417..7f62f7d64 100644
--- a/modules/ppcp-axo/resources/js/Views/BillingView.js
+++ b/modules/ppcp-axo/resources/js/Views/BillingView.js
@@ -45,42 +45,52 @@ class BillingView {
 				firstName: {
 					selector: '#billing_first_name_field',
 					valuePath: null,
+					inputName: 'billing_first_name',
 				},
 				lastName: {
 					selector: '#billing_last_name_field',
 					valuePath: null,
+					inputName: 'billing_last_name',
 				},
 				street1: {
 					selector: '#billing_address_1_field',
 					valuePath: 'billing.address.addressLine1',
+					inputName: 'billing_address_1',
 				},
 				street2: {
 					selector: '#billing_address_2_field',
 					valuePath: null,
+					inputName: 'billing_address_2',
 				},
 				postCode: {
 					selector: '#billing_postcode_field',
 					valuePath: 'billing.address.postalCode',
+					inputName: 'billing_postcode',
 				},
 				city: {
 					selector: '#billing_city_field',
 					valuePath: 'billing.address.adminArea2',
+					inputName: 'billing_city',
 				},
 				stateCode: {
 					selector: '#billing_state_field',
 					valuePath: 'billing.address.adminArea1',
+					inputName: 'billing_state',
 				},
 				countryCode: {
 					selector: '#billing_country_field',
 					valuePath: 'billing.address.countryCode',
+					inputName: 'billing_country',
 				},
 				company: {
 					selector: '#billing_company_field',
 					valuePath: null,
+					inputName: 'billing_company',
 				},
 				phone: {
 					selector: '#billing_phone_field',
 					valuePath: 'billing.phoneNumber',
+					inputName: 'billing_phone',
 				},
 			},
 		} );
diff --git a/modules/ppcp-axo/resources/js/Views/ShippingView.js b/modules/ppcp-axo/resources/js/Views/ShippingView.js
index ba7ffb408..c656a010e 100644
--- a/modules/ppcp-axo/resources/js/Views/ShippingView.js
+++ b/modules/ppcp-axo/resources/js/Views/ShippingView.js
@@ -90,42 +90,52 @@ class ShippingView {
 					key: 'firstName',
 					selector: '#shipping_first_name_field',
 					valuePath: 'shipping.name.firstName',
+					inputName: 'shipping_first_name',
 				},
 				lastName: {
 					selector: '#shipping_last_name_field',
 					valuePath: 'shipping.name.lastName',
+					inputName: 'shipping_last_name',
 				},
 				street1: {
 					selector: '#shipping_address_1_field',
 					valuePath: 'shipping.address.addressLine1',
+					inputName: 'shipping_address_1',
 				},
 				street2: {
 					selector: '#shipping_address_2_field',
 					valuePath: null,
+					inputName: 'shipping_address_2',
 				},
 				postCode: {
 					selector: '#shipping_postcode_field',
 					valuePath: 'shipping.address.postalCode',
+					inputName: 'shipping_postcode',
 				},
 				city: {
 					selector: '#shipping_city_field',
 					valuePath: 'shipping.address.adminArea2',
+					inputName: 'shipping_city',
 				},
 				stateCode: {
 					selector: '#shipping_state_field',
 					valuePath: 'shipping.address.adminArea1',
+					inputName: 'shipping_state',
 				},
 				countryCode: {
 					selector: '#shipping_country_field',
 					valuePath: 'shipping.address.countryCode',
+					inputName: 'shipping_country',
 				},
 				company: {
 					selector: '#shipping_company_field',
 					valuePath: null,
+					inputName: 'shipping_company',
 				},
 				shipDifferentAddress: {
 					selector: '#ship-to-different-address',
 					valuePath: null,
+					inputName: 'ship_to_different_address',
 				},
 				phone: {
 					//'selector': '#billing_phone_field', // There is no shipping phone field.

From 9e03c503acfcea5c1ccabc2a99287c18432450fa Mon Sep 17 00:00:00 2001
From: Philipp Stracker <p.stracker@syde.com>
Date: Tue, 1 Oct 2024 16:16:53 +0200
Subject: [PATCH 4/9] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Extract=20common=20log?=
 =?UTF-8?q?ic=20into=20a=20class-method?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../resources/js/Components/FormFieldGroup.js | 35 ++++++++++++-------
 1 file changed, 22 insertions(+), 13 deletions(-)

diff --git a/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js b/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
index ec7f69736..b963f2637 100644
--- a/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
+++ b/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
@@ -13,7 +13,6 @@ class FormFieldGroup {
 		this.#fields = config.fields || {};
 		this.#template = config.template;
 		this.#stored = new Map();
-
 	}
 
 	setData( data ) {
@@ -46,6 +45,25 @@ class FormFieldGroup {
 		return value ? value : '';
 	}
 
+	/**
+	 * Changes the value of the input field.
+	 *
+	 * @param {Element|null}   field
+	 * @param {string|boolean} value
+	 */
+	#setFieldValue( field, value ) {
+		if ( ! field ) {
+			return false;
+		}
+
+		if ( 'checkbox' === field.type || 'radio' === field.type ) {
+			value = !! value;
+			field.checked = value;
+		} else {
+			field.value = value;
+		}
+	}
+
 	/**
 	 * Activate form group: Render a custom Fastlane UI to replace the WooCommerce form.
 	 *
@@ -151,8 +169,10 @@ class FormFieldGroup {
 		const storeValue = ( field, name ) => {
 			if ( 'checkbox' === field.type || 'radio' === field.type ) {
 				this.#stored.set( name, field.checked );
+				this.#setFieldValue( field, this.dataValue( name ) );
 			} else {
 				this.#stored.set( name, field.value );
+				this.#setFieldValue( field, '' );
 			}
 		};
 
@@ -172,22 +192,11 @@ class FormFieldGroup {
 	 * This function iterates through the stored form fields and resets their values or states.
 	 */
 	restoreFormData() {
-		const restoreValue = ( field, name ) => {
-			if ( 'checkbox' === field.type || 'radio' === field.type ) {
-				field.checked = this.#stored.get( name );
-			} else {
-				field.value = this.#stored.get( name );
-			}
-		};
-
 		this.loopFields( ( { inputSelector }, fieldKey ) => {
 			if ( inputSelector && this.#stored.has( fieldKey ) ) {
 				const elInput = document.querySelector( inputSelector );
 
-				if ( elInput ) {
-					restoreValue( elInput, fieldKey );
-				}
-
+				this.#setFieldValue( elInput, this.#stored.get( fieldKey ) );
 				this.#stored.delete( fieldKey );
 			}
 		} );

From 1e645a64e20441a908004b73847548664b336776 Mon Sep 17 00:00:00 2001
From: Philipp Stracker <p.stracker@syde.com>
Date: Tue, 1 Oct 2024 16:20:37 +0200
Subject: [PATCH 5/9] =?UTF-8?q?=E2=9C=A8=20Sync=20Fastlane=20data=20with?=
 =?UTF-8?q?=20Woo=20cart?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Main change of this branch. Tell WooCommerce about the shipping address used by Fastlane, so taxes and shipping options are correct
---
 .../resources/js/Components/FormFieldGroup.js | 33 +++++++++++++++++++
 .../resources/js/Views/ShippingView.js        |  4 +++
 2 files changed, 37 insertions(+)

diff --git a/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js b/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
index b963f2637..b94932dd2 100644
--- a/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
+++ b/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
@@ -50,18 +50,24 @@ class FormFieldGroup {
 	 *
 	 * @param {Element|null}   field
 	 * @param {string|boolean} value
+	 * @return {boolean} True indicates that the previous value was different from the new value.
 	 */
 	#setFieldValue( field, value ) {
 		if ( ! field ) {
 			return false;
 		}
+		let oldVal;
 
 		if ( 'checkbox' === field.type || 'radio' === field.type ) {
 			value = !! value;
+			oldVal = field.checked;
 			field.checked = value;
 		} else {
+			oldVal = field.value;
 			field.value = value;
 		}
+
+		return oldVal !== value;
 	}
 
 	/**
@@ -202,6 +208,33 @@ class FormFieldGroup {
 		} );
 	}
 
+	/**
+	 * Syncs the internal field-data with the hidden checkout form fields.
+	 */
+	syncDataToForm() {
+		if ( ! this.#active ) {
+			return;
+		}
+
+		let formHasChanged = false;
+
+		// Push data to the (hidden) checkout form.
+		this.loopFields( ( { inputSelector }, fieldKey ) => {
+			const elInput = inputSelector
+				? document.querySelector( inputSelector )
+				: null;
+
+			if ( this.#setFieldValue( elInput, this.dataValue( fieldKey ) ) ) {
+				formHasChanged = true;
+			}
+		} );
+
+		// Tell WooCommerce about the changes.
+		if ( formHasChanged ) {
+			document.body.dispatchEvent( new Event( 'update_checkout' ) );
+		}
+	}
+
 	showField( selector ) {
 		const field = document.querySelector(
 			this.#baseSelector + ' ' + selector
diff --git a/modules/ppcp-axo/resources/js/Views/ShippingView.js b/modules/ppcp-axo/resources/js/Views/ShippingView.js
index c656a010e..4853659b7 100644
--- a/modules/ppcp-axo/resources/js/Views/ShippingView.js
+++ b/modules/ppcp-axo/resources/js/Views/ShippingView.js
@@ -136,6 +136,8 @@ class ShippingView {
 					selector: '#ship-to-different-address',
 					valuePath: null,
 					inputName: 'ship_to_different_address',
+					// Used by Woo to ensure correct location for taxes & shipping cost.
+					valueCallback: () => true,
 				},
 				phone: {
 					//'selector': '#billing_phone_field', // There is no shipping phone field.
@@ -173,6 +175,7 @@ class ShippingView {
 
 	activate() {
 		this.group.activate();
+		this.group.syncDataToForm();
 	}
 
 	deactivate() {
@@ -185,6 +188,7 @@ class ShippingView {
 
 	setData( data ) {
 		this.group.setData( data );
+		this.group.syncDataToForm();
 	}
 
 	toSubmitData( data ) {

From 09e7c3670a7211ab00f5989a2538f8bc44c4bcf4 Mon Sep 17 00:00:00 2001
From: Philipp Stracker <p.stracker@syde.com>
Date: Tue, 1 Oct 2024 16:27:21 +0200
Subject: [PATCH 6/9] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Small=20cleanup?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../resources/js/Components/FormFieldGroup.js | 25 ++++++++-----------
 1 file changed, 11 insertions(+), 14 deletions(-)

diff --git a/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js b/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
index b94932dd2..027a0c0d7 100644
--- a/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
+++ b/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
@@ -151,20 +151,17 @@ class FormFieldGroup {
 	 * @param {(field: object, key: string) => void} callback
 	 */
 	loopFields( callback ) {
-		Object.keys( this.#fields ).forEach( ( key ) => {
-			const field = this.#fields[ key ];
-			const fieldSelector = `${ this.#baseSelector } ${ field.selector }`;
-
-			callback(
-				{
-					inputSelector: field.inputName
-						? `${ fieldSelector } [name="${ field.inputName }"]`
-						: '',
-					...field,
-				},
-				key
-			);
-		} );
+		for ( const [ key, field ] of Object.entries( this.#fields ) ) {
+			const { selector, inputName } = field;
+			const inputSelector = `${ selector } [name="${ inputName }"]`;
+
+			const fieldInfo = {
+				inputSelector: inputName ? inputSelector : '',
+				...field,
+			};
+
+			callback( fieldInfo, key );
+		}
 	}
 
 	/**

From a897557f4f12195eadd288d846f241d928ef05c0 Mon Sep 17 00:00:00 2001
From: Philipp Stracker <p.stracker@syde.com>
Date: Tue, 1 Oct 2024 16:28:04 +0200
Subject: [PATCH 7/9] =?UTF-8?q?=F0=9F=90=9B=20Fix=20duplicate=20AXO=20chec?=
 =?UTF-8?q?k=20on=20initial=20load?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 modules/ppcp-axo/resources/js/AxoManager.js | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/modules/ppcp-axo/resources/js/AxoManager.js b/modules/ppcp-axo/resources/js/AxoManager.js
index d315ce374..551566042 100644
--- a/modules/ppcp-axo/resources/js/AxoManager.js
+++ b/modules/ppcp-axo/resources/js/AxoManager.js
@@ -801,8 +801,6 @@ class AxoManager {
 	}
 
 	async onChangeEmail() {
-		this.clearData();
-
 		if ( ! this.status.active ) {
 			log( 'Email checking skipped, AXO not active.' );
 			return;
@@ -813,11 +811,17 @@ class AxoManager {
 			return;
 		}
 
+		if ( this.data.email === this.emailInput.value ) {
+			log( 'Email has not changed since last validation.' );
+			return;
+		}
+
 		log(
 			`Email changed: ${
 				this.emailInput ? this.emailInput.value : '<empty>'
 			}`
 		);
+		this.clearData();
 
 		this.emailInput.value = this.stripSpaces( this.emailInput.value );
 

From d8d87ce1b817d63bf2133e1e40fcfb22052adb0e Mon Sep 17 00:00:00 2001
From: Philipp Stracker <p.stracker@syde.com>
Date: Tue, 1 Oct 2024 17:29:28 +0200
Subject: [PATCH 8/9] =?UTF-8?q?=F0=9F=90=9B=20Prevent=20infinite=20loop=20?=
 =?UTF-8?q?for=20invalid=20state=20values?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../resources/js/Components/FormFieldGroup.js   | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js b/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
index 027a0c0d7..67fdd58ec 100644
--- a/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
+++ b/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
@@ -53,10 +53,25 @@ class FormFieldGroup {
 	 * @return {boolean} True indicates that the previous value was different from the new value.
 	 */
 	#setFieldValue( field, value ) {
+		let oldVal;
+
+		const isValidOption = () => {
+			for ( let i = 0; i < field.options.length; i++ ) {
+				if ( field.options[ i ].value === value ) {
+					return true;
+				}
+			}
+			return false;
+		};
+
 		if ( ! field ) {
 			return false;
 		}
-		let oldVal;
+
+		// If an invalid option is provided, do nothing.
+		if ( 'SELECT' === field.tagName && ! isValidOption() ) {
+			return false;
+		}
 
 		if ( 'checkbox' === field.type || 'radio' === field.type ) {
 			value = !! value;

From 41e69fe556bbf6921bb702fcdf870453fea9af37 Mon Sep 17 00:00:00 2001
From: Philipp Stracker <p.stracker@syde.com>
Date: Tue, 1 Oct 2024 18:16:15 +0200
Subject: [PATCH 9/9] =?UTF-8?q?=F0=9F=90=9B=20Refresh=20cart=20when=20exit?=
 =?UTF-8?q?ing=20Ryan=20flow?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

After restoring the original WooCommerce form, also trigger a cart refresh Ajax call
---
 .../resources/js/Components/FormFieldGroup.js | 22 +++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)

diff --git a/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js b/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
index 67fdd58ec..2ea88feee 100644
--- a/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
+++ b/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js
@@ -210,14 +210,28 @@ class FormFieldGroup {
 	 * This function iterates through the stored form fields and resets their values or states.
 	 */
 	restoreFormData() {
+		let formHasChanged = false;
+
+		// Reset form fields to their initial state.
 		this.loopFields( ( { inputSelector }, fieldKey ) => {
-			if ( inputSelector && this.#stored.has( fieldKey ) ) {
-				const elInput = document.querySelector( inputSelector );
+			if ( ! this.#stored.has( fieldKey ) ) {
+				return;
+			}
 
-				this.#setFieldValue( elInput, this.#stored.get( fieldKey ) );
-				this.#stored.delete( fieldKey );
+			const elInput = inputSelector
+				? document.querySelector( inputSelector )
+				: null;
+			const oldValue = this.#stored.get( fieldKey );
+			this.#stored.delete( fieldKey );
+
+			if ( this.#setFieldValue( elInput, oldValue ) ) {
+				formHasChanged = true;
 			}
 		} );
+
+		if ( formHasChanged ) {
+			document.body.dispatchEvent( new Event( 'update_checkout' ) );
+		}
 	}
 
 	/**