Question:
Need help with the custom payment option
Product:
Wix editor
What are you trying to achieve:
Im following the template from this video https://www.youtube.com/watch?v=VoxYv7edayg&t=1967s however the pay button is not working im getting "428 general error " when the pay button is clicked (i assume i have issue with my formfields or maybe the video is too old)
What have you already tried:
I havent tried anything expect for just trying the code in the template
Additional information:
The addons and custom pricing is working completely fine my problem is with the pay button
in CMS , i have the addons and weekrate
here is my page code for the custom booking
import wixBookingsFrontend from 'wix-bookings-frontend';
import { getAddons } from 'backend/queries';
import { getRates } from 'backend/commons'
const SERVICE_ID = '18fb5351-e8fa-447a-8407-1d042de93f48';
const DATE_OPTIONS = { weekday:"short", year:"numeric", month:"short", day:"numeric"}
let userData = {};
let availableSlots = [];
let selectedDate;
let sessionPrice = 0;
let prices = {};
let addons = [];
$w.onReady(async function () {
setEvents();
$w('#addonSummaryRepeater').data = [];
$w('#thankYouSummaryRepeater').data = [];
prices = await getRates();
getAddons().then((results) => {
addons = results;
$w('#addonRepeater').data = addons;
});
});
function setEvents() {
$w('#numGuestsDropdown').onChange(prepareDateDropdown);
$w('#dateDropdown').onChange(prepareTimeDropdown);
$w('#timeDropdown').onChange(goToAddons);
$w('#datetimeNextBtn').onClick(() => $w('#bookFlowMSB').changeState('addons'));
$w('#addonsNextBtn').onClick(() => $w('#bookFlowMSB').changeState('form'));
$w('#addonsBackBtn').onClick(() => $w('#bookFlowMSB').changeState('datetime'));
$w('#formBackBtn').onClick(() => $w('#bookFlowMSB').changeState('addons'));
//Addons
$w('#addonRepeater').onItemReady(($item, itemData) => {
$item('#addonImage').src = itemData.image;
$item('#addonCheckbox').label = itemData.title;
$item('#addonPrice').text = `+ $${itemData.price} per person`;
$item('#addonDescription').text = itemData.description;
$item('#addonCheckbox').onChange(updateAddonsData);
$item('#addonQuantityInput').onInput(updateAddonsData);
$item('#addonQuantityInput').onChange(updateAddonsData); // Add this line
});
//Summary box - addons repeater
$w('#addonSummaryRepeater').onItemReady(($item, itemData) => {
$item('#title').text = itemData.title + " Package";
$item('#participants').text = `X ${itemData.participants} Participants`;
$item('#price').text = "$" + itemData.price;
});
//Thank you state - addons repeater
$w('#thankYouSummaryRepeater').onItemReady(($item, itemData) => {
$item('#addonName').text = itemData.title + " Package";
$item('#addonParticipants').text = `X ${itemData.participants} Participants`;
$item('#addonTyPrice').text = "$" + itemData.price;
});
//Form Fields
$w('#firstNameInput').onChange(updatePayNowButton);
$w('#lastNameInput').onChange(updatePayNowButton);
$w('#emailInput').onChange(updatePayNowButton);
$w('#phoneInput').onChange(updatePayNowButton);
$w('#messageInput').onChange(updatePayNowButton);
//Booking and Payment process
$w('#payNowBtn').onClick(checkout);
}
function checkout() {
$w('#payNowBtn').disable();
$w('#payNowLoader').show();
console.log('Frontend calculated price:', sessionPrice);
let selectedSlot = availableSlots.find(slot => String(slot.startDateTime) === userData.selectedTime);
let name = $w('#firstNameInput').value + ' ' + $w('#lastNameInput').value;
let email = $w('#emailInput').value;
let phone = $w('#phoneInput').value;
let message = $w('#messageInput').value;
let addon1Quantity = userData.addon1Selected ? userData.addon1Quantity : '0';
let addon2Quantity = userData.addon2Selected ? userData.addon2Quantity : '0';
let addon3Quantity = userData.addon3Selected ? userData.addon3Quantity : '0';
let formFieldValues = [{
_id: '00000000-0000-0000-0000-000000000001', //Name field
value: name
}, {
_id: '00000000-0000-0000-0000-000000000002', //Email field
value: email
}, {
_id: '00000000-0000-0000-0000-000000000003', //Phone field
value: phone
}, {
_id: '00000000-0000-0000-0000-000000000008', //Message field
value: message
}, {
_id: '0', //Addon no.1 field
value: addon1Quantity
}, {
_id: '1', //Addon no.2 field
value: addon2Quantity
}, {
_id: '2', //Addon no.3 field
value: addon3Quantity
}, {
_id: '9f1252be-0625-457b-b5cf-96a1fa42281a', //WeekendRate field
value: userData.weekendRate
}];
let bookingInfo = {
slot: selectedSlot,
formFields: formFieldValues,
numberOfSpots: userData.numberOfParticipants
};
let options = {
paymentType: 'wixPay_Offline',
};
wixBookingsFrontend.checkoutBooking(bookingInfo, options)
.then((result) => {
if (result.status.toLocaleLowerCase() === 'confirmed') {
$w('#payNowBtn').hide();
$w('#payNowLoader').hide();
showThankyouPage(result);
} else {
$w('#payNowBtn').enable();
$w('#payNowLoader').hide();
}
}).catch((e) => {
console.error('Failed to checkout: ', e);
$w('#payNowBtn').enable();
$w('#payNowLoader').hide();
});
}
async function showThankyouPage(result) {
if (result.status === 'Confirmed') {
$w('#bookingsConfirmedText').show();
} else {
$w('#bookingsConfirmedText').hide();
}
$w('#whereInput').expand();
$w('#summaryColumn').collapse();
$w('#bookFlowMSB').changeState('thankYou');
}
function updatePayNowButton() {
if ($w('#firstNameInput').valid && $w('#lastNameInput').valid && $w('#emailInput').valid && $w('#phoneInput').valid && $w('#messageInput').valid) {
$w('#payNowBtn').enable();
} else {
$w('#payNowBtn').disable();
}
}
function updateAddonsData() {
$w('#addonRepeater').forEachItem(($item, itemData, index) => {
const addonNumber = index + 1;
if ($item('#addonCheckbox').checked) {
userData[`addon${addonNumber}Selected`] = true;
userData[`addon${addonNumber}Quantity`] = $item('#addonQuantityInput').value;
} else {
userData[`addon${addonNumber}Selected`] = false;
delete(userData[`addon${addonNumber}Quantity`]);
}
});
console.log("userData:", userData);
updateSummary();
}
function goToAddons() {
sessionPrice = 0;
userData.selectedTime = $w('#timeDropdown').value;
updateSummary();
$w('#datetimeNextBtn').enable();
}
function prepareTimeDropdown() {
console.log('prepareTimeDropdown');
sessionPrice = 0;
updateSummary();
selectedDate = $w('#dateDropdown').value;
console.log('selectedDate:', selectedDate);
const dateObj = new Date(selectedDate);
const month = dateObj.getMonth(); // 0 = January, 11 = December
const day = dateObj.getDate();
const isWinter = (
(month >= 8) || // September (8) to December (11)
(month <= 3) || // January (0) to April (3)
(month === 3 && day <= 30) // April 1-30
);
userData.weekendRate = isWinter; // true = winter ($1062, weekend rate), false = summer ($897, weekday rate)
populateTimeDropdown();
updateSummary();
}
function populateTimeDropdown() {
console.log('populateTimeDropdown');
console.log('selectedDate:', selectedDate);
let timesForDropdown = [];
let availableTimeSlots = availableSlots.filter(slot => new Date(slot.startDateTime).toLocaleDateString('en-us', { weekday:"short", year:"numeric", month:"short", day:"numeric"}).includes(selectedDate));
console.log('availableTimeSlots:', availableTimeSlots);
availableTimeSlots.forEach(availableTimeSlot => {
let time = String(availableTimeSlot.startDateTime).split(' ')[4].split(':');
let label = time[0] + ':' + time[1];
timesForDropdown.push({ label, value: String(availableTimeSlot.startDateTime) });
});
$w('#timeDropdown').options = timesForDropdown;
$w('#timeDropdown').enable();
}
async function prepareDateDropdown() {
sessionPrice = 0;
userData.numberOfParticipants = $w('#numGuestsDropdown').value;
updateSummary();
$w('#progressLoader').show();
const allSlots = await getServiceAvailability(SERVICE_ID);
populateDatesDropdown(allSlots);
$w('#progressLoader').hide();
$w('#addonQuantityInput').max = parseInt(userData.numberOfParticipants);
}
function populateDatesDropdown(allSlots) {
$w('#dateDropdown').disable();
$w('#timeDropdown').disable();
let dateOptions = [];
let availableDates = [];
availableSlots = allSlots.slots.filter(slot => slot.remainingSpots >= userData.numberOfParticipants);
availableSlots.forEach(availableSlot => {
let stringForOptions = new Date(availableSlot.startDateTime).toLocaleDateString('en-us', DATE_OPTIONS);
if (!availableDates.includes(stringForOptions)) {
availableDates.push(stringForOptions);
let date = new Date(availableSlot.startDateTime);
const month = date.getMonth();
const day = date.getDate();
const isWinter = (
(month >= 8) || // September to December
(month <= 3) || // January to April
(month === 3 && day <= 30) // April 1-30
);
const price = isWinter ? prices.weekend : prices.weekday; // Winter = weekend rate, Summer = weekday rate
dateOptions.push({ label: `${stringForOptions} $${price}`, value: stringForOptions });
}
});
$w('#dateDropdown').options = dateOptions;
$w('#dateDropdown').enable();
}
async function getServiceAvailability(serviceId) {
disableScreenInfo();
const startDateTime = new Date();
const endDateTime = new Date();
endDateTime.setDate(startDateTime.getDate() + 730);
const serviceOptions = { startDateTime, endDateTime};
return await wixBookingsFrontend.getServiceAvailability(serviceId, serviceOptions);
}
function updateSummary() {
console.log('updateSummary');
if (userData.numberOfParticipants) {
updateServiceGroup();
updateChosenTime();
updateAddons();
updateSessionPrice();
} else {
console.error('Must select number of participants first');
}
}
//Review this function
function updateSessionPrice() {
console.log('updateSessionPrice');
if (sessionPrice != 0) {
$w('#addonSummaryRepeater').data.forEach(element => sessionPrice = sessionPrice + element.price);
$w('#totalAmount').text = `$${sessionPrice}`;
$w('#totalAmountSummary').text = `$${sessionPrice}`;
$w('#totalLine').expand();
$w('#totalGroup').expand();
$w('#line22').expand();
$w('#totalGroupSummary').expand();
} else {
$w('#totalLine').collapse();
$w('#totalGroup').collapse();
$w('#totalGroupSummary').collapse();
}
}
function updateServiceGroup() {
console.log('updateServiceGroup');
const text = (parseInt(userData.numberOfParticipants) > 1) ?
`X ${parseInt(userData.numberOfParticipants)} Participants` :
'1 Participant';
$w('#serviceParticipantsText').text = text;
$w('#serviceParticipantsTextSummary').text = text;
if (userData.weekendRate) {
$w('#basicAmount').text = '$' + parseInt(userData.numberOfParticipants) * prices.weekend;
$w('#weekendRate').show();
$w('#weekendRateSummary').show();
} else {
$w('#basicAmount').text = '$' + parseInt(userData.numberOfParticipants) * prices.weekday;
$w('#weekendRate').hide();
$w('#weekendRateSummary').hide();
}
$w('#basicAmountSummary').text = $w('#basicAmount').text;
if (userData.weekendRate) {
sessionPrice = parseInt(userData.numberOfParticipants) * prices.weekend;
} else {
sessionPrice = parseInt(userData.numberOfParticipants) * prices.weekday;
}
$w('#serviceGroup').expand();
$w('#serviceGroupSummary').expand();
}
function updateChosenTime() {
console.log('updateChosenTime');
console.log("userData:", userData);
if (userData.selectedTime) {
let timeDropdownChoice = String(userData.selectedTime).split('(')[0];
let selectedTimeArray = timeDropdownChoice.split(' ');
let hoursArray = selectedTimeArray[4].split(':');
let timeDropdownChoiceString = `${selectedTimeArray[0]} ${selectedTimeArray[1]} ${selectedTimeArray[2]} ${selectedTimeArray[3]} ${hoursArray[0]}:${hoursArray[1]} ${selectedTimeArray[5]}`;
$w('#chosenTimeText').text = timeDropdownChoiceString;
$w('#chosenTimeTextSummary').text = timeDropdownChoiceString;
$w('#chosenTimeText').expand();
}
}
function updateAddons() {
console.log('updateAddons');
const numberOfAddons = $w('#addonRepeater').data.length;
console.log('numberOfAddons:', numberOfAddons);
const addonData = [];
$w("#addonRepeater").data.forEach((addon, index) => {
if (userData[`addon${index+1}Selected`]) {
addonData.push( {
_id: String(index),
title: addon.title,
participants: userData[`addon${index+1}Quantity`],
price: addon.price * userData[`addon${index+1}Quantity`]
});
}
});
$w('#addonSummaryRepeater').data = [];
$w('#addonSummaryRepeater').data = addonData;
$w('#thankYouSummaryRepeater').data = [];
$w('#thankYouSummaryRepeater').data = addonData;
console.log("addonData:", addonData);
}
function disableScreenInfo() {
$w('#dateDropdown').disable();
$w('#timeDropdown').disable();
$w('#datetimeNextBtn').disable();
$w('#chosenTimeText').collapse();
$w('#serviceGroup').collapse();
}
my service plugin code for custom pircing spi.js
import { getAddons } from 'backend/queries';
import { getRates } from 'backend/commons';
export const calculatePrice = async (options) => {
let finalPrice;
console.log("options:", options);
const rates = await getRates();
console.log("rates:", rates);
const sessionDate = new Date(options.booking.startDate);
const month = sessionDate.getMonth(); // 0 = January, 11 = December
const day = sessionDate.getDate();
// Determine if the date is in the winter season (September 1 - April 30)
const isWinter = (
(month >= 8) || // September (8) to December (11)
(month <= 3) || // January (0) to April (3)
(month === 3 && day <= 30) // April 1-30
);
const sessionRate = isWinter ? rates.weekend : rates.weekday; // Winter = weekend rate, Summer = weekday rate
const numberOfParticipants = options.booking.numberOfParticipants;
console.log("numberOfParticipants:", numberOfParticipants);
const sessionPrice = numberOfParticipants * sessionRate;
const addons = await getAddons();
console.log("addons:", addons);
const additionalFields = options.booking.additionalFields;
finalPrice = sessionPrice;
const userFlow = additionalFields != null && additionalFields.length > 0;
if (userFlow) {
console.log("additionalFields:", additionalFields);
const addon1Value = getFieldValue(additionalFields, "Addon 1");
console.log("addon1Value:", addon1Value);
const addon2Value = getFieldValue(additionalFields, "Addon 2");
console.log("addon2Value:", addon2Value);
const addon3Value = getFieldValue(additionalFields, "Addon 3");
console.log("addon3Value:", addon3Value);
finalPrice +=
(addon1Value ? parseInt(addon1Value) : 0) * (addons.find(element => element.name === "addon1").price) +
(addon2Value ? parseInt(addon2Value) : 0) * (addons.find(element => element.name === "addon2").price) +
(addon3Value ? parseInt(addon3Value) : 0) * (addons.find(element => element.name === "addon3").price);
}
console.log("Final calculated price:", finalPrice);
return { calculatedPrice: finalPrice };
};
export function getFieldValue(additionalFields, text) {
const foundFieldArray = additionalFields.filter(additionalField => additionalField.label === text);
return foundFieldArray.length === 0 ? undefined : foundFieldArray[0].value;
}
and then have the commons.jsw and queries.jsw