Skip Navigation Website Accessibility
// ============================================ // 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; } });