mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-08-31 06:52:50 +08:00
Merge pull request #2665 from woocommerce/PCP-3541-sync-woo-with-axo-shipping
Fastlane update shipping options & taxes when changing address (3541)
This commit is contained in:
commit
3be4456bb0
4 changed files with 227 additions and 38 deletions
|
@ -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 );
|
||||
|
||||
|
|
|
@ -1,30 +1,35 @@
|
|||
class FormFieldGroup {
|
||||
#stored;
|
||||
#data = {};
|
||||
#active = false;
|
||||
#baseSelector;
|
||||
#contentSelector;
|
||||
#fields = {};
|
||||
#template;
|
||||
|
||||
constructor( config ) {
|
||||
this.data = {};
|
||||
|
||||
this.baseSelector = config.baseSelector;
|
||||
this.contentSelector = config.contentSelector;
|
||||
this.fields = config.fields || {};
|
||||
this.template = config.template;
|
||||
|
||||
this.active = false;
|
||||
this.#baseSelector = config.baseSelector;
|
||||
this.#contentSelector = config.contentSelector;
|
||||
this.#fields = config.fields || {};
|
||||
this.#template = config.template;
|
||||
this.#stored = new Map();
|
||||
}
|
||||
|
||||
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 +40,84 @@ class FormFieldGroup {
|
|||
.reduce(
|
||||
( acc, key ) =>
|
||||
acc && acc[ key ] !== undefined ? acc[ key ] : undefined,
|
||||
this.data
|
||||
this.#data
|
||||
);
|
||||
return value ? value : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the value of the input field.
|
||||
*
|
||||
* @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 ) {
|
||||
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;
|
||||
}
|
||||
|
||||
// If an invalid option is provided, do nothing.
|
||||
if ( 'SELECT' === field.tagName && ! isValidOption() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate form group: Render a custom Fastlane UI to replace the WooCommerce form.
|
||||
*
|
||||
* Indicates: Ryan flow.
|
||||
*/
|
||||
activate() {
|
||||
this.active = true;
|
||||
this.#active = true;
|
||||
this.storeFormData();
|
||||
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.restoreFormData();
|
||||
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,44 +125,145 @@ 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 ) => {
|
||||
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 );
|
||||
}
|
||||
} );
|
||||
|
||||
if ( typeof this.template === 'function' ) {
|
||||
content.innerHTML = this.template( {
|
||||
if ( typeof this.#template === 'function' ) {
|
||||
content.innerHTML = this.#template( {
|
||||
value: ( fieldKey ) => {
|
||||
return this.dataValue( fieldKey );
|
||||
},
|
||||
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 ) {
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
this.#setFieldValue( field, this.dataValue( name ) );
|
||||
} else {
|
||||
this.#stored.set( name, field.value );
|
||||
this.#setFieldValue( field, '' );
|
||||
}
|
||||
};
|
||||
|
||||
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() {
|
||||
let formHasChanged = false;
|
||||
|
||||
// Reset form fields to their initial state.
|
||||
this.loopFields( ( { inputSelector }, fieldKey ) => {
|
||||
if ( ! this.#stored.has( fieldKey ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
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' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
this.#baseSelector + ' ' + selector
|
||||
);
|
||||
if ( field ) {
|
||||
field.classList.remove( 'ppcp-axo-field-hidden' );
|
||||
|
@ -109,7 +272,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 +280,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 ) {
|
||||
|
@ -138,9 +301,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;
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
} );
|
||||
|
|
|
@ -90,42 +90,54 @@ 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',
|
||||
// Used by Woo to ensure correct location for taxes & shipping cost.
|
||||
valueCallback: () => true,
|
||||
},
|
||||
phone: {
|
||||
//'selector': '#billing_phone_field', // There is no shipping phone field.
|
||||
|
@ -163,6 +175,7 @@ class ShippingView {
|
|||
|
||||
activate() {
|
||||
this.group.activate();
|
||||
this.group.syncDataToForm();
|
||||
}
|
||||
|
||||
deactivate() {
|
||||
|
@ -175,6 +188,7 @@ class ShippingView {
|
|||
|
||||
setData( data ) {
|
||||
this.group.setData( data );
|
||||
this.group.syncDataToForm();
|
||||
}
|
||||
|
||||
toSubmitData( data ) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue