Shipping Migration: - Move shipping configuration from product-level to form-level - Add form shipping tab in form settings (no_shipping, flat_rate, free_shipping) - Update FlatRate to register at form level instead of product level - Update checkout logic to read from form settings - Support percentage-based flat rate calculation - Simplify shipping method IDs (flat_rate, free_shipping) Currency Flags Integration: - Add formipay_get_all_currency_flags() to read from admin/assets/json/flags.json - Remove hardcoded CURRENCY_FLAGS emoji map from VariationField.js - Create CurrencyFlag component to render base64 flag images - Localize currency_flags to window.formipayProductDetails - Update shipping info display in admin order details Benefits: - Form-level shipping prevents multiplying shipping costs per product - Single source of truth for currency flags (flags.json) - Better support for future cart system - Consistent with e-commerce standards
347 lines
12 KiB
JavaScript
347 lines
12 KiB
JavaScript
/**
|
|
* Formipay Checkout Shipping Integration
|
|
* Handles country selection, shipping method display, and cost calculation
|
|
*/
|
|
|
|
(function($) {
|
|
'use strict';
|
|
|
|
const FormipayCheckoutShipping = {
|
|
|
|
selectedCountry: '',
|
|
selectedMethod: '',
|
|
availableMethods: [],
|
|
formId: null,
|
|
|
|
init() {
|
|
this.formId = $('form[data-form-id]').data('form-id');
|
|
|
|
if (!this.formId) {
|
|
return;
|
|
}
|
|
|
|
this.bindEvents();
|
|
this.initializeCountrySelector();
|
|
},
|
|
|
|
bindEvents() {
|
|
// Country selection change
|
|
$(document).on('change', '.formipay-shipping-country', (e) => {
|
|
this.onCountryChange($(e.currentTarget).val());
|
|
});
|
|
|
|
// Shipping method selection change
|
|
$(document).on('change', '.formipay-shipping-method', (e) => {
|
|
this.onShippingMethodChange($(e.currentTarget).val());
|
|
});
|
|
},
|
|
|
|
initializeCountrySelector() {
|
|
// Check if this form has shipping enabled
|
|
if (this.isShippingEnabled()) {
|
|
// Add country selector before payment options
|
|
this.insertCountrySelector();
|
|
}
|
|
},
|
|
|
|
isShippingEnabled() {
|
|
// Check if shipping is enabled for this form
|
|
// With form-level shipping, we check shipping_enabled setting
|
|
return window.formipayFormData?.shipping_enabled &&
|
|
window.formipayFormData.shipping_enabled !== 'no_shipping';
|
|
},
|
|
|
|
insertCountrySelector() {
|
|
const orderReviewTable = $('#formipay-review-order');
|
|
|
|
if (orderReviewTable.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// Create country selector row before subtotal
|
|
const countryRow = `
|
|
<tr class="formipay-shipping-row">
|
|
<th>
|
|
<label for="shipping-country-${this.formId}">${formipay_shipping.labels.country || 'Shipping Country'}</label>
|
|
<select id="shipping-country-${this.formId}"
|
|
class="formipay-shipping-country"
|
|
name="shipping_country"
|
|
required>
|
|
<option value="">${formipay_shipping.labels.selectCountry || 'Select your country'}</option>
|
|
</select>
|
|
</th>
|
|
<td>
|
|
<span class="formipay-loading-spinner" style="display:none;">⏳</span>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
|
|
orderReviewTable.find('tbody').append(countryRow);
|
|
|
|
// Populate countries from settings
|
|
this.loadCountries();
|
|
},
|
|
|
|
loadCountries() {
|
|
// Get supported countries from shipping settings
|
|
$.ajax({
|
|
url: formipay_admin.ajaxUrl,
|
|
type: 'POST',
|
|
data: {
|
|
action: 'formipay_get_supported_countries',
|
|
nonce: formipay_admin.nonce,
|
|
form_id: this.formId
|
|
},
|
|
success: (response) => {
|
|
if (response.success && response.data.countries) {
|
|
this.populateCountries(response.data.countries);
|
|
}
|
|
},
|
|
error: () => {
|
|
// Fallback: show all countries
|
|
this.populateCountries(this.getAllCountries());
|
|
}
|
|
});
|
|
},
|
|
|
|
populateCountries(countries) {
|
|
const select = $(`#shipping-country-${this.formId}`);
|
|
select.find('option:not([value=""])').remove();
|
|
|
|
$.each(countries, (code, name) => {
|
|
select.append(`<option value="${code}">${name}</option>`);
|
|
});
|
|
},
|
|
|
|
getAllCountries() {
|
|
// Fallback country list
|
|
return {
|
|
'ID': 'Indonesia',
|
|
'MY': 'Malaysia',
|
|
'SG': 'Singapore',
|
|
'TH': 'Thailand',
|
|
'VN': 'Vietnam',
|
|
'PH': 'Philippines',
|
|
'TW': 'Taiwan',
|
|
'HK': 'Hong Kong',
|
|
'IN': 'India',
|
|
'CN': 'China',
|
|
'JP': 'Japan',
|
|
'KR': 'South Korea',
|
|
'AU': 'Australia',
|
|
'NZ': 'New Zealand',
|
|
'GB': 'United Kingdom',
|
|
'US': 'United States',
|
|
'CA': 'Canada',
|
|
'FR': 'France',
|
|
'DE': 'Germany',
|
|
'IT': 'Italy',
|
|
'ES': 'Spain',
|
|
'NL': 'Netherlands',
|
|
'BE': 'Belgium',
|
|
'CH': 'Switzerland',
|
|
'AT': 'Austria',
|
|
'IE': 'Ireland',
|
|
'DK': 'Denmark',
|
|
'SE': 'Sweden',
|
|
'NO': 'Norway',
|
|
'FI': 'Finland',
|
|
};
|
|
},
|
|
|
|
onCountryChange(countryCode) {
|
|
this.selectedCountry = countryCode;
|
|
|
|
if (!countryCode) {
|
|
this.hideShippingMethods();
|
|
return;
|
|
}
|
|
|
|
// Get available shipping methods for this country
|
|
this.fetchShippingMethods(countryCode);
|
|
},
|
|
|
|
fetchShippingMethods(countryCode) {
|
|
const spinner = $('.formipay-loading-spinner');
|
|
spinner.show();
|
|
|
|
$.ajax({
|
|
url: formipay_admin.ajaxUrl,
|
|
type: 'POST',
|
|
data: {
|
|
action: 'formipay_get_shipping_methods',
|
|
nonce: formipay_public.nonce,
|
|
form_id: this.formId,
|
|
country: countryCode,
|
|
currency: formipay.currency_code || 'IDR'
|
|
},
|
|
success: (response) => {
|
|
spinner.hide();
|
|
|
|
if (response.success) {
|
|
this.availableMethods = response.data.methods || [];
|
|
this.displayShippingMethods(this.availableMethods, response.data.default_method);
|
|
} else {
|
|
this.showError(response.data.message || 'Unable to load shipping methods');
|
|
}
|
|
},
|
|
error: () => {
|
|
spinner.hide();
|
|
this.showError('Unable to connect to shipping service');
|
|
}
|
|
});
|
|
},
|
|
|
|
displayShippingMethods(methods, defaultMethod) {
|
|
// Remove existing shipping method selector if any
|
|
$('.formipay-shipping-method-row').remove();
|
|
|
|
if (methods.length === 0) {
|
|
this.showError('Shipping is not available for this form');
|
|
return;
|
|
}
|
|
|
|
// Get order total for percentage calculations
|
|
const orderTotal = this.getOrderTotal();
|
|
|
|
let methodsHtml = '<div class="formipay-shipping-methods">';
|
|
methodsHtml += `<input type="hidden" name="shipping_method" value="${defaultMethod}" class="formipay-shipping-method-input">`;
|
|
|
|
$.each(methods, (index, method) => {
|
|
const methodId = method.id;
|
|
const isFree = method.cost === 0;
|
|
const isPercentage = method.type === 'percentage';
|
|
|
|
// Calculate actual cost
|
|
let actualCost = method.cost;
|
|
let costDisplay = '';
|
|
|
|
if (isFree) {
|
|
costDisplay = 'FREE';
|
|
} else if (isPercentage) {
|
|
actualCost = (orderTotal * method.cost) / 100;
|
|
costDisplay = `${method.cost}% (${this.formatCost(actualCost, method.currency)})`;
|
|
} else {
|
|
costDisplay = this.formatCost(method.cost, method.currency);
|
|
}
|
|
|
|
methodsHtml += `
|
|
<div class="formipay-shipping-option" data-method="${methodId}">
|
|
<label class="formipay-shipping-label">
|
|
<input type="radio"
|
|
name="shipping_method_display"
|
|
value="${methodId}"
|
|
${methodId === defaultMethod ? 'checked' : ''}
|
|
class="formipay-shipping-method"
|
|
data-cost="${actualCost}"
|
|
data-type="${method.type || 'fixed'}"
|
|
data-base-cost="${method.cost}">
|
|
<span class="shipping-method-name">${method.name}</span>
|
|
${isFree ? '<span class="badge badge-free">FREE</span>' : ''}
|
|
<span class="shipping-method-cost">${costDisplay}</span>
|
|
${method.description ? `<span class="shipping-method-desc">${method.description}</span>` : ''}
|
|
</label>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
methodsHtml += '</div>';
|
|
|
|
// Insert after country selector
|
|
const countryRow = $('.formipay-shipping-row');
|
|
const shippingRow = `
|
|
<tr class="formipay-shipping-method-row">
|
|
<td colspan="2">
|
|
${methodsHtml}
|
|
</td>
|
|
</tr>
|
|
`;
|
|
|
|
countryRow.after(shippingRow);
|
|
|
|
// Bind shipping method change events
|
|
$('.formipay-shipping-method').on('change', (e) => {
|
|
const target = $(e.currentTarget);
|
|
const cost = parseFloat(target.data('cost'));
|
|
const type = target.data('type');
|
|
const baseCost = parseFloat(target.data('base-cost'));
|
|
const method = target.val();
|
|
|
|
// For percentage, recalculate in case order total changed
|
|
let finalCost = cost;
|
|
if (type === 'percentage') {
|
|
finalCost = (this.getOrderTotal() * baseCost) / 100;
|
|
}
|
|
|
|
// Update hidden input
|
|
$('.formipay-shipping-method-input').val(method);
|
|
|
|
// Update order total
|
|
this.updateOrderTotal(finalCost);
|
|
});
|
|
|
|
// Trigger change on default method to set initial cost
|
|
$(`input[name="shipping_method_display"][value="${defaultMethod}"]`).trigger('change');
|
|
},
|
|
|
|
hideShippingMethods() {
|
|
$('.formipay-shipping-method-row').remove();
|
|
},
|
|
|
|
getOrderTotal() {
|
|
// Get current order total (excluding shipping)
|
|
const subtotalRow = $('.formipay-total-row td').text();
|
|
const subtotal = this.parseCurrency(subtotalRow);
|
|
return subtotal;
|
|
},
|
|
|
|
updateOrderTotal(shippingCost) {
|
|
const currentTotal = this.getOrderTotal();
|
|
const newTotal = currentTotal + shippingCost;
|
|
|
|
// Update the total display
|
|
const totalRow = $('.formipay-grand-total-row td');
|
|
totalRow.text(this.formatCost(newTotal));
|
|
|
|
// Update submit button
|
|
const submitBtn = $('.formipay-submit-button');
|
|
const currentText = submitBtn.attr('data-button-text');
|
|
submitBtn.html(`${currentText} - ${this.formatCost(newTotal)}`);
|
|
},
|
|
|
|
formatCost(cost, currency) {
|
|
// Format cost using same format as product prices
|
|
const formatted = cost.toFixed(formipay.decimal_digits || 2);
|
|
return (currency || formipay.currency || '') + ' ' + formatted;
|
|
},
|
|
|
|
parseCurrency(text) {
|
|
// Parse currency string to get numeric value
|
|
// Removes currency symbols and formats
|
|
const numeric = text.replace(/[^\d.-]/g, '');
|
|
return parseFloat(numeric) || 0;
|
|
},
|
|
|
|
showError(message) {
|
|
const errorHtml = `
|
|
<div class="formipay-shipping-error notice notice-error">
|
|
<p>${message}</p>
|
|
</div>
|
|
`;
|
|
$('.formipay-shipping-method-row').html(`<td colspan="2">${errorHtml}</td>`);
|
|
}
|
|
|
|
};
|
|
|
|
// Initialize when document is ready
|
|
$(document).ready(() => {
|
|
if (typeof formipay_shipping !== 'undefined' && typeof formipay_shipping.labels !== 'undefined') {
|
|
FormipayCheckoutShipping.init();
|
|
}
|
|
});
|
|
|
|
// Expose for global access
|
|
window.FormipayCheckoutShipping = FormipayCheckoutShipping;
|
|
|
|
})(jQuery);
|