// checkout-stripe.el -- Stripe Payment Element setup and form submission. // Reads NEURON_CFG.plan and NEURON_CFG.pub_key from window. // Compiled with: elc --target=js --bundle --minify --obfuscate // // Required globals: window.NEURON_CFG.plan, window.NEURON_CFG.pub_key // Required CDN: Stripe.js loaded before this script fn main() -> Void { native_js("(function() { var cfg = window.NEURON_CFG || {}; var PLAN = cfg.plan || ''; var STRIPE_PK = cfg.pub_key || ''; var stripe, elements; function waitForStripe(cb) { if (window.Stripe) { cb(); return; } setTimeout(function() { waitForStripe(cb); }, 50); } function showMessage(msg) { var el = document.getElementById('payment-message'); if (el) { el.textContent = msg; el.style.display = 'block'; } } function setLoading(loading) { var btn = document.getElementById('submit-btn'); var label = document.getElementById('submit-label'); var spinner = document.getElementById('submit-spinner'); if (btn) btn.disabled = loading; if (label) label.style.display = loading ? 'none' : ''; if (spinner) spinner.style.display = loading ? '' : 'none'; } // Free plan has no payment form — bail out entirely. if (str_eq(PLAN, 'free')) return; window._neuronMode = 'payment'; var paymentEl = null; var userEmail = ''; var userName = ''; function appearance() { return { theme: 'flat', variables: { colorPrimary: '#0052A0', colorBackground: '#ffffff', colorText: '#1A1A2E', colorDanger: '#c0392b', colorTextPlaceholder: '#9B9BAD', borderRadius: '0px', fontFamily: 'system-ui, -apple-system, sans-serif', fontSizeBase: '15px', fontWeightNormal: '300', spacingUnit: '4px' }, rules: { '.Input': { border: '1px solid rgba(0,82,160,.22)', boxShadow: 'none', padding: '10px 14px' }, '.Input:focus': { border: '1px solid rgba(0,82,160,.6)', boxShadow: '0 0 0 3px rgba(0,82,160,.08)', outline: 'none' }, '.Label': { fontSize: '11px', fontWeight: '500', letterSpacing: '.06em', textTransform: 'uppercase', color: '#6B6B7E', marginBottom: '6px' }, '.Tab': { border: '1px solid rgba(0,82,160,.18)', boxShadow: 'none' }, '.Tab--selected':{ border: '1px solid rgba(0,82,160,.5)', boxShadow: '0 0 0 2px rgba(0,82,160,.12)' }, '.Error': { color: '#c0392b' } } }; } function currentTiming() { var later = document.getElementById('timing-later'); return (later && later.checked) ? 'later' : 'now'; } function fetchAndMount() { var submitBtn = document.getElementById('submit-btn'); if (submitBtn) submitBtn.disabled = true; if (paymentEl) { try { paymentEl.unmount(); } catch(e) {} paymentEl = null; } var hostEl = document.getElementById('payment-element'); if (hostEl && !document.querySelector('.checkout-element-loading')) { var d = document.createElement('div'); d.className = 'checkout-element-loading'; d.textContent = 'Loading payment form...'; hostEl.appendChild(d); } var timing = currentTiming(); return fetch('/api/payment-intent', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ plan: PLAN, timing: timing, email: userEmail, name: userName }) }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.error === 'sold_out') { showMessage('All 1,000 Founding Member spots have been claimed. Thank you for your interest - please consider the Professional plan.'); if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Sold out'; } return; } if (!data.client_secret) { showMessage('Unable to initialise payment. Please try again.'); return; } window._neuronMode = data.setup_mode ? 'setup' : 'payment'; window._neuronPiId = data.id || (data.client_secret ? data.client_secret.split('_secret_')[0] : ''); var submitLabel = document.getElementById('submit-label'); if (submitLabel) { submitLabel.textContent = window._neuronMode === 'setup' ? 'Save my card - no charge today →' : 'Complete purchase →'; } waitForStripe(function() { if (!stripe) stripe = Stripe(STRIPE_PK); elements = stripe.elements({ clientSecret: data.client_secret, appearance: appearance() }); paymentEl = elements.create('payment', { fields: { billingDetails: { name: 'never', email: 'never' } } }); paymentEl.mount('#payment-element'); paymentEl.on('ready', function() { var ld = document.querySelector('.checkout-element-loading'); if (ld) ld.remove(); if (submitBtn) submitBtn.disabled = false; }); }); }) .catch(function() { showMessage('Unable to connect. Please check your connection and try again.'); }); } // Don't init Stripe at page load — wait for auth. // checkout-auth.el calls window.initStripe(email, name) after sign-in. window.initStripe = function(email, name) { userEmail = email || ''; userName = name || ''; fetchAndMount(); var tNow = document.getElementById('timing-now'); var tLater = document.getElementById('timing-later'); if (tNow) tNow.addEventListener('change', fetchAndMount); if (tLater) tLater.addEventListener('change', fetchAndMount); }; var form = document.getElementById('payment-form'); if (form) form.addEventListener('submit', async function(e) { e.preventDefault(); if (!stripe || !elements) return; var attestCb = document.getElementById('founding-attest-cb'); if (attestCb && !attestCb.checked) { var warn = document.getElementById('attest-warn'); if (warn) warn.style.display = 'block'; attestCb.closest('label').scrollIntoView({ behavior: 'smooth', block: 'center' }); return; } var name = document.getElementById('buyer-name').value.trim(); var email = document.getElementById('buyer-email').value.trim(); if (!name || !email) { showMessage('Please enter your name and email.'); return; } if (attestCb) { try { await fetch('/api/attest', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ plan: PLAN, name: name, email: email, timestamp: new Date().toISOString(), attestation: 'I am joining as a genuine early user, not to extract proprietary information about Neuron technology, architecture, or roadmap. I will engage in good faith. I understand that if this is not my intent, a different plan is a better fit.', user_agent: navigator.userAgent }) }); } catch(e) {} } setLoading(true); var pmsg = document.getElementById('payment-message'); if (pmsg) pmsg.style.display = 'none'; if (window._neuronPiId) { try { await fetch('/api/link-customer', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ pi_id: window._neuronPiId, email: email, name: name, plan: PLAN, timing: currentTiming(), mode: window._neuronMode || 'payment', supabase_user_id: window._neuronSupaId || '' }) }); } catch(e) {} } var confirmParams = { return_url: window.location.origin + '/account?welcome=1', payment_method_data: { billing_details: { name: name, email: email } } }; var result; if (window._neuronMode === 'setup') { result = await stripe.confirmSetup({ elements: elements, confirmParams: confirmParams }); } else { confirmParams.receipt_email = email; result = await stripe.confirmPayment({ elements: elements, confirmParams: confirmParams }); } if (result.error) { showMessage(result.error.message || (window._neuronMode === 'setup' ? 'Could not save your card. Please try again.' : 'Payment failed. Please try again.')); setLoading(false); } }); })()") }