1(() => { 2 function getConfig() { 3 const sdk = document.getElementById('paddle-sdk'); 4 if (!sdk) return null; 5 return { 6 token: sdk.dataset.paddleToken, 7 environment: sdk.dataset.paddleEnvironment, 8 successUrl: sdk.dataset.paddleSuccessUrl 9 }; 10 } 11 12 function initPaddle(config) { 13 if (!window.Paddle || !config || !config.token) return false; 14 if (config.environment === 'sandbox') { 15 window.Paddle.Environment.set('sandbox'); 16 } 17 window.Paddle.Setup({ token: config.token }); 18 return true; 19 } 20 21 function getSelectedPlan() { 22 const toggle = document.getElementById('billing-toggle'); 23 return (toggle && toggle.checked) ? 'annual' : 'monthly'; 24 } 25 26 function getLocale() { 27 return (document.documentElement.lang || 'en').toLowerCase().slice(0, 2); 28 } 29 30 async function launchCheckout(config, priceId, plan, opts = {}) { 31 const locale = opts.locale || getLocale(); 32 const customData = { 33 plan: plan === 'annual' ? 'proAnnual' : 'proMonthly', 34 locale 35 }; 36 if (opts.userId) customData.userId = opts.userId; 37 38 const resp = await fetch('/api/v1/checkouts/create_session', { 39 method: 'POST', 40 headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, 41 body: JSON.stringify({ price_id: priceId, custom_data: customData }) 42 }); 43 const data = await resp.json(); 44 if (!data.transaction_id) throw new Error(data.error || 'Failed to create transaction'); 45 46 window.Paddle.Checkout.open({ 47 transactionId: data.transaction_id, 48 settings: { 49 displayMode: 'overlay', 50 theme: 'light', 51 locale, 52 successUrl: withTransaction(config.successUrl, data.transaction_id) 53 } 54 }); 55 } 56 57 // Embed our own transaction id so the success page can find the license 58 // without relying on Paddle's `_ptxn` (which is not reliably carried to 59 // successUrl in the overlay flow). 60 function withTransaction(url, transactionId) { 61 if (!url || !transactionId) return url; 62 const sep = url.indexOf('?') === -1 ? '?' : '&'; 63 return url + sep + 'txn=' + encodeURIComponent(transactionId); 64 } 65 66 function makeClickHandler(config) { 67 return async (event) => { 68 event.preventDefault(); 69 const button = event.currentTarget; 70 const plan = getSelectedPlan(); 71 const priceId = plan === 'annual' ? button.dataset.priceAnnual : button.dataset.priceMonthly; 72 if (!priceId) { 73 console.error('[Paddle] Missing price ID for plan:', plan); 74 return; 75 } 76 77 button.disabled = true; 78 const originalText = button.textContent; 79 button.textContent = '...'; 80 81 try { 82 await launchCheckout(config, priceId, plan); 83 } catch (err) { 84 console.error('[Paddle] Checkout error:', err); 85 alert('Could not start checkout. Please try again or contact support.'); 86 } finally { 87 button.disabled = false; 88 button.textContent = originalText; 89 } 90 }; 91 } 92 93 function autoLaunchFromUrl(config) { 94 const params = new URLSearchParams(window.location.search); 95 if (params.get('upgrade') !== 'pro') return; 96 97 const plan = params.get('plan') === 'annual' ? 'annual' : 'monthly'; 98 const userId = params.get('user_id') || undefined; 99 const locale = params.get('locale') || undefined; 100 101 const button = document.querySelector('[data-paddle-checkout]'); 102 if (!button) { 103 console.error('[Paddle] Auto-launch: Pro button not found'); 104 return; 105 } 106 const priceId = plan === 'annual' ? button.dataset.priceAnnual : button.dataset.priceMonthly; 107 if (!priceId) { 108 console.error('[Paddle] Auto-launch: Missing price ID for plan:', plan); 109 return; 110 } 111 112 const toggle = document.getElementById('billing-toggle'); 113 if (toggle) { 114 toggle.checked = (plan === 'annual'); 115 toggle.dispatchEvent(new Event('change', { bubbles: true })); 116 } 117 118 history.replaceState({}, '', window.location.pathname + window.location.hash); 119 120 setTimeout(() => { 121 launchCheckout(config, priceId, plan, { userId, locale }) 122 .catch((err) => { 123 console.error('[Paddle] Auto-launch error:', err);
124 alert('Could not start checkout. Please scroll to the pricing section and click "Upgrade to Pro".'); 125 }); 126 }, 100); 127 } 128 129 function ready(fn) { 130 if (document.readyState !== 'loading') fn(); 131 else document.addEventListener('DOMContentLoaded', fn); 132 } 133 134 ready(() => { 135 const config = getConfig(); 136 if (!config) return; 137 initPaddle(config); 138 const handler = makeClickHandler(config); 139 document.querySelectorAll('[data-paddle-checkout]').forEach((btn) => { 140 btn.addEventListener('click', handler); 141 }); 142 autoLaunchFromUrl(config); 143 }); 144})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.