Charmers Bracelets

Choose Your Bracelet

Customize Your Bracelet

Selected: Bracelet

Shipping Information

Shipping Address

Payment

🎉 Order Successful!

Your payment was successful and your order has been saved to our system.

Expect a confirmation email from Charmers soon.

No product selected $0.00
Shipping $0.00
Total c.classList.remove('selected')); const cardEl = document.querySelector('.product-card[data-product="' + productType + '"]'); if (cardEl) cardEl.classList.add('selected'); document.querySelectorAll('.select-btn[data-select]').forEach(btn => { btn.classList.remove('selected'); btn.textContent = btn.getAttribute('data-select') === productType ? '✓ Selected' : 'Select This Bracelet'; }); buttonEl.classList.add('selected'); buttonEl.textContent = '✓ Selected'; document.getElementById('cart-summary').classList.add('show'); document.getElementById('checkout-btn').disabled = false; document.getElementById('product-name').textContent = products[productType].name; document.getElementById('selected-product-name').textContent = products[productType].name; braceletPrice = 0; selectedSize = null; document.querySelectorAll('.size-option').forEach(el => el.classList.remove('selected')); updateCartSummary(); updateOrderSummary(); syncPaymentRequestTotals(); } function initSizeSelection() { document.addEventListener('click', function (e) { const option = e.target.closest('.size-option'); if (!option) return; document.querySelectorAll('.size-option').forEach(el => el.classList.remove('selected')); option.classList.add('selected'); selectedSize = option.getAttribute('data-size'); braceletPrice = parseFloat(option.getAttribute('data-price')) || 0; updateCartSummary(); updateOrderSummary(); syncPaymentRequestTotals(); }); document.querySelectorAll('.customization-input').forEach(function (input) { input.addEventListener('input', function () { updateOrderSummary(); syncPaymentRequestTotals(); }); input.addEventListener('change', function () { updateOrderSummary(); syncPaymentRequestTotals(); }); }); } function initShippingListeners() { const shippingSelect = document.getElementById('shipping-option'); shippingSelect.addEventListener('change', function () { const selectedOption = shippingSelect.options[shippingSelect.selectedIndex]; shippingPrice = parseFloat(selectedOption.dataset.price || '0'); updateCartSummary(); updateOrderSummary(); syncPaymentRequestTotals(); }); document.getElementById('card-button').addEventListener('click', handleCardPayment); document.getElementById('dashboard-button').addEventListener('click', function () { window.open(BACKEND_URL + '/dashboard', '_blank'); }); } function goToStep(step) { if (step > currentStep) { if (currentStep === 1 && !selectedProduct) { alert('Please select a bracelet first.'); return; } if (currentStep === 2 && !validateCustomization()) { return; } if (currentStep === 3 && !validateShippingForm()) { return; } } document.querySelectorAll('.flow-step').forEach(el => el.classList.remove('active')); const target = document.getElementById('step-' + step); if (target) target.classList.add('active'); document.querySelectorAll('.flow-dot').forEach((dot, idx) => { dot.classList.toggle('active', idx < step); }); currentStep = step; if (step === 2 && selectedProduct) { showCustomization(selectedProduct); updateOrderSummary(); syncPaymentRequestTotals(); } if (step === 4) { updateOrderSummary(); syncPaymentRequestTotals(); } } function showCustomization(productType) { // Only switch which customization block is visible. document.querySelectorAll('.bracelet-customization').forEach(el => { el.style.display = 'none'; }); const id = products[productType].customization + '-customization'; const block = document.getElementById(id); if (block) block.style.display = 'block'; // Do NOT reset selectedSize/braceletPrice here; we handle resets when changing products. updateCartSummary(); syncPaymentRequestTotals(); } function validateCustomization() { if (!selectedSize) { alert('Please select a size.'); return false; } if (selectedProduct === 'birthstone') { const val = document.getElementById('birthstone-input').value.trim(); if (!val) { alert('Please enter your birthstone month.'); return false; } } if (selectedProduct === 'custom') { const feature = document.getElementById('custom-feature').value.trim(); if (!feature) { alert('Please describe what you want your bracelet to feature.'); return false; } } return true; } function validateShippingForm() { const requiredIds = ['shipping-option', 'name', 'email', 'address1', 'city', 'state', 'postal', 'country']; for (const id of requiredIds) { const el = document.getElementById(id); if (!el || !el.value.trim()) { alert('Please fill in ' + (el.placeholder || id) + '.'); if (el) el.focus(); return false; } } const emailVal = document.getElementById('email').value.trim(); const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(emailVal)) { alert('Please enter a valid email address.'); return false; } const shippingSelect = document.getElementById('shipping-option'); const opt = shippingSelect.options[shippingSelect.selectedIndex]; shippingPrice = parseFloat(opt.dataset.price || '0'); updateCartSummary(); syncPaymentRequestTotals(); return true; } function updateCartSummary() { const nameSpan = document.getElementById('cart-product-name'); const priceSpan = document.getElementById('cart-product-price'); const shipSpan = document.getElementById('cart-shipping-price'); const totalSpan = document.getElementById('cart-total-price'); if (selectedProduct) { nameSpan.textContent = products[selectedProduct].name; priceSpan.textContent = '$' + braceletPrice.toFixed(2); } else { nameSpan.textContent = 'No product selected'; priceSpan.textContent = '$0.00'; } shipSpan.textContent = '$' + shippingPrice.toFixed(2); const total = getTotal(); totalSpan.textContent = '$' + total.toFixed(2); const checkoutBtn = document.getElementById('checkout-btn'); if (checkoutBtn) { checkoutBtn.textContent = 'Checkout - $' + total.toFixed(2); } syncPaymentRequestTotals(); } function updateOrderSummary() { const div = document.getElementById('order-summary'); let html = '

Order Summary

'; if (!selectedProduct) { html += '

No product selected.

'; div.innerHTML = html; return; } html += '

Product: ' + products[selectedProduct].name + '

'; html += '

Size: ' + (selectedSize ? (selectedSize.charAt(0).toUpperCase() + selectedSize.slice(1)) : 'Not selected') + '

'; if (selectedProduct === 'birthstone') { const birthstone = document.getElementById('birthstone-input').value.trim(); const pers = document.getElementById('birthstone-personalization').value.trim(); if (birthstone) html += '

Birthstone: ' + birthstone + '

'; if (pers) html += '

Personalization: ' + pers + '

'; } else if (selectedProduct === 'heart') { const pers = document.getElementById('heart-personalization').value.trim(); if (pers) html += '

Personalization: ' + pers + '

'; } else if (selectedProduct === 'custom') { const feature = document.getElementById('custom-feature').value.trim(); const color = document.getElementById('custom-color').value || document.getElementById('custom-color-custom').value; const color2 = document.getElementById('custom-color2').value; const donation = document.getElementById('donation').value; const pers = document.getElementById('custom-personalization').value.trim(); if (feature) html += '

Feature: ' + feature + '

'; if (color) html += '

Primary color: ' + color + '

'; if (color2) html += '

Second color: ' + color2 + '

'; if (donation && donation !== 'none') html += '

Donation: ' + donation + '

'; if (pers) html += '

Personalization: ' + pers + '

'; } html += '

Bracelet Price: $' + braceletPrice.toFixed(2) + '

'; html += '

Shipping: $' + shippingPrice.toFixed(2) + '

'; html += '

Total: $' + getTotal().toFixed(2) + '

'; div.innerHTML = html; } function prepareOrderData() { const total = getTotal(); const type = selectedProduct; const orderDetails = { braceletType: type, total: total, size: selectedSize }; if (type === 'birthstone') { orderDetails.birthstone = document.getElementById('birthstone-input').value.trim(); orderDetails.personalization = document.getElementById('birthstone-personalization').value.trim(); } else if (type === 'heart') { orderDetails.personalization = document.getElementById('heart-personalization').value.trim(); } else if (type === 'custom') { orderDetails.feature = document.getElementById('custom-feature').value.trim(); orderDetails.color = document.getElementById('custom-color').value || document.getElementById('custom-color-custom').value.trim(); orderDetails.color2 = document.getElementById('custom-color2').value; orderDetails.donation = document.getElementById('donation').value; orderDetails.personalization = document.getElementById('custom-personalization').value.trim(); } const customerInfo = { name: document.getElementById('name').value.trim(), email: document.getElementById('email').value.trim(), phone: document.getElementById('phone').value.trim() }; const shipping = { address1: document.getElementById('address1').value.trim(), address2: document.getElementById('address2').value.trim(), city: document.getElementById('city').value.trim(), state: document.getElementById('state').value.trim(), postal: document.getElementById('postal').value.trim(), country: document.getElementById('country').value.trim(), shippingOption: document.getElementById('shipping-option').value }; return { total, orderDetails, customerInfo, shipping }; } async function saveOrderToBackend(paymentIntentId, orderData, recaptchaToken) { try { if (!recaptchaToken) { try { recaptchaToken = await getRecaptchaToken('save_order'); } catch (e) {} } if (!recaptchaToken) { return { success: false, error: 'reCAPTCHA unavailable; order was not saved.' }; } const res = await fetch(saveOrderUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ recaptchaToken: recaptchaToken, recaptchaAction: 'save_order', paymentIntentId: paymentIntentId, status: 'completed', customerInfo: orderData.customerInfo, orderDetails: orderData.orderDetails, shipping: orderData.shipping, amount: orderData.total }) }); const result = await res.json(); if (result.success) { console.log('Order saved to backend:', result.orderId); localStorage.setItem('hasOrdered', 'yes'); } else { console.warn('Backend save failed:', result.error || result); } return result; } catch (err) { console.error('Failed to save order to backend:', err); return { success: false, error: err.message }; } } async function handleCardPayment() { const button = document.getElementById('card-button'); if (!validateShippingForm()) return; if (!validateCustomization()) return; let recaptchaToken; try { recaptchaToken = await getRecaptchaToken('card_payment'); } catch (e) { alert(e && e.message ? e.message : 'reCAPTCHA failed.'); return; } button.disabled = true; button.textContent = 'Processing...'; const orderData = prepareOrderData(); const amountCents = Math.round(orderData.total * 100); let pi; try { pi = await postJSON(paymentIntentUrl, { amount: amountCents, recaptchaToken: recaptchaToken, recaptchaAction: 'card_payment', currency: 'usd', orderDetails: orderData.orderDetails, customerInfo: orderData.customerInfo, shipping: orderData.shipping }); } catch (err) { document.getElementById('card-errors').textContent = 'Payment server error: ' + (err && err.message ? err.message : err); button.disabled = false; button.textContent = 'Pay Now'; return; } if (pi.error || !pi.clientSecret) { document.getElementById('card-errors').textContent = pi.error || 'Unable to create payment. Try again later.'; button.disabled = false; button.textContent = 'Pay Now'; return; } const { error, paymentIntent } = await stripe.confirmCardPayment(pi.clientSecret, { payment_method: { card: card, billing_details: { name: orderData.customerInfo.name, email: orderData.customerInfo.email, phone: orderData.customerInfo.phone, address: { line1: orderData.shipping.address1, line2: orderData.shipping.address2, city: orderData.shipping.city, state: orderData.shipping.state, postal_code: orderData.shipping.postal, country: orderData.shipping.country } } } }); if (error) { let msg = error.message || 'Payment failed. Please try again.'; if (error.code === 'card_declined' && error.decline_code === 'insufficient_funds') { msg = 'The card was declined by your bank, try again later (code: insufficient_funds).'; } else if (error.code === 'invalid_number' || error.code === 'incorrect_number') { msg = 'Check the card number (code: ' + error.code + ').'; } else if (error.code === 'expired_card') { msg = 'Your card has expired. Please use a different card.'; } else if (error.code === 'incorrect_cvc') { msg = 'The CVC number is incorrect. Please check and try again.'; } document.getElementById('card-errors').textContent = msg; button.disabled = false; button.textContent = 'Pay Now'; return; } if (paymentIntent && paymentIntent.status === 'succeeded') { await saveOrderToBackend(paymentIntent.id, orderData, recaptchaToken); showSuccess(); } else { document.getElementById('card-errors').textContent = 'Payment processing... Please check your email for confirmation.'; } button.disabled = false; button.textContent = 'Pay Now'; } function showSuccess() { document.querySelectorAll('.flow-step').forEach(el => el.classList.remove('active')); document.getElementById('step-success').classList.add('active'); document.querySelectorAll('.flow-dot').forEach(dot => dot.classList.add('active')); document.getElementById('cart-summary').classList.remove('show'); } async function testBackendConnection() { try { const res = await fetch(BACKEND_URL + '/health'); const data = await res.json(); console.log('Backend connected:', data.status || data); } catch (err) { console.warn('Backend connection test failed:', err.message); } }('Backend connected:', data.status || data); } catch (err) { console.warn('Backend connection test failed:', err.message); } }