// ============================================
// CONFIGURATION - GOOGLE APPS SCRIPT SETUP
// ============================================
const GOOGLE_SCRIPT_URL = 'https://script.google.com/macros/s/AKfycbyQUSKz45IeP7IoZbfY-vq0p-grGzaIHhxVLe6CmeLX--nlNGVgWllDXiF5bR-g6tVa/exec';
// Rain POS Checkout URLs for each instrument
const CHECKOUT_URLS = {
'Violin': 'https://www.samsstrings.com/rent/Violin-Rental.htm',
'Viola (14" and smaller)': 'https://www.samsstrings.com/rent/Viola-Rental/p/Sams-Strings-Select-Viola-14-and-smaller-x67471030.htm',
'Viola (15" and larger)': 'https://www.samsstrings.com/rent/Viola-Rental/p/Sams-Strings-Select-Viola-15-and-larger-x67470967.htm',
'Cello': 'https://www.samsstrings.com/rent/Cello-Rental.htm'
};
// Canvas signature functionality
const canvas = document.getElementById('signature-canvas');
const ctx = canvas.getContext('2d');
let isDrawing = false;
let lastX = 0;
let lastY = 0;
// Set canvas size
function resizeCanvas() {
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
ctx.strokeStyle = '#dc143c';
ctx.lineWidth = 2;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
function startDrawing(e) {
isDrawing = true;
const rect = canvas.getBoundingClientRect();
[lastX, lastY] = [
(e.clientX || e.touches[0].clientX) - rect.left,
(e.clientY || e.touches[0].clientY) - rect.top
];
}
function draw(e) {
if (!isDrawing) return;
e.preventDefault();
const rect = canvas.getBoundingClientRect();
const currentX = (e.clientX || e.touches[0].clientX) - rect.left;
const currentY = (e.clientY || e.touches[0].clientY) - rect.top;
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(currentX, currentY);
ctx.stroke();
[lastX, lastY] = [currentX, currentY];
}
function stopDrawing() {
isDrawing = false;
}
// Mouse events
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mouseout', stopDrawing);
// Touch events
canvas.addEventListener('touchstart', startDrawing);
canvas.addEventListener('touchmove', draw);
canvas.addEventListener('touchend', stopDrawing);
// Clear signature
document.getElementById('clear-signature').addEventListener('click', () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
});
// Update instrument display when selection changes
document.getElementById('instrument-type').addEventListener('change', (e) => {
const selectedInstrument = e.target.value;
if (selectedInstrument) {
// Update display name
document.getElementById('instrument-type-display').textContent = selectedInstrument;
// Update pricing based on instrument
let monthlyRate = '';
let initialPayment = '';
if (selectedInstrument === 'Violin') {
monthlyRate = '$29.99 plus tax ($22.00 rental + $7.99 maintenance)';
initialPayment = '$89.97 plus tax (First 3 months, non-refundable)';
} else if (selectedInstrument === 'Viola (up to 14")') {
monthlyRate = '$29.99 plus tax ($22.00 rental + $7.99 maintenance)';
initialPayment = '$89.97 plus tax (First 3 months, non-refundable)';
} else if (selectedInstrument === 'Viola (15" and larger)') {
monthlyRate = '$32.49 plus tax ($23.00 rental + $9.49 maintenance)';
initialPayment = '$97.47 plus tax (First 3 months, non-refundable)';
} else if (selectedInstrument === 'Cello') {
monthlyRate = '$53.99 plus tax ($43.00 rental + $10.99 maintenance)';
initialPayment = '$161.97 plus tax (First 3 months, non-refundable)';
}
// Update the display
document.getElementById('monthly-rate').textContent = monthlyRate;
document.getElementById('initial-payment').textContent = initialPayment;
}
});
// Form submission
document.getElementById('rental-agreement-form').addEventListener('submit', async (e) => {
e.preventDefault();
const errorMessage = document.getElementById('error-message');
const successMessage = document.getElementById('success-message');
errorMessage.style.display = 'none';
successMessage.style.display = 'none';
// Validate signature
const canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const hasSignature = canvasData.data.some(channel => channel !== 0);
if (!hasSignature) {
errorMessage.textContent = 'Please draw your signature in the box provided.';
errorMessage.style.display = 'block';
errorMessage.scrollIntoView({ behavior: 'smooth', block: 'center' });
return;
}
// Validate instrument selection
const instrumentType = document.getElementById('instrument-type').value;
if (!instrumentType) {
errorMessage.textContent = 'Please select the instrument you are renting.';
errorMessage.style.display = 'block';
document.getElementById('instrument-type').scrollIntoView({ behavior: 'smooth', block: 'center' });
return;
}
const signatureData = canvas.toDataURL();
// Collect form data
const formData = {
parentName: document.getElementById('parent-name').value,
studentName: document.getElementById('student-name').value,
driversLicense: document.getElementById('drivers-license').value,
email: document.getElementById('email').value,
phone: document.getElementById('phone').value,
cardLast4: document.getElementById('card-last4').value,
instrumentType: instrumentType,
signatureData: signatureData,
rentalPeriod: document.getElementById('rental-period').textContent,
monthlyRate: document.getElementById('monthly-rate').textContent,
initialPayment: document.getElementById('initial-payment').textContent,
agreementAccepted: document.getElementById('agreement-checkbox').checked,
recurringChargesAccepted: document.getElementById('recurring-charge-checkbox').checked,
timestamp: new Date().toLocaleString()
};
try {
// Compress signature image to reduce PDF size
console.log('Compressing signature...');
const compressedSignature = await new Promise((resolve) => {
const img = new Image();
img.onload = function() {
const tempCanvas = document.createElement('canvas');
// Reduce size significantly
tempCanvas.width = 300;
tempCanvas.height = 120;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.fillStyle = 'white';
tempCtx.fillRect(0, 0, 300, 120);
tempCtx.drawImage(img, 0, 0, 300, 120);
// Use JPEG with heavy compression
resolve(tempCanvas.toDataURL('image/jpeg', 0.5));
};
img.src = signatureData;
});
console.log('Signature compressed successfully');
// Generate PDF
const { jsPDF } = window.jspdf;
const pdf = new jsPDF('p', 'mm', 'a4');
let yPos = 20;
const leftMargin = 20;
const pageWidth = pdf.internal.pageSize.width;
const pageHeight = pdf.internal.pageSize.height;
// Add Sam's Strings header
pdf.setFontSize(24);
pdf.setFont(undefined, 'bold');
pdf.setTextColor(220, 20, 60);
pdf.text("Sam's Strings", pageWidth / 2, yPos, { align: 'center' });
yPos += 8;
pdf.setFontSize(12);
pdf.setFont(undefined, 'normal');
pdf.setTextColor(0, 0, 0);
pdf.text("Violin Shop - Katy, TX", pageWidth / 2, yPos, { align: 'center' });
yPos += 15;
pdf.setFontSize(18);
pdf.setFont(undefined, 'bold');
pdf.text("Rental Agreement", pageWidth / 2, yPos, { align: 'center' });
// Add date
yPos += 10;
pdf.setFontSize(10);
pdf.setFont(undefined, 'normal');
pdf.text(`Date: ${formData.timestamp}`, pageWidth / 2, yPos, { align: 'center' });
// Renter Information
yPos += 15;
pdf.setFontSize(14);
pdf.setFont(undefined, 'bold');
pdf.text("Renter Information", leftMargin, yPos);
yPos += 8;
pdf.setFontSize(10);
pdf.setFont(undefined, 'normal');
pdf.text(`Parent/Guardian: ${formData.parentName}`, leftMargin, yPos);
yPos += 6;
pdf.text(`Student: ${formData.studentName}`, leftMargin, yPos);
yPos += 6;
pdf.text(`Driver's License: ${formData.driversLicense}`, leftMargin, yPos);
yPos += 6;
pdf.text(`Email: ${formData.email}`, leftMargin, yPos);
yPos += 6;
pdf.text(`Phone: ${formData.phone}`, leftMargin, yPos);
yPos += 6;
pdf.text(`Card on File (Last 4): ${formData.cardLast4}`, leftMargin, yPos);
// Rental Details
yPos += 12;
pdf.setFontSize(14);
pdf.setFont(undefined, 'bold');
pdf.text("Rental Details", leftMargin, yPos);
yPos += 8;
pdf.setFontSize(10);
pdf.setFont(undefined, 'normal');
pdf.text(`Instrument: ${formData.instrumentType}`, leftMargin, yPos);
yPos += 6;
pdf.text(`Monthly Rate: ${formData.monthlyRate}`, leftMargin, yPos);
yPos += 6;
pdf.text(`Initial Payment: ${formData.initialPayment}`, leftMargin, yPos);
yPos += 6;
pdf.text(`Rental Period: ${formData.rentalPeriod}`, leftMargin, yPos);
// Agreement Acceptance
yPos += 12;
pdf.setFontSize(14);
pdf.setFont(undefined, 'bold');
pdf.text("Agreement Acceptance", leftMargin, yPos);
yPos += 8;
pdf.setFontSize(9);
pdf.setFont(undefined, 'normal');
const termsText = [
"? I have read and agree to all terms and conditions",
"? I authorize recurring monthly charges to my credit card on file",
"? I understand the first 3 months are non-refundable",
"? I am responsible for the instrument until returned"
];
termsText.forEach(line => {
pdf.text(line, leftMargin, yPos);
yPos += 5;
});
// Check if we need a new page for full terms
if (yPos > pageHeight - 100) {
pdf.addPage();
yPos = 20;
}
// Full Rental Agreement Terms
yPos += 15;
pdf.setFontSize(14);
pdf.setFont(undefined, 'bold');
pdf.text("Rental Agreement Terms", leftMargin, yPos);
yPos += 10;
pdf.setFontSize(10);
pdf.setFont(undefined, 'bold');
pdf.text("Maintenance Plan Agreement", leftMargin, yPos);
yPos += 6;
pdf.setFontSize(8);
pdf.setFont(undefined, 'normal');
const maintenanceText = pdf.splitTextToSize(
"The renter will be responsible for our instrument, including lost or damaged (reasonable wear excepted), until the instrument is returned. The renter is required to pay for a monthly maintenance agreement. The maintenance agreement covers broken bridges, bows, open seams, broken strings or damaged strings and general upkeep of the instrument.",
pageWidth - (leftMargin * 2)
);
maintenanceText.forEach(line => {
if (yPos > pageHeight - 20) {
pdf.addPage();
yPos = 20;
}
pdf.text(line, leftMargin, yPos);
yPos += 4;
});
yPos += 3;
const damageText = pdf.splitTextToSize(
"Major damage to the body of the instrument, including but not limited to, broken necks and stolen instruments may result in a deductible fee: Violin $150, Viola $250, Cello $350, Bass $400. Claims for stolen instruments must include a police report.",
pageWidth - (leftMargin * 2)
);
damageText.forEach(line => {
if (yPos > pageHeight - 20) {
pdf.addPage();
yPos = 20;
}
pdf.text(line, leftMargin, yPos);
yPos += 4;
});
yPos += 3;
const coverageText = pdf.splitTextToSize(
"The maintenance agreement is not in effect during air or overseas travel, except with specific authorization. The maintenance agreement will not cover: theft from an automobile, heat damage, lost or intentionally damaged instruments, damage incurred during shipping, or shipping costs.",
pageWidth - (leftMargin * 2)
);
coverageText.forEach(line => {
if (yPos > pageHeight - 20) {
pdf.addPage();
yPos = 20;
}
pdf.text(line, leftMargin, yPos);
yPos += 4;
});
yPos += 3;
const repairText = pdf.splitTextToSize(
"We must perform or authorize all repairs. Should damage or loss not be covered by the maintenance agreement, we reserve the right to charge the cost of repair or replacement to any credit card on file. Renters whose instruments are stolen or badly damaged will lose any equity accrued. Should maintenance or repair be required on an instrument, bow and/or case, the renter is responsible to bring the instrument, bow and/or case to Sam's Strings during regular business hours. Sam's Strings may also be able to provide other pickup and delivery options as an added convenience.",
pageWidth - (leftMargin * 2)
);
repairText.forEach(line => {
if (yPos > pageHeight - 20) {
pdf.addPage();
yPos = 20;
}
pdf.text(line, leftMargin, yPos);
yPos += 4;
});
// Monthly Maintenance Fees
yPos += 8;
if (yPos > pageHeight - 40) {
pdf.addPage();
yPos = 20;
}
pdf.setFontSize(10);
pdf.setFont(undefined, 'bold');
pdf.text("Monthly Maintenance Fees", leftMargin, yPos);
yPos += 6;
pdf.setFontSize(8);
pdf.setFont(undefined, 'italic');
pdf.text("All prices below are plus applicable sales tax.", leftMargin, yPos);
yPos += 5;
pdf.setFont(undefined, 'normal');
pdf.text("• Violin: $5.99", leftMargin + 5, yPos);
yPos += 4;
pdf.text("• Viola: $7.49", leftMargin + 5, yPos);
yPos += 4;
pdf.text("• Cello: $8.99", leftMargin + 5, yPos);
yPos += 4;
pdf.text("• Bass: $9.99", leftMargin + 5, yPos);
// Fee Structure and Payment Policy
yPos += 8;
if (yPos > pageHeight - 40) {
pdf.addPage();
yPos = 20;
}
pdf.setFontSize(10);
pdf.setFont(undefined, 'bold');
pdf.text("Fee Structure and Payment Policy", leftMargin, yPos);
yPos += 6;
pdf.setFontSize(8);
pdf.setFont(undefined, 'normal');
const feeText = pdf.splitTextToSize(
"Rental fees must be paid in advance monthly, and will be automatically deducted from your credit card on file. Rental contracts are month-to-month, with the first 3 months fees being paid at the opening of a rental agreement. This initial 3-month payment is non-refundable, after which the contract continues month-to-month until the instrument is returned.",
pageWidth - (leftMargin * 2)
);
feeText.forEach(line => {
if (yPos > pageHeight - 20) {
pdf.addPage();
yPos = 20;
}
pdf.text(line, leftMargin, yPos);
yPos += 4;
});
// Return/Past Due Account Policy
yPos += 8;
if (yPos > pageHeight - 40) {
pdf.addPage();
yPos = 20;
}
pdf.setFontSize(10);
pdf.setFont(undefined, 'bold');
pdf.text("Return/Past Due Account Policy", leftMargin, yPos);
yPos += 6;
pdf.setFontSize(8);
pdf.setFont(undefined, 'normal');
const returnText = pdf.splitTextToSize(
"Returns: Returns can be made at any time! To avoid being charged for the following month, instruments must be returned by the last day of the preceding month. In addition, the first 3 months rent are non-refundable, regardless of if you return the instrument beforehand.",
pageWidth - (leftMargin * 2)
);
returnText.forEach(line => {
if (yPos > pageHeight - 20) {
pdf.addPage();
yPos = 20;
}
pdf.text(line, leftMargin, yPos);
yPos += 4;
});
yPos += 3;
const pastDueText = pdf.splitTextToSize(
"Past Due Account Policy: You may rent the instrument for as long a period as you wish, so long as your monthly rental payment is on time and in full. If you are having trouble making a payment, please contact us and we will try to work out a solution with you. However, if we do not hear from you and payments are more than 30 days late, a $10 penalty fee will apply. Clients with rental contracts over 60 days delinquent must return our instruments or we will report the instrument stolen. We reserve the right to use any remedy deemed necessary (including lawsuit) to take possession of unreturned rental instruments and to collect all past due rental fees, late fees, legal fees, collection costs, and losses due to instruments not covered by the maintenance agreement.",
pageWidth - (leftMargin * 2)
);
pastDueText.forEach(line => {
if (yPos > pageHeight - 20) {
pdf.addPage();
yPos = 20;
}
pdf.text(line, leftMargin, yPos);
yPos += 4;
});
// Check if we need a new page for signature
if (yPos > pageHeight - 60) {
pdf.addPage();
yPos = 20;
}
// Signature
yPos += 10;
pdf.setFontSize(12);
pdf.setFont(undefined, 'bold');
pdf.text("Customer Signature:", leftMargin, yPos);
// Add signature image
yPos += 5;
if (compressedSignature) {
try {
pdf.addImage(compressedSignature, 'JPEG', leftMargin, yPos, 60, 24);
yPos += 28;
} catch (e) {
console.error('Error adding signature:', e);
yPos += 5;
}
}
pdf.setFontSize(9);
pdf.setFont(undefined, 'normal');
pdf.text(`Signed on: ${formData.timestamp}`, leftMargin, yPos);
// Add footer
yPos = pageHeight - 20;
pdf.setFontSize(8);
pdf.setTextColor(100, 100, 100);
pdf.text("Sam's Strings | Katy, TX | (713) 257-0459", pageWidth / 2, yPos, { align: 'center' });
// Convert PDF to base64
const pdfBase64 = pdf.output('datauristring').split(',')[1];
const pdfFileName = `Rental_Agreement_${formData.studentName.replace(/s+/g, '_')}_${Date.now()}.pdf`;
// Check PDF size
const pdfSizeKB = Math.round((pdfBase64.length * 3) / 4 / 1024);
console.log(`PDF size: ${pdfSizeKB}KB`);
console.log('Sending to Google Apps Script...');
// Prepare data for Google Apps Script
const scriptData = {
parentName: formData.parentName,
studentName: formData.studentName,
driversLicense: formData.driversLicense,
email: formData.email,
phone: formData.phone,
cardLast4: formData.cardLast4,
instrumentType: formData.instrumentType,
monthlyRate: formData.monthlyRate,
initialPayment: formData.initialPayment,
rentalPeriod: formData.rentalPeriod,
timestamp: formData.timestamp,
pdfBase64: pdfBase64,
pdfFileName: pdfFileName
};
// Send to Google Apps Script
const response = await fetch(GOOGLE_SCRIPT_URL, {
method: 'POST',
mode: 'no-cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(scriptData)
});
console.log('Sent to Google Apps Script');
// Get the checkout URL for the selected instrument
const checkoutUrl = CHECKOUT_URLS[formData.instrumentType];
// Show success message with countdown
successMessage.innerHTML = '? Agreement Signed Successfully!
' +
'Your signed rental agreement has been submitted. ' +
'You will receive a confirmation email with a copy of the signed PDF shortly.
' +
'Redirecting to checkout in 3 seconds...';
successMessage.style.display = 'block';
successMessage.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Also download PDF to customer's computer as backup
pdf.save(pdfFileName);
// Countdown and redirect
let countdown = 3;
const countdownElement = document.getElementById('countdown');
const countdownInterval = setInterval(() => {
countdown--;
if (countdownElement) {
countdownElement.textContent = countdown;
}
if (countdown <= 0) {
clearInterval(countdownInterval);
window.location.href = checkoutUrl;
}
}, 1000);
} catch (error) {
console.error('Full error details:', error);
console.error('Error message:', error.message);
errorMessage.innerHTML = 'Error submitting agreement: ' +
(error.message || 'Unknown error') +
'
Please contact us at (713) 257-0459.';
errorMessage.style.display = 'block';
errorMessage.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
// Example: Populate rental details from URL parameters or session storage
// You would replace this with your actual data source
window.addEventListener('DOMContentLoaded', () => {
// Example of how you might populate the form from session data
const sessionData = sessionStorage.getItem('rentalDetails');
if (sessionData) {
const details = JSON.parse(sessionData);
if (details.instrument) {
document.getElementById('instrument-type').value = details.instrument;
document.getElementById('instrument-type-display').textContent = details.instrument;
}
if (details.period) document.getElementById('rental-period').textContent = details.period;
if (details.monthlyRate) document.getElementById('monthly-rate').textContent = details.monthlyRate;
if (details.initialPayment) document.getElementById('initial-payment').textContent = details.initialPayment;
}
});