1/** 2 * PuntoPunto - Subscription Cancellation Script for Webflow 3 * 4 * ТÑебÑеÑ: env_redirect_rotation.js 5 * ÐÑполÑзÑÐµÑ PuntoEnv Ð´Ð»Ñ ÐºÐ¾Ð½ÑигÑÑаÑии и логиÑÐ¾Ð²Ð°Ð½Ð¸Ñ 6 * 7 * Usage: 8 * 1. Add this script to Webflow page: Settings â Custom Code â Before </body> tag 9 * 2. Add custom attribute to cancel button: data-cancel-action="subscription" 10 * 3. Token will be extracted from URL query parameter: ?token=... 11 * 4. On success: redirect to /success 12 * 5. On error: redirect to /error 13 * 6. Messages will be inserted into elements with finish-message attributes 14 */ 15 16(function() { 17 'use strict'; 18 19 // ÐÑовеÑка налиÑÐ¸Ñ PuntoEnv (должен бÑÑÑ Ð·Ð°Ð³ÑÑжен из env_redirect_rotation.js) 20 if (!window.PuntoEnv) { 21 console.error('â PuntoEnv not found! Make sure env_redirect_rotation.js is loaded first.'); 22 return; 23 } 24 25 // ============================================ 26 // ÐÐÐÐРТ ÐÐÐФÐÐУРÐЦÐÐ ÐÐ ÐÐÐÐÐÐÐ ÐСТÐЧÐÐÐÐ 27 // ============================================ 28 29 const { API_BASE, SITE_BASE, log, warn, error } = window.PuntoEnv; 30 31 log('ð Cancel Subscription Script initializing...'); 32 log('ð Using API Base:', API_BASE); 33 log('ð Using Site Base:', SITE_BASE); 34 35 // Fallback messages (only if server doesn't provide them) 36 const FALLBACK_SUCCESS_MESSAGES = { 37 headline: 'ÐодпиÑка оÑменена', 38 text: 'ÐаннÑе ваÑей каÑÑÑ ÑÐ´Ð°Ð»ÐµÐ½Ñ Ð´Ð»Ñ Ð¿Ð¾ÑледÑÑÑÐ¸Ñ ÑпиÑаний. ÐаÑа лиÑÐµÐ½Ð·Ð¸Ñ Ð±ÑÐ´ÐµÑ Ð°Ð½Ð½ÑлиÑована поÑле оконÑÐ°Ð½Ð¸Ñ ÑекÑÑего оплаÑенного пеÑиода.' 39 }; 40 41 const FALLBACK_ERROR_MESSAGES = { 42 headline: 'ÐÑибка пÑи оÑмене подпиÑки', 43 text: 'Ðо вÑÐµÐ¼Ñ Ð¾ÑÐ¼ÐµÐ½Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñки пÑоизоÑла оÑибка. ÐопÑобÑйÑе Ñнова оÑкÑÑÑÑ Ð¿Ñиложение и повÑоÑиÑÑ Ð¿ÑоÑедÑÑÑ.' 44 }; 45 46 /** 47 * Extract token from URL query parameters 48 */ 49 function getTokenFromURL() { 50 const urlParams = new URLSearchParams(window.location.search); 51 return urlParams.get('token'); 52 } 53 54 /** 55 * Update finish messages on success/error page 56 */ 57 function updateFinishMessages(messages) { 58 // Update page title 59 if (messages.headline) { 60 document.title = messages.headline; 61 log(`ð Page title updated: ${messages.headline}`); 62 } 63 64 // Update headline 65 const headlineElement = document.querySelector('[finish-message="headline"]'); 66 if (headlineElement && messages.headline) { 67 headlineElement.textContent = messages.headline; 68 } 69 70 // Update text 71 const textElement = document.querySelector('[finish-message="text"]'); 72 if (textElement && messages.text) { 73 textElement.textContent = messages.text; 74 } 75 } 76 77 /** 78 * Redirect to success/error page with messages from server 79 * 80 * @param {boolean} isSuccess - Whether to redirect to success or error page 81 * @param {Object} serverMessages - Messages from server {headline, text} 82 */ 83 function redirectWithMessage(isSuccess, serverMessages = {}) { 84 const targetPath = isSuccess ? '/success' : '/error'; 85 86 // Use server messages or fallback to defaults 87 const fallbackMessages = isSuccess ? FALLBACK_SUCCESS_MESSAGES : FALLBACK_ERROR_MESSAGES; 88 const messages = { 89 headline: serverMessages.headline || fallbackMessages.headline, 90 text: serverMessages.text || fallbackMessages.text 91 }; 92 93 // Store messages in sessionStorage for the next page 94 sessionStorage.setItem('finishMessages', JSON.stringify(messages)); 95 96 log(`ð Redirecting to: ${SITE_BASE}${targetPath}`); 97 98 // Redirect (SITE_BASE бÑÐ´ÐµÑ Ð°Ð²ÑомаÑиÑеÑки пÑавилÑнÑм в завиÑимоÑÑи Ð¾Ñ Ð¾ÐºÑÑжениÑ) 99 window.location.href = `${SITE_BASE}${targetPath}`; 100 } 101 102 /** 103 * Cancel subscription via API 104 */ 105 async function cancelSubscription(token, button) { 106 // Disable button and show loading 107 button.disabled = true; 108 const originalText = button.textContent; 109 button.textContent = 'ÐÑменÑем подпиÑкÑ...'; 110 111 try { 112 const response = await fetch(`${API_BASE}/api/subscriptions/cancel`, { 113 method: 'POST', 114 headers: { 115 'Content-Type': 'application/json' 116 }, 117 body: JSON.stringify({ token }) 118 }); 119 120 const data = await response.json(); 121 122 if (response.ok && data.success) { 123 // Success - use messages from server 124 const serverMessages = { 125 headline: data.headline, 126 text: data.text 127 }; 128 129 log('â Subscription cancelled successfully'); 130 redirectWithMessage(true, serverMessages); 131 } else { 132 // API returned error - use error messages from server 133 const serverMessages = { 134 headline: data.headline, 135 text: data.text || data.error // Fallback to error field 136 }; 137 138 error('â Subscription cancellation failed:', data.error); 139 redirectWithMessage(false, serverMessages); 140 } 141 142 } catch (err) { 143 // Network error - use fallback messages 144 error('â Network error during subscription cancellation:', err); 145 redirectWithMessage(false, { 146 text: 'ÐÑибка ÑеÑи. ÐÑовеÑÑÑе подклÑÑение к инÑеÑнеÑÑ Ð¸ попÑобÑйÑе Ñнова.' 147 }); 148 } finally { 149 // Restore button state (in case redirect fails) 150 button.disabled = false;
151 button.textContent = originalText; 152 } 153 } 154 155 /** 156 * Initialize cancellation button 157 */ 158 function initCancelButton() { 159 const cancelButton = document.querySelector('[data-cancel-action="subscription"]'); 160 161 if (!cancelButton) { 162 log('â¹ï¸ Cancel subscription button not found on this page'); 163 return; 164 } 165 166 // Extract token from URL 167 const token = getTokenFromURL(); 168 169 if (!token) { 170 error('â No token found in URL. User must open cancellation link from app.'); 171 // Disable button if no token 172 cancelButton.disabled = true; 173 cancelButton.textContent = 'ÐевеÑÐ½Ð°Ñ ÑÑÑлка'; 174 return; 175 } 176 177 log('â Cancel button initialized with token'); 178 179 // Add click handler 180 cancelButton.addEventListener('click', async function(event) { 181 event.preventDefault(); 182 183 // Optional: Show confirmation dialog 184 const confirmed = confirm( 185 'ÐÑ ÑвеÑенÑ, ÑÑо Ñ Ð¾ÑиÑе оÑмениÑÑ Ð¿Ð¾Ð´Ð¿Ð¸ÑкÑ?\n\n' + 186 'ÐоÑÑÑп к Pro ÑÑнкÑиÑм ÑÐ¾Ñ ÑаниÑÑÑ Ð´Ð¾ конÑа оплаÑенного пеÑиода.' 187 ); 188 189 if (confirmed) { 190 await cancelSubscription(token, cancelButton); 191 } 192 }); 193 } 194 195 /** 196 * Apply finish messages on success/error pages 197 */ 198 function applyFinishMessages() { 199 const messages = sessionStorage.getItem('finishMessages'); 200 201 if (messages) { 202 try { 203 const parsedMessages = JSON.parse(messages); 204 updateFinishMessages(parsedMessages); 205 206 // Clear after applying 207 sessionStorage.removeItem('finishMessages'); 208 } catch (err) { 209 error('â Failed to parse finish messages:', err); 210 } 211 } 212 } 213 214 /** 215 * Initialize on DOM ready 216 */ 217 if (document.readyState === 'loading') { 218 document.addEventListener('DOMContentLoaded', function() { 219 initCancelButton(); 220 applyFinishMessages(); 221 }); 222 } else { 223 initCancelButton(); 224 applyFinishMessages(); 225 } 226 227})(); 228
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.