Files
formipay/public/assets/js/form-action.js
dwindown 35569923a5 docs: add comprehensive audit report and architectural recommendation
Checkpoint before implementation. Includes audit findings (FINDINGS.md),
architectural recommendation (RECOMMENDATION.md), and existing code changes
to Form, Order, Render, and form-action.js from recent development.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 17:00:47 +07:00

641 lines
27 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
jQuery(function($){
console.info(formipay_form);
let formipay_form_id = $('[data-form-id]').attr('data-form-id');
let formipay = formipay_form.forms[formipay_form_id];
var select_fields = $('.formipay-input.formipay-select');
if(select_fields.length > 0){
$.each(select_fields, function(index, field){
new Choices('#'+$(field).attr('id'));
});
}
function validateForm(input_class) {
var valid = true;
input_class.each(function () {
if ($(this).val() === '') {
valid = false;
return false;
}
});
return valid;
}
// Optional safety: guard for bad inputs to price_format
function price_format(nStr) {
nStr = isNaN(parseFloat(nStr)) ? 0 : parseFloat(nStr); nStr = nStr.toFixed(formipay.decimal_digits) + '';
var x = nStr.split('.');
var x1 = x[0];
var x2 = x.length > 1 ? formipay.decimal_symbol + x[1] : '';
var rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + formipay.thousand_separator + '$2');
}
return formipay.currency + ' ' + x1 + x2;
}
// Removed unconditional product price write: in static-products mode there is no #product_price and this forced a 0.
$('.formipay-payment-option-group:first-child').find('input').trigger('click');
// PAGE BREAK
var page_break = $('.formipay-page-break');
if(page_break.length > 0){
$.each( $('.formipay-field-group:not(.formipay-page-break)'), function(){
var prev_page_break = $(this).prev('.formipay-page-break');
$(this).appendTo(prev_page_break);
} );
var payment_page_break = $('.formipay-page-break.formipay-page-break-payment');
$('.result-wrapper').appendTo(payment_page_break);
$.each( page_break, function(index, page){
if(index > 0){
$(page).hide();
}
index = index + 1;
$(page).addClass('formipay-page-'+index);
});
$('.formipay-page-break-payment .formipay-submit-button').appendTo('.formipay-bottom-pagination').css({
'margin-left': 'unset',
'margin-right': 'unset'
}).hide();
}
var page_break_progress = $('.formipay-progress');
if(page_break_progress.length > 0){
// $(page_break_progress[0]).addClass('active');
$.each(page_break_progress, function(index, page){
if(index == 0){
$(page).addClass('active');
}
index = index + 1;
$(page).attr('data-page-number', index);
});
}
$('.formipay-progress').on('click', function(){
var page_number = $(this).attr('data-page-number');
var inputs_in_page = $('.formipay-page-break:visible').find('.formipay-input');
var valid_to_continue = check_page_input_invalid(inputs_in_page);
if(valid_to_continue === false){
return false;
}
if($('.formipay-page-'+page_number).hasClass('formipay-page-break-payment')){
$('.formipay-page-break-next-button').hide();
$('.formipay-page-break-next-button').siblings('.formipay-submit-button').show();
}else{
$('.formipay-page-break-next-button').show();
$('.formipay-page-break-next-button').siblings('.formipay-submit-button').hide();
}
$('.formipay-progress').removeClass('active');
$(this).addClass('active');
$('.formipay-page-break').hide();
$('.formipay-page-'+page_number).show();
});
function check_page_input_invalid(inputs){
var invalid_input = 0;
inputs.each(function(index, field) {
var the_label = $(field).data('label');
// $(field).removeAttr('style');
$(field).removeClass('formipay-input-invalid')
if (!$(field).is(':valid')) {
// $(field).attr('style', 'border-color: #d93258!important;');
$(field).addClass('formipay-input-invalid');
$(field).siblings('.formipay-validate-field').remove();
if ($(field).attr('type') == 'select' || $(field).attr('type') == 'radio' || $(field).attr('type') == 'checkbox') {
if ($(field).attr('type') == 'radio' || $(field).attr('type') == 'checkbox') {
if ($('[name="' + $(field).attr('name') + '"]:checked').length == 0) {
var notice_message = formipay.notice_empty_select_message;
$(field).parent().append('<p class="formipay-validate-field"><i class="bi bi-exclamation-circle"></i> '+notice_message.replace('{{field}}', the_label)+'</p>');
invalid_input ++;
}
} else if ($(field).attr('name').search('agreement') != -1) {
var notice_message = formipay.notice_empty_agreement_message;
$(field).parent().append('<p class="formipay-validate-field"><i class="bi bi-exclamation-circle"></i> '+notice_message.replace('{{field}}', the_label)+'</p>');
invalid_input ++;
} else {
var notice_message = formipay.notice_empty_select_message;
$(field).parent().append('<p class="formipay-validate-field"><i class="bi bi-exclamation-circle"></i> '+notice_message.replace('{{field}}', the_label)+'</p>');
invalid_input ++;
}
} else {
var notice_message = formipay.notice_empty_text_message;
$(field).parent().append('<p class="formipay-validate-field"><i class="bi bi-exclamation-circle"></i> '+notice_message.replace('{{field}}', the_label)+'</p>');
invalid_input ++;
}
}
});
if(invalid_input > 0){
return false;
}
return true;
}
$('.formipay-page-break-next-button').on('click', function(e){
var next_page = $('.formipay-page-break:visible').next('.formipay-page-break');
var inputs_in_page = $('.formipay-page-break:visible').find('.formipay-input');
var valid_to_continue = check_page_input_invalid(inputs_in_page);
if(valid_to_continue === false){
return false;
}
var active_progress = $('.formipay-progress.active');
if(next_page.length > 0){
active_progress.next('.formipay-progress').addClass('active');
active_progress.removeClass('active');
if(next_page.hasClass('formipay-page-break-payment')){
// $(this).prop('disabled', true);
$(this).hide();
$(this).siblings('.formipay-submit-button').show();
}
$('.formipay-page-break:visible').hide();
next_page.show();
e.target.blur()
}
$('.formipay-page-break-prev-button').prop('disabled', false);
});
$('.formipay-page-break-prev-button').on('click', function(e){
var prev_page = $('.formipay-page-break:visible').prev('.formipay-page-break');
var active_progress = $('.formipay-progress.active');
if(prev_page.length > 0){
active_progress.prev('.formipay-progress').addClass('active');
active_progress.removeClass('active');
if(prev_page.is('.formipay-page-break:nth-child(2)')){
$(this).prop('disabled', true);
}
$('.formipay-page-break-next-button').show();
$('.formipay-page-break-next-button').siblings('.formipay-submit-button').hide();
$('.formipay-page-break:visible').hide();
prev_page.show();
e.target.blur()
}
$('.formipay-page-break-next-button').prop('disabled', false);
});
$('.formipay-input-calculable, .formipay-qty-input').on('change', function(){
var form = $(this).parents('form');
set_qty_button();
do_calculate(form, 'calculate');
});
function set_qty_button() {
var input = $('.formipay-qty-input');
var min_val = input.attr('min');
var max_val = input.attr('max');
$('button.qty-min, button.qty-plus').prop('disabled', false);
if(input.val() == min_val){
$('button.qty-min').prop('disabled', true);
}
if(max_val && input.val() == max_val){
$('button.qty-plus').prop('disabled', true);
}
}
set_qty_button();
$('button.qty-min').on('click', function(){
var value = parseInt($('.formipay-qty-input').val());
$('.formipay-qty-input').val(value-1).trigger('change');
});
$('button.qty-plus').on('click', function(){
var value = parseInt($('.formipay-qty-input').val());
$('.formipay-qty-input').val(value+1).trigger('change');
});
function do_calculate(form, action, condition = 'general'){
var form_id = form.data('form-id');
var inputs = form.find('.formipay-input');
var meta_inputs = form.find('.formipay-meta-input');
var form_inputs = new FormData();
form_inputs.append('action', 'formipay_submission');
form_inputs.append('nonce', formipay_form.nonce);
form_inputs.append('data[qty]', $('.formipay-qty-input').val());
form_inputs.append('form_id', form_id);
form_inputs.append('currency', formipay.currency_code || 'IDR');
var $valid = true; // Initialize as true
inputs.each(function(index, field) {
var the_key = $(field).attr('name');
var the_label = $(field).data('label');
// $(field).removeAttr('style');
$(field).removeClass('formipay-input-invalid');
if (!$(field).is(':valid')) {
// $(field).attr('style', 'border-color: #d93258!important;');
$(field).siblings('.formipay-validate-field').remove();
if ($(field).attr('type') == 'select' || $(field).attr('type') == 'radio' || $(field).attr('type') == 'checkbox') {
var notice_message;
if ($(field).attr('type') == 'radio' || $(field).attr('type') == 'checkbox') {
if (!$('[name="' + $(field).attr('name') + '"]:checked').length) {
var notice_message = formipay.notice_empty_select_message;
$valid = false; // Set valid to false
}
} else if ($(field).attr('name').search('agreement') != -1) {
var notice_message = formipay.notice_empty_agreement_message;
$valid = false; // Set valid to false
} else {
var notice_message = formipay.notice_empty_select_message;
$valid = false; // Set valid to false
}
} else {
var notice_message = formipay.notice_empty_text_message;
$valid = false; // Set valid to false
}
if($valid == false && action == 'checkout' && condition == 'general'){
$(field).addClass('formipay-input-invalid');
$(field).parent().append('<p class="formipay-validate-field"><i class="bi bi-exclamation-circle"></i> '+notice_message.replace('{{field}}', the_label)+'</p>');
}
} else {
var the_value;
if ($(field).attr('type') == 'checkbox') {
var val = [];
form.find('[name="' + the_key + '"]:checked').each(function(i, check) {
val[i] = $(check).val();
});
the_value = val;
} else if ($(field).attr('type') == 'radio') {
the_value = $('[name=' + the_key + ']:checked').val();
} else {
the_value = $(field).val();
}
if ($(field).attr('type') == 'hidden' || $(field).parent().is(':hidden')) {
if ($(field).hasClass('formipay-select')) {
form_inputs.append('data[' + the_key + '][value]', the_value);
form_inputs.append('data[' + the_key + '][label]', $(field).find('option:selected').attr('data-label'));
} else {
form_inputs.append('data[' + the_key + ']', the_value);
}
} else {
$(field).siblings('.formipay-validate-field').remove();
if ($(field).hasClass('formipay-select')) {
form_inputs.append('data[' + the_key + '][value]', the_value);
form_inputs.append('data[' + the_key + '][label]', $(field).find('option:selected').attr('data-label'));
} else {
form_inputs.append('data[' + the_key + ']', the_value);
}
}
}
});
if(meta_inputs.length > 0){
$.each(meta_inputs, function(key, field){
var the_key = $(field).attr('name');
var the_value = $(field).val();
form_inputs.append('meta_data[' + the_key + ']', the_value);
});
}
if($valid || action == 'calculate') {
form_inputs.append('purpose', action);
$.ajax({
url: formipay_form.ajax_url,
data: form_inputs,
processData: false,
contentType: false,
type: 'POST',
enctype: 'multipart/form-data',
beforeSend: function() {
form.find('.formipay-submit-button').text(formipay.button_processing_text).prop('disabled', true);
$(document).trigger('formipayCalculateAjaxBeforeSend', [form, action]);
},
success: function(res) {
console.log(res);
form.find('.formipay-submit-button').text(formipay.button_text).prop('disabled', false);
$(document).trigger('formipayCalculateAjaxSuccess', [res, form, action]);
},
error: function(xhr, status, error) {
Swal.fire({
title: 'Error!',
html: xhr.responseText,
icon: 'error',
customClass: {
confirmButton: 'formipay-button-error'
},
allowOutsideClick: false,
allowEscapeKey: false,
showCloseButton: false,
}).then((result) => {
if (result.isConfirmed) {
window.location.reload();
}
});
}
});
}
}
$(document).on('change', '.formipay-input-invalid', function(){
if($(this).is(':valid')){
$(this).removeClass('formipay-input-invalid');
$(this).siblings('.formipay-validate-field').remove();
}
});
var forms = $('.formipay-form');
if(forms.length > 0){
$.each(forms, function(index, form){
do_calculate($(form), 'calculate', 'first-load');
});
}
$(document).on('formipayCalculateAjaxBeforeSend', function(event, form, action) {
if( action === 'checkout' ){
form.find('.formipay-validate-field').remove();
var form_id = form.data('form-id');
$('.formipay-input').removeAttr('style');
$('[data-form-id=' + form_id + ']').siblings('.submit-response').html('');
$('[data-form-id=' + form_id + ']').siblings('.submit-response').hide();
$('[data-form-id=' + form_id + ']').siblings('.submit-response').removeClass('formipay-message-success formipay-message-failed');
}
});
$(document).on('formipayCalculateAjaxSuccess', function(event, res, form, action) {
if(action == 'calculate') {
var button_text = form.find('.formipay-submit-button').data('button-text');
// If backend returns structured items, rebuild dynamic rows
if(res && Array.isArray(res.items)){
// Remove previously injected rows (keep total/grand-total rows)
form.find('.formipay-item-row:not(.formipay-total-row):not(.formipay-grand-total-row)').remove();
// If server provides totals
if(typeof res.total !== 'undefined'){
form.find('td.grand_total').html(price_format(res.total));
}
// If server provides per-line subtotals
if(res.items.length){
// If server distinguishes a primary product subtotal, reflect it
// but do not rely on index 0 always being product
res.items.forEach(function(item, index){
var qty = '';
if('qty' in item && item.qty > 1){ qty = ' x ' + item.qty; }
// If context is 'product' and there is a dedicated product row, update it
if(item.context === 'product' && form.find('tr.formipay-product-row').length === 1 && index === 0){
form.find('td.product_price').html(price_format(item.subtotal));
return; // skip duplicate append below
}
// Append any extra dynamic items before the total row
form.find('table#formipay-review-order .formipay-total-row').before(
'<tr class="formipay-item-row '+(item.context||'')+'">\n' +
' <th>'+ item.item + qty +'</th>\n' +
' <td>'+ price_format(item.subtotal) +'</td>\n' +
'</tr>'
);
});
}
// Update button from new grand total
var grand_html = form.find('td.grand_total').text();
form.find('.formipay-submit-button').html(button_text + ' - ' + grand_html);
} else {
// Backend didnt send calculable payload; keep server-rendered table intact
var grand_html = form.find('td.grand_total').text();
if(grand_html){
form.find('.formipay-submit-button').html(button_text + ' - ' + grand_html);
} else {
// Fallback: no grand total cell; do nothing
}
}
}else if(action == 'checkout'){
var form_id = form.data('form-id');
if(res.success) {
if(res.data.response_type == 'notice') {
$('[data-form-id=' + form_id + ']').find('.submit-response').html('Success! ' + res.data.message);
$('[data-form-id=' + form_id + ']').find('.submit-response').addClass('formipay-message-success');
$('[data-form-id=' + form_id + ']').find('.submit-response').show();
setTimeout(() => {
window.location.href = res.data.url;
}, 2500);
} else if(res.data.response_type == 'popup') {
let timerInterval;
Swal.fire({
title: 'Success!',
html: res.data.message,
icon: 'success',
timer: 2500,
timerProgressBar: true,
showConfirmButton: false,
willClose: () => {
clearInterval(timerInterval);
},
allowOutsideClick: false,
allowEscapeKey: false,
showCloseButton: false,
}).then((result) => {
if (result.dismiss === Swal.DismissReason.timer) {
window.location.href = res.data.url;
}
});
}
} else {
if(res.data.response_type == 'notice') {
$('[data-form-id=' + form_id + ']').find('.submit-response').html('Failed! ' + res.data.message);
$('[data-form-id=' + form_id + ']').find('.submit-response').addClass('formipay-message-failed');
$('[data-form-id=' + form_id + ']').find('.submit-response').show();
} else if(res.data.response_type == 'popup') {
Swal.fire({
title: 'Failed!',
html: res.data.message,
icon: 'error',
customClass: {
confirmButton: 'formipay-button-error'
},
allowOutsideClick: false,
allowEscapeKey: false,
showCloseButton: false,
}).then((result) => {
if (result.isConfirmed) {
window.location.reload();
}
});
}
}
}
});
$('.formipay-form .formipay-submit-button').on('submit click', function(e) {
e.preventDefault();
var form = $(this).parents('form');
do_calculate(form, 'checkout');
});
$('#apply_coupon_code').on('click', function(e){
e.preventDefault();
var $thisbutton = $(this);
var form = $thisbutton.closest('form');
if($('.formipay-code-input').val() !== ''){
$.ajax({
type: 'post',
url: formipay_form.ajax_url,
data: {
action: 'check_coupon_code',
code: $('.formipay-code-input').val(),
form: form.data('form-id'),
security: formipay_form.frontend_nonce
},
beforeSend: function() {
$thisbutton.addClass('loading').prop('disabled', true);
},
success: function (res) {
if(res.success){
$thisbutton
.html('<i class="bi bi-check-circle"></i>')
.removeClass('loading')
.css('background-color', '#008000');
}else{
$thisbutton
.html('<i class="bi bi-exclamation-circle"></i>')
.removeClass('loading')
.css('background-color', '#FF0000');
$('.formipay-code-input').val('');
}
do_calculate(form, 'calculate');
setTimeout(() => {
$thisbutton
.removeAttr('style')
.html($thisbutton.data('text'))
.prop('disabled', false);
}, 3000);
}
});
}
});
const $phoneInput = $(`[name=${formipay.buyer_phone_field}]`);
const $countrySelect = formipay.buyer_country_field !== "" ? $(`[name=${formipay.buyer_country_field}]`) : '';
let countryCode = formipay.buyer_phone_country_code || '';
// Function: Get all available phone codes from select options
function getAllCountryCodes() {
const codes = [];
$countrySelect.find('option').each(function () {
const code = $(this).data('phone-code');
if (code) codes.push(code.toString());
});
return codes;
}
// Function: Convert number to WhatsApp-compatible format
function toWhatsAppNumber(phoneNumber, newCountryCode = '62') {
if (!phoneNumber) return '';
let cleaned = phoneNumber.replace(/\D/g, '');
// Step 1: Remove any existing known country code
const knownCountryCodes = getAllCountryCodes();
knownCountryCodes.forEach(code => {
if (cleaned.startsWith(code)) {
cleaned = cleaned.substring(code.length);
}
});
// Step 2: Remove leading zero after removing old country code
if (cleaned.startsWith('0')) {
cleaned = cleaned.substring(1);
}
// Step 3: Add new country code
return newCountryCode + cleaned;
}
// Get current country code dynamically
function getCurrentCountryCode() {
return $countrySelect.length > 0
? ($countrySelect.find(':selected').data('phone-code') || '')
: (formipay.buyer_phone_country_code || '');
}
// On country change: Update country code and re-format phone number
if ($countrySelect.length > 0) {
$countrySelect.on('change', function () {
const oldCountryCode = countryCode;
countryCode = $countrySelect.find(':selected').data('phone-code') || '';
if (!countryCode) return;
const currentValue = $phoneInput.val().trim();
// If there's a current phone number and country changed
if (currentValue && currentValue !== oldCountryCode) {
const converted = toWhatsAppNumber(currentValue, countryCode);
$phoneInput.val(converted);
} else {
// Set placeholder if empty
$phoneInput.val(countryCode);
}
});
}
// On focus: insert country code as placeholder
$phoneInput.on('focus', function () {
const $this = $(this);
const currentValue = $this.val().trim();
const currentCode = getCurrentCountryCode();
if (!currentCode) return;
// Save original value before modifying
$this.data('original-value', currentValue);
if (currentValue === '' || currentValue === currentCode) {
$this.val(currentCode);
}
});
// On blur: clean up or convert to WhatsApp number
$phoneInput.on('blur', function () {
const $this = $(this);
const originalValue = $this.data('original-value') || '';
const currentValue = $this.val().trim();
const currentCode = getCurrentCountryCode();
if (!currentCode) return;
// Case: user left field empty or only had country code
if (currentValue === '' || currentValue === currentCode || currentValue.length <= 4) {
$this.val(''); // Clear it for placeholder behavior
return;
}
// Skip conversion if already formatted
const isAlreadyFormatted = getAllCountryCodes().some(code =>
currentValue.startsWith(code)
);
if (!isAlreadyFormatted || originalValue !== currentValue) {
const waNumber = toWhatsAppNumber(currentValue, currentCode);
$this.val(waNumber);
}
});
});