Refactor axo module

This commit is contained in:
Pedro Silva 2024-03-08 14:39:50 +00:00
parent b7d43d3c5b
commit 039cc53f40
No known key found for this signature in database
GPG key ID: E2EE20C0669D24B3
6 changed files with 581 additions and 51 deletions

View file

@ -30,3 +30,7 @@
font-weight: bold;
color: #000000;
}
.ppcp-axo-field-hidden {
display: none;
}

View file

@ -2,6 +2,7 @@ import Fastlane from "./Entity/Fastlane";
import MockData from "./Helper/MockData";
import {log} from "./Helper/Debug";
import {hide, show} from '../../../ppcp-button/resources/js/modules/Helper/Hiding';
import FormFieldGroup from "./Helper/FormFieldGroup";
class AxoManager {
@ -13,6 +14,15 @@ class AxoManager {
this.fastlane = new Fastlane();
this.$ = jQuery;
this.isConnectProfile = false;
this.hideGatewaySelection = false;
this.data = {
billing: null,
shipping: null,
card: null,
}
this.elements = {
gatewayRadioButton: {
selector: '#payment_method_ppcp-axo-gateway',
@ -35,11 +45,24 @@ class AxoManager {
selector: '#ppcp-axo-email-widget',
className: 'ppcp-axo-email-widget'
},
shippingAddressContainer: {
id: 'ppcp-axo-shipping-address-container',
selector: '#ppcp-axo-shipping-address-container',
className: 'ppcp-axo-shipping-address-container',
anchorSelector: '.woocommerce-shipping-fields'
},
billingAddressContainer: {
id: 'ppcp-axo-billing-address-container',
selector: '#ppcp-axo-billing-address-container',
className: 'ppcp-axo-billing-address-container',
anchorSelector: '.woocommerce-billing-fields__field-wrapper'
},
fieldBillingEmail: {
selector: '#billing_email_field'
},
submitButtonContainer: {
selector: '#ppcp-axo-submit-button-container'
selector: '#ppcp-axo-submit-button-container',
buttonSelector: '#ppcp-axo-submit-button-container button'
},
}
@ -51,6 +74,226 @@ class AxoManager {
this.locale = 'en_us';
this.registerEventHandlers();
this.shippingFormFields = new FormFieldGroup({
baseSelector: '.woocommerce-checkout',
contentSelector: this.elements.shippingAddressContainer.selector,
template: (data) => {
const valueOfSelect = (selectSelector, key) => {
const selectElement = document.querySelector(selectSelector);
const option = selectElement.querySelector(`option[value="${key}"]`);
return option ? option.textContent : key;
}
if (data.isEditing()) {
return `
<div style="margin-bottom: 20px;">
<h3>Shipping details <a href="javascript:void(0)" data-ppcp-axo-save-shipping-address style="margin-left: 20px;">Save</a></h3>
</div>
`;
}
if (data.isEmpty()) {
return `
<div style="margin-bottom: 20px;">
<h3>Shipping details <a href="javascript:void(0)" data-ppcp-axo-change-shipping-address style="margin-left: 20px;">Edit</a></h3>
<div>Please fill in your shipping details.</div>
</div>
`;
}
return `
<div style="margin-bottom: 20px;">
<h3>Shipping details <a href="javascript:void(0)" data-ppcp-axo-change-shipping-address style="margin-left: 20px;">Edit</a></h3>
<div>${data.value('company')}</div>
<div>${data.value('firstName')} ${data.value('lastName')}</div>
<div>${data.value('street1')}</div>
<div>${data.value('street2')}</div>
<div>${data.value('postCode')} ${data.value('city')}</div>
<div>${valueOfSelect('#shipping_state', data.value('stateCode'))}</div>
<div>${valueOfSelect('#shipping_country', data.value('countryCode'))}</div>
</div>
`;
},
fields: {
firstName: {
'key': 'firstName',
'selector': '#shipping_first_name_field',
'valuePath': 'shipping.name.firstName',
},
lastName: {
'selector': '#shipping_last_name_field',
'valuePath': 'shipping.name.lastName',
},
street1: {
'selector': '#shipping_address_1_field',
'valuePath': 'shipping.address.addressLine1',
},
street2: {
'selector': '#shipping_address_2_field',
'valuePath': null
},
postCode: {
'selector': '#shipping_postcode_field',
'valuePath': 'shipping.address.postalCode',
},
city: {
'selector': '#shipping_city_field',
'valuePath': 'shipping.address.adminArea2',
},
stateCode: {
'selector': '#shipping_state_field',
'valuePath': 'shipping.address.adminArea1',
},
countryCode: {
'selector': '#shipping_country_field',
'valuePath': 'shipping.address.countryCode',
},
company: {
'selector': '#shipping_company_field',
'valuePath': null,
},
shipDifferentAddress: {
'selector': '#ship-to-different-address',
'valuePath': null,
}
}
});
this.billingFormFields = new FormFieldGroup({
baseSelector: '.woocommerce-checkout',
contentSelector: this.elements.billingAddressContainer.selector,
template: (data) => {
const valueOfSelect = (selectSelector, key) => {
const selectElement = document.querySelector(selectSelector);
const option = selectElement.querySelector(`option[value="${key}"]`);
return option ? option.textContent : key;
}
if (data.isEditing()) {
return `
<div style="margin-bottom: 20px;">
<h4><a href="javascript:void(0)" data-ppcp-axo-save-billing-address>Save</a></h4>
</div>
`;
}
if (data.isEmpty()) {
return `
<div style="margin-bottom: 20px;">
<div>Please fill in your billing details.</div>
<h4><a href="javascript:void(0)" data-ppcp-axo-change-billing-address>Edit</a></h4>
</div>
`;
}
return `
<div style="margin-bottom: 20px;">
<h4>Billing address</h4>
<div>${data.value('company')}</div>
<div>${data.value('firstName')} ${data.value('lastName')}</div>
<div>${data.value('street1')}</div>
<div>${data.value('street2')}</div>
<div>${data.value('postCode')} ${data.value('city')}</div>
<div>${valueOfSelect('#billing_state', data.value('stateCode'))}</div>
<div>${valueOfSelect('#billing_country', data.value('countryCode'))}</div>
<div>${data.value('phone')}</div>
<h4><a href="javascript:void(0)" data-ppcp-axo-change-billing-address>Edit</a></h4>
</div>
`;
},
fields: {
firstName: {
'selector': '#billing_first_name_field',
'valuePath': 'billing.name.firstName',
},
lastName: {
'selector': '#billing_last_name_field',
'valuePath': 'billing.name.lastName',
},
street1: {
'selector': '#billing_address_1_field',
'valuePath': 'billing.address.addressLine1',
},
street2: {
'selector': '#billing_address_2_field',
'valuePath': null
},
postCode: {
'selector': '#billing_postcode_field',
'valuePath': 'billing.address.postalCode',
},
city: {
'selector': '#billing_city_field',
'valuePath': 'billing.address.adminArea2',
},
stateCode: {
'selector': '#billing_state_field',
'valuePath': 'billing.address.adminArea1',
},
countryCode: {
'selector': '#billing_country_field',
'valuePath': 'billing.address.countryCode',
},
company: {
'selector': '#billing_company_field',
'valuePath': null,
},
phone: {
'selector': '#billing_phone_field',
'valuePath': null,
},
}
});
this.cardFormFields = new FormFieldGroup({
baseSelector: '.ppcp-axo-payment-container',
contentSelector: this.elements.paymentContainer.selector,
template: (data) => {
const selectOtherPaymentMethod = () => {
if (!this.hideGatewaySelection) {
return '';
}
return `<p style="margin-top: 40px; text-align: center;"><a href="javascript:void(0)" data-ppcp-axo-show-gateway-selection>Select other payment method</a></p>`;
};
if (data.isEmpty()) {
return `
<div style="margin-bottom: 20px; text-align: center;">
<div>Please fill in your card details.</div>
<h4><a href="javascript:void(0)" data-ppcp-axo-change-card>Edit</a></h4>
${selectOtherPaymentMethod()}
</div>
`;
}
return `
<div style="margin-bottom: 20px;">
<h3>Card Details <a href="javascript:void(0)" data-ppcp-axo-change-card>Edit</a></h3>
<div>${data.value('name')}</div>
<div>${data.value('brand')}</div>
<div>${data.value('lastDigits') ? '************' + data.value('lastDigits'): ''}</div>
<div>${data.value('expiry')}</div>
${selectOtherPaymentMethod()}
</div>
`;
},
fields: {
brand: {
'valuePath': 'card.paymentSource.card.brand',
},
expiry: {
'valuePath': 'card.paymentSource.card.expiry',
},
lastDigits: {
'valuePath': 'card.paymentSource.card.lastDigits',
},
name: {
'valuePath': 'card.paymentSource.card.name',
},
}
});
}
registerEventHandlers() {
// Listen to Gateway Radio button changes.
this.$(document).on('change', this.elements.gatewayRadioButton.selector, (ev) => {
if (ev.target.checked) {
@ -64,16 +307,86 @@ class AxoManager {
this.triggerGatewayChange();
});
this.$(document).on('click', this.elements.submitButtonContainer.selector + ' button', () => {
// On checkout form submitted.
this.$(document).on('click', this.elements.submitButtonContainer.buttonSelector, () => {
this.onClickSubmitButton();
return false;
});
// Click change shipping address link.
this.$(document).on('click', '*[data-ppcp-axo-change-shipping-address]', async () => {
if (this.isConnectProfile) {
console.log('profile', this.fastlane.profile);
//this.shippingFormFields.deactivate();
const { selectionChanged, selectedAddress } = await this.fastlane.profile.showShippingAddressSelector();
console.log('selectedAddress', selectedAddress);
if (selectionChanged) {
this.setShipping(selectedAddress);
this.shippingFormFields.activate();
}
} else {
let checkbox = document.querySelector('#ship-to-different-address-checkbox');
if (checkbox && !checkbox.checked) {
jQuery(checkbox).trigger('click');
}
this.shippingFormFields.deactivate();
}
});
this.$(document).on('click', '*[data-ppcp-axo-save-shipping-address]', async () => {
this.shippingFormFields.activate();
});
// Click change billing address link.
this.$(document).on('click', '*[data-ppcp-axo-change-billing-address]', async () => {
if (this.isConnectProfile) {
this.$('*[data-ppcp-axo-change-card]').trigger('click');
} else {
this.billingFormFields.deactivate();
}
});
this.$(document).on('click', '*[data-ppcp-axo-save-billing-address]', async () => {
this.billingFormFields.activate();
});
// Click change card link.
this.$(document).on('click', '*[data-ppcp-axo-change-card]', async () => {
console.log('profile', this.fastlane.profile);
const response = await this.fastlane.profile.showCardSelector();
console.log('card response', response);
if (response.selectionChanged) {
this.setCard(response.selectedCard);
this.setBilling({
address: response.selectedCard.paymentSource.card.billingAddress
});
}
});
// Cancel "continuation" mode.
this.$(document).on('click', '*[data-ppcp-axo-show-gateway-selection]', async () => {
this.hideGatewaySelection = false;
this.$('.wc_payment_methods label').show();
this.cardFormFields.refresh();
});
}
showAxo() {
this.initEmail();
this.init();
this.initPlacements();
this.initFastlane();
this.shippingFormFields.activate();
this.billingFormFields.activate();
show(this.elements.emailWidgetContainer.selector);
show(this.elements.watermarkContainer.selector);
@ -87,6 +400,9 @@ class AxoManager {
}
hideAxo() {
this.shippingFormFields.deactivate();
this.billingFormFields.deactivate();
hide(this.elements.emailWidgetContainer.selector);
hide(this.elements.watermarkContainer.selector);
hide(this.elements.paymentContainer.selector);
@ -98,15 +414,31 @@ class AxoManager {
}
}
initEmail() {
initPlacements() {
let emailRow = document.querySelector(this.elements.fieldBillingEmail.selector);
const bc = this.elements.billingAddressContainer;
const sc = this.elements.shippingAddressContainer;
const ec = this.elements.emailWidgetContainer;
if (!document.querySelector(bc.selector)) {
document.querySelector(bc.anchorSelector).insertAdjacentHTML('beforeend', `
<div id="${bc.id}" class="${bc.className}"></div>
`);
}
if (!document.querySelector(sc.selector)) {
document.querySelector(sc.anchorSelector).insertAdjacentHTML('afterbegin', `
<div id="${sc.id}" class="${sc.className}"></div>
`);
}
if (this.useEmailWidget()) {
// Display email widget.
if (!document.querySelector(this.elements.emailWidgetContainer.selector)) {
if (!document.querySelector(ec.selector)) {
emailRow.parentNode.insertAdjacentHTML('afterbegin', `
<div id="${this.elements.emailWidgetContainer.id}" class="${this.elements.emailWidgetContainer.className}">
<div id="${ec.id}" class="${ec.className}">
--- EMAIL WIDGET PLACEHOLDER ---
</div>
`);
@ -120,7 +452,7 @@ class AxoManager {
}
}
async init() {
async initFastlane() {
if (this.initialized) {
return;
}
@ -188,30 +520,79 @@ class AxoManager {
async onChangeEmail () {
log('Email changed: ' + this.emailInput.value);
this.isConnectProfile = false;
this.hideGatewaySelection = false;
if (!this.emailInput.checkValidity()) {
log('The email address is not valid.');
return;
}
const { customerContextId } = await this.fastlane.identity.lookupCustomerByEmail(this.emailInput.value);
const lookupResponse = await this.fastlane.identity.lookupCustomerByEmail(this.emailInput.value);
if (customerContextId) {
show(this.elements.paymentContainer.selector);
if (lookupResponse.customerContextId) {
// Email is associated with a Connect profile or a PayPal member.
// Authenticate the customer to get access to their profile.
log('Email is associated with a Connect profile or a PayPal member');
// TODO : enter hideOtherGateways mode
const authResponse = await this.fastlane.identity.triggerAuthenticationFlow(lookupResponse.customerContextId);
log('AuthResponse', authResponse);
if (authResponse.authenticationState === 'succeeded') {
log(JSON.stringify(authResponse));
// document.querySelector(this.elements.paymentContainer.selector).innerHTML =
// '<a href="javascript:void(0)" data-ppcp-axo-change-card>Change card</a>';
// Add addresses
this.setShipping(authResponse.profileData.shippingAddress);
// TODO : set billing
this.setCard(authResponse.profileData.card);
this.isConnectProfile = true;
this.hideGatewaySelection = true;
this.$('.wc_payment_methods label').hide();
this.shippingFormFields.activate();
this.billingFormFields.activate();
this.cardFormFields.activate();
} else {
// authentication failed or canceled by the customer
log("Authentication Failed")
}
} else {
// No profile found with this email address.
// This is a guest customer.
log('No profile found with this email address.');
document.querySelector(this.elements.paymentContainer.selector)?.classList.remove('hidden');
this.cardComponent = await this.fastlane
.FastlaneCardComponent(MockData.cardComponent())
.render(this.elements.paymentContainer.selector);
}
}
setShipping(shipping) {
this.data.shipping = shipping;
this.shippingFormFields.setData(this.data);
}
setBilling(billing) {
this.data.billing = billing;
this.billingFormFields.setData(this.data);
}
setCard(card) {
this.data.card = card;
this.cardFormFields.setData(this.data);
}
onClickSubmitButton() {
try {
this.cardComponent.tokenize(MockData.cardComponentTokenize()).then((response) => {
@ -227,29 +608,6 @@ class AxoManager {
log('nonce: ' + nonce);
alert('nonce: ' + nonce);
// fetch('submit.php', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify({
// nonce: nonce
// }),
// })
// .then(response => {
// if (!response.ok) {
// throw new Error('Network response was not ok');
// }
// return response.json();
// })
// .then(data => {
// log('Submit response', data);
// log(JSON.stringify(data));
// })
// .catch(error => {
// console.error('There has been a problem with your fetch operation:', error);
// });
// Submit form.
document.querySelector(this.elements.defaultSubmitButton.selector).click();
}

View file

@ -0,0 +1,13 @@
class DomElement {
constructor(config) {
this.config = config;
this.selector = this.config.selector;
this.id = this.config.selector || null;
this.className = this.config.selector || null;
}
}
export default DomElement;

View file

@ -0,0 +1,57 @@
import DomElement from "./DomElement";
class DomElementCollection {
constructor() {
this.gatewayRadioButton = new DomElement({
selector: '#payment_method_ppcp-axo-gateway',
});
this.defaultSubmitButton = new DomElement({
selector: '#place_order',
});
this.paymentContainer = new DomElement({
id: 'ppcp-axo-payment-container',
selector: '#ppcp-axo-payment-container',
className: 'ppcp-axo-payment-container'
});
this.watermarkContainer = new DomElement({
id: 'ppcp-axo-watermark-container',
selector: '#ppcp-axo-watermark-container',
className: 'ppcp-axo-watermark-container'
});
this.emailWidgetContainer = new DomElement({
id: 'ppcp-axo-email-widget',
selector: '#ppcp-axo-email-widget',
className: 'ppcp-axo-email-widget'
});
this.shippingAddressContainer = new DomElement({
id: 'ppcp-axo-shipping-address-container',
selector: '#ppcp-axo-shipping-address-container',
className: 'ppcp-axo-shipping-address-container',
anchorSelector: '.woocommerce-shipping-fields'
});
this.billingAddressContainer = new DomElement({
id: 'ppcp-axo-billing-address-container',
selector: '#ppcp-axo-billing-address-container',
className: 'ppcp-axo-billing-address-container',
anchorSelector: '.woocommerce-billing-fields__field-wrapper'
});
this.fieldBillingEmail = new DomElement({
selector: '#billing_email_field'
});
this.submitButtonContainer = new DomElement({
selector: '#ppcp-axo-submit-button-container',
buttonSelector: '#ppcp-axo-submit-button-container button'
});
}
}
export default DomElementCollection;

View file

@ -0,0 +1,105 @@
class MockData {
constructor(config) {
this.data = {};
this.baseSelector = config.baseSelector;
this.contentSelector = config.contentSelector;
this.fields = config.fields || {};
this.template = config.template;
this.active = false;
}
setData(data) {
this.data = data;
this.refresh();
}
getDataValue(path) {
if (!path) {
return '';
}
const value = path.split('.').reduce((acc, key) => (acc && acc[key] !== undefined) ? acc[key] : undefined, this.data);
return value ? value : '';
}
activate() {
this.active = true;
this.refresh();
}
deactivate() {
this.active = false;
this.refresh();
}
toggle() {
this.active ? this.deactivate() : this.activate();
}
refresh() {
let content = document.querySelector(this.contentSelector);
if (!content) {
return;
}
content.innerHTML = '';
Object.keys(this.fields).forEach((key) => {
const field = this.fields[key];
if (this.active) {
this.hideField(field.selector);
//this.showField(this.contentSelector);
} else {
this.showField(field.selector);
//this.hideField(this.contentSelector);
}
if (typeof this.template === 'function') {
content.innerHTML = this.template({
value: (valueKey) => {
return this.getDataValue(this.fields[valueKey].valuePath);
},
isEmpty: () => {
let isEmpty = true;
Object.values(this.fields).forEach((valuefield) => {
console.log('isEmpty', valuefield.valuePath, this.data);
if (this.getDataValue(valuefield.valuePath)) {
console.log('isEmpty', 'no');
isEmpty = false;
return false;
}
});
console.log('isEmpty', 'r:' + isEmpty);
return isEmpty;
},
isEditing: () => {
return ! this.active;
},
});
}
});
}
showField(selector) {
const field = document.querySelector(this.baseSelector + ' ' + selector);
if (field) {
field.classList.remove('ppcp-axo-field-hidden');
}
}
hideField(selector) {
const field = document.querySelector(this.baseSelector + ' ' + selector);
if (field) {
field.classList.add('ppcp-axo-field-hidden');
}
}
}
export default MockData;