1/** 2 * Registration Modal System - Main Controller 3 * This file orchestrates the modal flow, relying on specialized modules for 4 * payment processing (Stripe, PayPal) and validation. 5 */ 6 7// Global variables for Stripe Elements (PCI DSS compliant) 8let stripe = null; 9let cardElement = null; 10let subscriptionStripe = null; 11let subscriptionCardElement = null; 12let retryStripe = null; 13let retryCardElement = null; 14 15// Global variables for PayPal 16let paypalSDKLoaded = false; 17let payPalButtonsRendered = false; // Add flag to prevent duplicate rendering 18 19// Global variable to store selected plan 20let selectedPlan = null; 21 22// Store form data globally to preserve on errors 23let savedFormData = {}; 24 25 26function showRegisterModal() { 27 showPlanSelectionModal(); 28} 29 30function showSubscriptionModal() { 31 showPlanSelectionModal(); 32} 33 34function showSubscriptionModalFromModule() { 35 showPlanSelectionModal(); 36} 37 38function showPlanSelectionModal() { 39 Swal.fire({ 40 title: 'Loading Subscription Plans...', 41 text: 'Please wait...', 42 allowOutsideClick: false, 43 allowEscapeKey: false,
44 showConfirmButton: false, 45 didOpen: () => Swal.showLoading() 46 }); 47 48 fetch('https://www.theteacherscorner.net/member/registration_api.php', { 49 method: 'POST', 50 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 51 credentials: 'include', // Fix for cross-subdomain session cookies 52 body: 'action=get_plans' 53 }) 54 .then(response => response.json()) 55 .then(data => { 56 if (data.success) { 57 displayPlanSelectionModal(data.plans); 58 } else { 59 Swal.fire('Error', data.message || 'Failed to load subscription plans', 'error'); 60 } 61 }) 62 .catch(error => { 63 Swal.fire('Error', 'Failed to load subscription plans. Please try again.', 'error'); 64 }); 65} 66 67function displayPlanSelectionModal(plans) { 68 let plansHtml = '<div class="plans-grid">'; 69 70 plans.forEach(plan => { 71 const isRecurring = Boolean(plan.recurring && plan.recurring !== "0" && plan.recurring !== 0); 72 const recurringText = isRecurring ? '<span class="recurring-yes">Recurring: YES</span>' : 'Recurring: <b>NO</b>'; 73 const processedDescription = plan.description.replace( 74 /click here for included features/gi, 75 '<a href="#" onclick="event.stopPropagation(); showFeaturesOverlay(); return false;" class="features-link">click here for included features</a>' 76 ); 77 78 plansHtml += ` 79 <div class="plan-card" data-plan-id="${plan.id}" onclick="selectPlan(${plan.id}, '${plan.title}', '${plan.price}', '${plan.days} ${plan.period}', ${isRecurring}, '${plan.pp_plan_id || ''}')"> 80 <div class="plan-title" style="background-color: #${plan.color};">${plan.title}</div> 81 <div class="plan-price" style="background-color: #${plan.color};"> 82 ${plan.price} 83 <span>${plan.days} ${plan.period}</span> 84 </div> 85 <div class="plan-recurring">${recurringText}</div> 86 <div class="plan-description">${processedDescription}</div> 87 <button type="button" class="plan-select-btn">Select This Plan</button> 88 </div> 89 `; 90 }); 91 92 plansHtml += '</div>'; 93 94 const featuresOverlayHtml = ` 95 <div id="features-overlay" class="features-overlay" style="display: none;"> 96 <div class="features-overlay-content"> 97 <div class="features-overlay-header"> 98 <h3>Premium Subscription Benefits</h3> 99 <button type="button" class="features-close-btn" onclick="hideFeaturesOverlay()">Ã</button> 100 </div> 101 <div class="features-overlay-body"> 102 ${getSubscriptionFeaturesHTML()} 103 </div> 104 </div> 105 </div> 106 `; 107 108 Swal.fire({ 109 title: 'Choose Your Subscription Plan', 110 html: plansHtml + featuresOverlayHtml, 111 footer: '<div class="back-to-login"><a href="#" onclick="event.preventDefault(); showLoginModal();">Go back to Login</a></div>', 112 showCancelButton: true,
113 showConfirmButton: false, 114 cancelButtonText: 'Cancel', 115 cancelButtonColor: '#999', 116 customClass: { popup: 'swal2-plans-modal' }, 117 width: '90%', 118 maxWidth: '1000px' 119 }); 120} 121 122function showFeaturesOverlay() { 123 const overlay = document.getElementById('features-overlay'); 124 if (overlay) { 125 overlay.style.display = 'flex'; 126 overlay.addEventListener('click', function(e) { 127 if (e.target === overlay) { 128 hideFeaturesOverlay(); 129 } 130 }); 131 document.addEventListener('keydown', handleEscapeKey); 132 } 133} 134 135function hideFeaturesOverlay() { 136 const overlay = document.getElementById('features-overlay'); 137 if (overlay) { 138 overlay.style.display = 'none'; 139 document.removeEventListener('keydown', handleEscapeKey); 140 } 141} 142 143function handleEscapeKey(e) { 144 if (e.key === 'Escape') { 145 hideFeaturesOverlay(); 146 } 147} 148 149function getSubscriptionFeaturesHTML() { 150 return ` 151 <div class="subscription-features" style="font-size: 12px; color: #009; text-align: left; padding: 15px; background: #f9f9f9; border-radius: 8px; margin-top: 10px;"> 152 <p style="font-weight: 700; font-size: 1.1em; color: red; margin-bottom: 10px;"> 153 <a href="#" onclick="event.preventDefault(); hideFeaturesOverlay();" style="color: #00f;">Click here to view all of our Premium Subscription plans</a> 154 </p> 155 <p style="margin-bottom: 10px;"> 156 <img src="https://www.theteacherscorner.net/images/world-famous-seal.png" width="74" height="55" alt="World Famous Puzzle and Worksheet Makers" style="float: right; margin-left: 10px;"> 157 Subscription Features include: 158 </p> 159 <ul style="margin-left: 0em; font-size: 11px;"> 160 <li><b>All Banner Ads removed from the entire website</b><br> 161 <span style="font-size: 9px; font-style: italic;"><b style="color:red;font-weight:bold">Note:</b> Subscriptions do NOT allow citation removal from puzzles and worksheets created on our site.</span> 162 </li> 163 <li><strong>More options available for customizing puzzles!</strong> 164 <ul style="margin-left: 0em;"> 165 <li>
165More AI generated word lists and clues!</li> 166 <li>More stock images to choose from!</li> 167 <li>More images allowed per puzzle!</li> 168 <li>More definition lookups (crossword and match-ups)</li> 169 <li>Many more available fonts! (over 125 fonts!)</li> 170 <li>Use ANY font installed on your computer</li> 171 <li>20 word list save slots - and access your saved lists from anywhere!</li> 172 <li>More word/clues for crosswords (up to 50)</li> 173 <li>More word/clues for match-ups (up to 100)</li> 174 <li>More word/clues for fill-in-the-blank (up to 100)</li> 175 <li>More word/clues for word scrambles (up to 100)</li> 176 <li>More options for Sudoku puzzles</li> 177 <li>No wait delays imposed for saving puzzles to an image or PDF</li> 178 <li>'Fake Bank' on our Fill-in-the-Blank worksheets</li> 179 <li>More features coming soon</li> 180 </ul> 181 </li> 182 <li><strong>More Daily Writing Prompts!</strong><br> 183 Over 30 extra prompts for our premium members for every month! (excludes July)<br> 184 <i>Note: Each month of Premium Daily Writing Prompts are made available 21 days before the start of the month.</i> 185 </li> 186 <li><b>All subscriptions support The Teacher's Corner!</b></li> 187 <li><b>FAQ:</b> Can I remove the "<em>Created on The Teacher's Corner</em>" (or similar) citation on the puzzles I create? <b>No</b>. That is not a benefit of our subscriptions. All citations must remain on worksheets and puzzles you create.</li> 188 </ul> 189 <p style="margin-top: 10px; font-size: 11px;"> 190 <a href="#" onclick="event.preventDefault(); hideFeaturesOverlay();" style="color: #00f; font-weight: bold;">Go here to view our Premium Subscription plans</a> 191 </p> 192 <p style="font-size: 10px; margin-top: 5px; font-weight:bold">NOTE: ALL Packages contain the same features, longer subscriptions get better price breaks</p> 193 <p style="font-size: 10px;">SAFE: All subscriptions can be paid via Paypal or Stripe, so your sensitive banking information is never seen nor stored by our website.</p> 194 <p style="font-size: 10px;">Click on <a href="https://www.facebook.com/theteacherscorner" rel="noopener" target="_blank">Facebook</a> and LIKE US to get up-to-date information and tips!</p> 195 </div> 196 `; 197} 198 199function selectPlan(planId, title, price, duration, recurring, pp_plan_id) { 200 selectedPlan = { 201 id: planId, 202 title: title, 203 price: price, 204 duration: duration, 205 recurring: recurring, 206 pp_plan_id: pp_plan_id 207 }; 208 209 checkUserLoginStatus().then(status => { 210 if (status.isLoggedIn) { 211 showSubscriptionPaymentModal(status.userInfo); 212 } else { 213 showRegistrationModal(); 214 } 215 }); 216} 217 218function checkUserLoginStatus() { 219 return fetch('https://www.theteacherscorner.net/member/registration_api.php', { 220 method: 'POST', 221 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 222 credentials: 'include', // Fix for cross-subdomain session cookies 223 body: 'action=check_login_status' 224 }) 225 .then(response => response.json()) 226 .then(data => { 227 console.log("Login Status Check:", data); // Added for debugging 228 return { isLoggedIn: data.success && data.logged_in, userInfo: data.user_info }; 229 }) 230 .catch(error => { 231 console.error('Error checking login status:', error); 232 return { isLoggedIn: false, userInfo: null }; 233 }); 234} 235 236async function showSubscriptionPaymentModal(userInfo) { 237 if (!selectedPlan) { 238 Swal.fire('Error', 'Please select a subscription plan first.', 'error'); 239 return; 240 } 241 242 // Create/update the cart on the backend before showing the payment modal 243 try { 244 const cartResponse = await fetch('https://www.theteacherscorner.net/member/registration_api.php', { 245 method: 'POST', 246 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 247 credentials: 'include', 248 body: `action=create_or_update_cart&user_id=${userInfo.user_id}&membership_id=${selectedPlan.id}` 249 }); 250 const cartData = await cartResponse.json(); 251 if (!cartData.success) { 252 throw new Error(cartData.message || 'Failed to update cart.'); 253 } 254 } catch (error) { 255 console.error("Cart update error:", error); 256 Swal.fire('Error', 'Could not prepare your transaction. Please try again.', 'error'); 257 return; 258 } 259 260 const recurringText = selectedPlan.recurring ? 'Yes (Cancel Anytime)' : 'No'; 261 262 Swal.fire({ 263 title: 'Complete Your Subscription', 264 html: ` 265 <div class="subscription-payment-form"> 266 <div class="form-section"> 267 <div class="selected-plan-summary"> 268 <h4>Selected Plan: ${selectedPlan.title}</h4> 269 <div class="plan-summary-details"> 270 <span>${selectedPlan.duration} ⢠Recurring: ${recurringText}</span> 271 <span class="plan-summary-price">${selectedPlan.price}</span> 272 </div> 273 </div> 274 </div> 275 <div class="form-section"> 276 <h3>Payment Method</h3> 277 <div id="ngrok-reminder-message" class="ngrok-reminder" style="display: none; margin-bottom: 15px; padding: 10px; background-color: #fff3cd; border: 1px solid #ffeeba; color: #856404; border-radius: 5px; font-family: monospace; font-size: 12px;"></div> 278 <div class="payment-methods" id="subscription-payment-methods-container"></div> 279 <div id="subscription-stripe-fields" class="stripe-fields"> 280 <div class="form-group"> 281 <label for="subscription-card-element">Card Information:</label> 282 <div id="subscription-card-element" class="stripe-card-element"></div> 283 <div id="subscription-card-errors" class="stripe-card-errors" role="alert"></div> 284 </div> 285 </div> 286 <div id="subscription-paypal-button-container" style="display: none; margin-top: 20px;"></div> 287 </div> 288 </div> 289 `, 290 footer: '<div class="back-to-login"><a href="#" onclick="event.preventDefault(); showPlanSelectionModal();">â Back to Plan Selection</a></div>', 291 showCancelButton: true, 292 confirmButtonText: 'Complete Payment', 293 cancelButtonText: 'Cancel', 294 confirmButtonColor: '#666633', 295 cancelButtonColor: '#999', 296 customClass: { popup: 'swal2-registration-modal' }, 297 width: '90%', 298 maxWidth: '600px', 299 didOpen: async () => { 300 payPalButtonsRendered = false; // Reset flag when modal opens 301 const submitBtn = Swal.getConfirmButton(); 302 if (submitBtn) { 303 submitBtn.disabled = true; 304 submitBtn.style.opacity = '0.5'; 305 submitBtn.style.cursor = 'not-allowed'; 306 } 307 populateSubscriptionPaymentMethods(); 308 initializeSubscriptionStripeElements(); 309 }, 310 preConfirm: () => { 311 return processSubscriptionPayment(); 312 } 313 }); 314} 315 316// Define event handler in a broader scope to prevent re-declaration 317function handlePaymentMethodClick(event, selectFunction) { 318 const target = event.target.closest('.payment-method'); 319 if (target) { 320 // Reset rendered flag whenever a new payment method is chosen 321 payPalButtonsRendered = false; 322 console.log("PayPal rendered flag reset to false."); 323 window[selectFunction](target.dataset.gatewayId, target.dataset.gatewayDir, false); 324 } 325} 326 327function populatePaymentMethodsGeneric(containerId, radioName, idPrefix, stripeFieldsId, validityCheckFunction, selectFunction) { 328 const container = document.getElementById(containerId); 329 if (!container || !selectedPlan) return; 330 331 // Use event delegation. Bind a specific instance of the handler. 332 const specificHandler = (event) => handlePaymentMethodClick(event, selectFunction); 333 334 // Clean up old listeners before adding new ones. 335 const oldHandler = container.currentHandler; 336 if (oldHandler) { 337 container.removeEventListener('click', oldHandler); 338 } 339 container.addEventListener('click', specificHandler); 340 container.currentHandler = specificHandler; // Store reference for cleanup 341 342 fetch('https://www.theteacherscorner.net/member/registration_api.php', { 343 method: 'POST', 344 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 345 credentials: 'include', 346 body: `action=get_gateways&membership_id=${selectedPlan.id}&recurring=${selectedPlan.recurring}` 347 }) 348 .then(response => response.json()) 349 .then(data => { 350 const ngrokReminderDiv = document.getElementById('ngrok-reminder-message'); 351 if (data.ngrok_reminder && ngrokReminderDiv) { 352 ngrokReminderDiv.innerHTML = data.ngrok_reminder; 353 ngrokReminderDiv.style.display = 'block'; 354 } else if (ngrokReminderDiv) { 355 ngrokReminderDiv.style.display = 'none'; 356 ngrokReminderDiv.innerHTML = ''; 357 } 358 359 if (data.success && data.gateways) { 360 let paymentMethodsHtml = ''; 361 data.gateways.forEach((gateway, index) => { 362 const isFirst = index === 0; 363 const checkedAttr = (data.gateways.length === 1 || isFirst) ? 'checked' : ''; 364 paymentMethodsHtml += ` 365 <div class="payment-method ${checkedAttr ? 'selected' : ''}" data-gateway-id="${gateway.id}" data-gateway-dir="${gateway.dir}"> 366 <input type="radio" name="${radioName}" value="${gateway.id}" id="${idPrefix}${gateway.id}" ${checkedAttr}> 367 <label for="${idPrefix}${gateway.id}">${gateway.displayname}</label> 368 <div style="font-size: 0.8em; color: #666; margin-top: 5px;">${gateway.description || ''}</div> 369 </div> 370 `; 371 }); 372 container.innerHTML = paymentMethodsHtml; 373 374 if (data.gateways.length > 0) { 375 setTimeout(() => { 376 const firstGateway = data.gateways[0]; 377 window[selectFunction](firstGateway.id, firstGateway.dir, true); 378 validityCheckFunction(); 379 }, 100); 380 } 381 } else { 382 container.innerHTML = '<div style="color: red;">No payment methods available for this plan.</div>'; 383 } 384 }) 385 .catch(error => { 386 console.error('Error loading payment methods:', error); 387 container.innerHTML = '<div style="color: red;">Error loading payment methods. Please try again.</div>'; 388 });
389} 390 391function populateSubscriptionPaymentMethods() { 392 populatePaymentMethodsGeneric('subscription-payment-methods-container', 'subscription_payment_method', 'sub_payment_', 'subscription-stripe-fields', checkSubscriptionFormValidity, 'selectSubscriptionPaymentMethod'); 393} 394 395function populatePaymentMethods() { 396 populatePaymentMethodsGeneric('payment-methods-container', 'payment_method', 'payment_', 'stripe-fields', checkFormValidity, 'selectPaymentMethodWithData'); 397} 398 399function selectPaymentMethodGeneric(gatewayId, gatewayDir, radioName, stripeFieldsId, validityCheckFunction, isAutoSelect = false, paymentData = {}) { 400 // Reset Stripe completeness state whenever a new method is selected 401 window.isStripeElementComplete = false; 402 403 const containerId = (radioName === 'payment_method') ? 'payment-methods-container' : 'subscription-payment-methods-container'; 404 document.querySelectorAll(`#${containerId} .payment-method`).forEach(el => el.classList.remove('selected')); 405 406 const selectedDiv = document.querySelector(`#${containerId} .payment-method[data-gateway-id='${gatewayId}']`); 407 if (selectedDiv) { 408 selectedDiv.classList.add('selected'); 409 } 410 411 const radioButton = document.querySelector(`input[name="${radioName}"][value="${gatewayId}"]`); 412 if (radioButton) radioButton.checked = true; 413 414 const stripeFields = document.getElementById(stripeFieldsId); 415 const payPalContainerId = (stripeFieldsId === 'stripe-fields') ? 'paypal-button-container' : 'subscription-paypal-button-container'; 416 const payPalContainer = document.getElementById(payPalContainerId); 417 const confirmBtn = Swal.getConfirmButton(); 418 419 if (stripeFields && payPalContainer) { 420 if (gatewayDir === 'stripe') { 421 stripeFields.style.display = 'block'; 422 stripeFields.classList.add('active'); 423 payPalContainer.style.display = 'none'; 424 if(confirmBtn) confirmBtn.style.display = 'block'; 425 } else if (gatewayDir === 'paypal_modal') { 426 stripeFields.style.display = 'none'; 427 stripeFields.classList.remove('active'); 428 payPalContainer.style.display = 'block'; 429 if(confirmBtn) confirmBtn.style.display = 'none'; 430 const isSubscription = (stripeFieldsId === 'subscription-stripe-fields'); 431 432 // For new registrations, paymentData is passed down from processRegistrationAndPayment 433 // For existing users, we need to fetch their ID. 434 let finalPaymentData = paymentData; 435 if (isSubscription && !finalPaymentData.user_id) { 436 const status = checkUserLoginStatus(); 437 status.then(s => { 438 if(s.isLoggedIn) { 439 finalPaymentData.user_id = s.userInfo.user_id; 440 handlePayPalPayment(finalPaymentData, isSubscription); 441 } 442 }); 443 } else { 444 handlePayPalPayment(finalPaymentData, isSubscription); 445 } 446 447 } else { // for legacy paypal, etc. 448 stripeFields.style.display = 'none'; 449 stripeFields.classList.remove('active'); 450 payPalContainer.style.display = 'none'; 451 if(confirmBtn) confirmBtn.style.display = 'block'; 452 } 453 } 454 validityCheckFunction(); 455} 456 457function selectSubscriptionPaymentMethod(gatewayId, gatewayDir, isAutoSelect = false) { 458 selectPaymentMethodGeneric(gatewayId, gatewayDir, 'subscription_payment_method', 'subscription-stripe-fields', checkSubscriptionFormValidity, isAutoSelect); 459} 460 461function selectPaymentMethodWithData(gatewayId, gatewayDir, isAutoSelect = false) { 462 selectPaymentMethodGeneric(gatewayId, gatewayDir, 'payment_method', 'stripe-fields', checkFormValidity, isAutoSelect, { /* empty payment data for now */ }); 463} 464 465function checkSubscriptionFormValidity() { 466 const submitBtn = Swal.getConfirmButton(); 467 if (!submitBtn) return; 468 469 const paymentMethod = document.querySelector('input[name="subscription_payment_method"]:checked'); 470 if (!paymentMethod) { 471 submitBtn.disabled = true; 472 return; 473 } 474 475 const stripeFields = document.getElementById('subscription-stripe-fields');
476 if (stripeFields && stripeFields.classList.contains('active')) { 477 const cardErrors = document.getElementById('subscription-card-errors'); 478 const hasErrors = cardErrors && cardErrors.textContent.trim() !== ''; 479 const cardElementContainer = document.getElementById('subscription-card-element'); 480 if (hasErrors || !cardElementContainer || !cardElementContainer.hasChildNodes()) { 481 submitBtn.disabled = true; 482 return; 483 } 484 } 485 submitBtn.disabled = false; 486} 487 488async function processSubscriptionPayment() { 489 const paymentMethod = document.querySelector('input[name="subscription_payment_method"]:checked'); 490 if (!paymentMethod) { 491 Swal.showValidationMessage('Please select a payment method'); 492 return false; 493 } 494 495 const stripeFields = document.getElementById('subscription-stripe-fields'); 496 const isStripeWithFields = stripeFields && stripeFields.classList.contains('active'); 497 498 if (isStripeWithFields) { 499 try { 500 if (!subscriptionStripe || !subscriptionCardElement) { 501 Swal.showValidationMessage('Stripe payment system not initialized'); 502 return false; 503 } 504 const {token, error} = await subscriptionStripe.createToken(subscriptionCardElement); 505 if (error) { 506 Swal.showValidationMessage(error.message); 507 return false; 508 } 509 if (!token) { 510 Swal.showValidationMessage('Failed to create payment token'); 511 return false; 512 } 513 processLoggedInStripePaymentWithToken(paymentMethod.value, token.id); 514 } catch (error) { 515 console.error('Stripe tokenization error:', error); 516 Swal.showValidationMessage('Payment processing error. Please try again.'); 517 return false; 518 } 519 } 520 return false; 521} 522 523// Helper function to save form data to sessionStorage 524function saveRegistrationDataToSession() { 525 try { 526 const formData = { 527 fname: document.getElementById('reg_fname')?.value || '', 528 lname: document.getElementById('reg_lname')?.value || '', 529 username: document.getElementById('reg_username')?.value || '', 530 email: document.getElementById('reg_email')?.value || '', 531 confirm_email: document.getElementById('reg_confirm_email')?.value || '', 532 // Do NOT store password fields 533 }; 534 sessionStorage.setItem('ttcRegistrationData', JSON.stringify(formData)); 535 } catch (error) { 536 console.error("Error saving registration data to session storage:", error); 537 } 538} 539 540// Helper function to restore form data from sessionStorage 541function restoreRegistrationDataFromSession() { 542 try { 543 const savedDataJSON = sessionStorage.getItem('ttcRegistrationData'); 544 if (!savedDataJSON) return; 545 546 const savedData = JSON.parse(savedDataJSON); 547 548 if (document.getElementById('reg_fname')) document.getElementById('reg_fname').value = savedData.fname || ''; 549 if (document.getElementById('reg_lname')) document.getElementById('reg_lname').value = savedData.lname || ''; 550 if (document.getElementById('reg_username')) document.getElementById('reg_username').value = savedData.username || ''; 551 if (document.getElementById('reg_email')) document.getElementById('reg_email').value = savedData.email || ''; 552 if (document.getElementById('reg_confirm_email')) document.getElementById('reg_confirm_email').value = savedData.confirm_email || ''; 553 554 // Re-trigger validation for all fields that were restored 555 setTimeout(() => { 556 Object.keys(savedData).forEach(key => { 557 if (savedData[key] && typeof validateField === 'function') { 558 const fieldName = key.replace('reg_', ''); 559 if (['fname', 'lname', 'username', 'email', 'confirm_email'].includes(fieldName)) { 560 validateField(fieldName, savedData[key]); 561 } 562 } 563 }); 564 }, 250); 565 } catch (error) { 566 console.error("Error restoring registration data from session storage:", error); 567 // If data is corrupt, remove it 568 sessionStorage.removeItem('ttcRegistrationData'); 569 } 570} 571 572function showRegistrationModal() { 573 if (!selectedPlan) { 574 Swal.fire('Error', 'Please select a subscription plan first.', 'error'); 575 return; 576 } 577 578 const recurringText = selectedPlan.recurring ? 'Yes (Cancel Anytime)' : 'No'; 579 580 Swal.fire({ 581 title: 'Complete Your Registration', 582 html: ` 583 <div class="registration-payment-form"> 584 <div class="form-section"> 585 <div class="selected-plan-summary"> 586 <h4>Selected Plan: ${selectedPlan.title}</h4> 587 <div class="plan-summary-details"> 588 <span>${selectedPlan.duration} ⢠Recurring: ${recurringText}</span> 589 <span class="plan-summary-price">${selectedPlan.price}</span> 590 </div> 591 </div> 592 </div> 593 <div class="form-section"> 594 <h3>Account Information</h3> 595 <div class="form-group"><label for="reg_email">Email Address:</label><input type="email" id="reg_email" name="email" autocomplete="off" required><div id="email_validation" class="validation-message"></div></div> 596 <div class="form-group"><label for="reg_confirm_email">Confirm Email:</label><input type="email" id="reg_confirm_email" name="confirm_email" autocomplete="off" required><div id="confirm_email_validation" class="validation-message"></div></div> 597 <div class="form-row"> 598 <div class="form-group">
598<label for="reg_fname">First Name:</label><input type="text" id="reg_fname" name="fname" required></div> 599 <div class="form-group"><label for="reg_lname">Last Name:</label><input type="text" id="reg_lname" name="lname" required></div> 600 </div> 601 <div class="form-group"><label for="reg_username">Username:</label><input type="text" id="reg_username" name="username" autocomplete="off" required><div id="username_validation" class="validation-message"></div></div> 602 <div class="form-row"> 603 <div class="form-group"><label for="reg_password">Password:</label><input type="password" id="reg_password" name="password" required><div id="password_validation" class="validation-message"></div></div> 604 <div class="form-group"><label for="reg_password2">Confirm Password:</label><input type="password" id="reg_password2" name="password2" autocomplete="off" required><div id="password2_validation" class="validation-message"></div></div> 605 </div> 606 </div> 607 <div class="form-section"> 608 <h3>Payment Method</h3> 609 <div id="ngrok-reminder-message" class="ngrok-reminder" style="display: none; margin-bottom: 15px; padding: 10px; background-color: #fff3cd; border: 1px solid #ffeeba; color: #856404; border-radius: 5px; font-family: monospace; font-size: 12px;"></div> 610 <div class="payment-methods" id="payment-methods-container"></div> 611 <div id="stripe-fields" class="stripe-fields"> 612 <div class="form-group"><label for="registration-card-element">Card Information:</label><div id="registration-card-element" class="stripe-card-element"></div><div id="registration-card-errors" class="stripe-card-errors" role="alert"></div></div> 613 </div> 614 <div id="paypal-button-container" style="display: none; margin-top: 20px;"></div> 615 </div> 616 </div> 617 `, 618 footer: '<div class="back-to-login"><a href="#" onclick="event.preventDefault(); showPlanSelectionModal();">â Back to Plan Selection</a></div>', 619 showCancelButton: true, 620 confirmButtonText: 'Complete Registration & Payment', 621 cancelButtonText: 'Cancel', 622 confirmButtonColor: '#666633', 623 cancelButtonColor: '#999', 624 customClass: { popup: 'swal2-registration-modal' }, 625 width: '90%', 626 maxWidth: '600px', 627 didOpen: async () => { 628 payPalButtonsRendered = false; // Reset flag when modal opens 629 const submitBtn = Swal.getConfirmButton(); 630 if (submitBtn) { 631 submitBtn.disabled = true; 632 submitBtn.style.opacity = '0.5'; 633 submitBtn.style.cursor = 'not-allowed'; 634 } 635 populatePaymentMethods(); 636 initializeStripeElements(); 637 setTimeout(() => { 638 addFormValidationListeners(); 639 restoreRegistrationDataFromSession(); // Restore data on open 640 641 // Add listeners to save subsequent changes to session storage 642 const form = document.querySelector('.swal2-html-container .registration-payment-form'); 643 if (form) { 644 form.addEventListener('input', (e) => { 645 // Only save for text and email inputs, ignore passwords 646 if (e.target.matches('input[type="text"], input[type="email"]')) { 647 saveRegistrationDataToSession(); 648 } 649 }); 650 } 651 }, 200); 652 }, 653 preConfirm: () => { 654 return processRegistrationAndPayment(); 655 } 656 }); 657 setTimeout(() => setupRealTimeValidation(), 100); 658} 659 660async function processRegistrationAndPayment() { 661 saveFormData(); 662 663 const formData = new FormData(); 664 formData.append('fname', document.getElementById('reg_fname').value); 665 formData.append('lname', document.getElementById('reg_lname').value); 666 formData.append('username', document.getElementById('reg_username').value); 667 formData.append('email', document.getElementById('reg_email').value); 668 formData.append('confirm_email', document.getElementById('reg_confirm_email').value); 669 formData.append('password', document.getElementById('reg_password').value); 670 formData.append('password2', document.getElementById('reg_password2').value); 671 formData.append('membership_id', selectedPlan.id); 672 673 const paymentMethod = document.querySelector('input[name="payment_method"]:checked'); 674 if (!paymentMethod) { 675 Swal.showValidationMessage('Please select a payment method'); 676 return false; 677 } 678 formData.append('payment_method', paymentMethod.value); 679 680 const stripeFields = document.getElementById('stripe-fields');
681 const isStripeWithFields = stripeFields && stripeFields.classList.contains('active'); 682 683 if (isStripeWithFields) { 684 try { 685 if (!stripe || !cardElement) { 686 Swal.showValidationMessage('Stripe payment system not initialized'); 687 return false; 688 } 689 const {token, error} = await stripe.createToken(cardElement); 690 if (error) { 691 Swal.showValidationMessage(error.message); 692 return false; 693 } 694 if (!token) { 695 Swal.showValidationMessage('Failed to create payment token'); 696 return false; 697 } 698 formData.append('stripe_token', token.id); 699 formData.append('process_stripe_direct', '1'); 700 } catch (error) { 701 console.error('Stripe tokenization error:', error); 702 Swal.showValidationMessage('Payment processing error. Please try again.'); 703 return false; 704 } 705 } 706 707 Swal.fire({ 708 title: 'Processing...', 709 text: 'Creating your account and processing payment...', 710 allowOutsideClick: false, 711 allowEscapeKey: false, 712 showConfirmButton: false, 713 didOpen: () => Swal.showLoading() 714 }); 715 716 formData.append('action', 'register_user'); 717 718 fetch('https://www.theteacherscorner.net/member/registration_api.php', { 719 method: 'POST', 720 credentials: 'include', // Fix for cross-subdomain session cookies 721 body: formData 722 }) 723 .then(response => response.json()) 724 .then(data => { 725 if (data.success) { 726 sessionStorage.removeItem('ttcRegistrationData'); // Clear saved form data on success 727 updatePageForLoggedInUser(); 728 if (data.gateway_dir === 'paypal_modal') { 729 // The user is registered, now show the PayPal buttons to complete payment. 730 // We hide the main submit button and show the PayPal container. 731 const confirmBtn = Swal.getConfirmButton(); 732 if(confirmBtn) confirmBtn.style.display = 'none'; 733 734 const paypalContainer = document.getElementById('paypal-button-container'); 735 if(paypalContainer) paypalContainer.style.display = 'block'; 736 737 // We call handlePayPalPayment with the data from the registration response, 738 // which now includes user_id and total_price. 739 handlePayPalPayment(data, false); 740 } else if (data.requires_redirect) { 741 handleLegacyPayPalPayment(data); 742 } else if (data.requires_gateway_form || data.gateway_dir === 'stripe') { 743 processStripePaymentDirectWithToken(data); 744 } else { 745 handleGatewayForm(data); 746 } 747 } else { 748 Swal.fire('Error', data.message, 'error').then(() => showRegistrationModalWithData()); 749 } 750 }) 751 .catch(error => { 752 Swal.fire('Error', 'An error occurred. Please try again.', 'error').then(() => showRegistrationModalWithData()); 753 }); 754 755 return false; 756} 757 758function saveFormData() { 759 savedFormData = { 760 fname: document.getElementById('reg_fname')?.value || '', 761 lname: document.getElementById('reg_lname')?.value || '', 762 username: document.getElementById('reg_username')?.value || '', 763 email: document.getElementById('reg_email')?.value || '', 764 confirm_email: document.getElementById('reg_confirm_email')?.value || '', 765 payment_method: document.querySelector('input[name="payment_method"]:checked')?.value || '' 766 }; 767} 768 769function showRegistrationModalWithData() { 770 showRegistrationModal(); 771 setTimeout(() => restoreFormData(), 100); 772} 773 774function restoreFormData() { 775 if (Object.keys(savedFormData).length === 0) return; 776 777 Object.keys(savedFormData).forEach(key => { 778 const field = document.getElementById('reg_' + key) || document.getElementById(key); 779 if (field && savedFormData[key]) { 780 field.value = savedFormData[key]; 781 } 782 }); 783 784 if (savedFormData.payment_method) { 785 const paymentRadio = document.getElementById('payment_' + savedFormData.payment_method); 786 if (paymentRadio) { 787 paymentRadio.checked = true; 788 } 789 } 790 791 setTimeout(() => { 792 setupRealTimeValidation(); 793 Object.keys(savedFormData).forEach(key => { 794 if (savedFormData[key] && key !== 'payment_method') { 795 const fieldName = key.replace('reg_', ''); 796 if (['fname', 'lname', 'username', 'email', 'confirm_email', 'password', 'password2'].includes(fieldName)) { 797 validateField(fieldName, savedFormData[key]); 798 } 799 } 800 }); 801 }, 200); 802} 803 804function updatePageForLoggedInUser() { 805 const loginBtn = document.querySelector('.login-btn, .register-btn, [onclick*="showLoginModal"], [onclick*="showRegisterModal"]'); 806 if (loginBtn) loginBtn.style.display = 'none'; 807 808 const userMenu = document.querySelector('.user-menu, .account-menu, .logged-in-menu'); 809 if (userMenu) userMenu.style.display = 'block'; 810 811 const loginStatus = document.querySelector('.login-status'); 812 if (loginStatus) loginStatus.textContent = 'Logged In'; 813} 814 815function showPaymentRetryModal(userId, membershipId, errorMessage) { 816 fetch('https://www.theteacherscorner.net/member/registration_api.php', { 817 method: 'POST', 818 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 819 credentials: 'include', // Fix for cross-subdomain session cookies 820 body: `action=get_plans` 821 }) 822 .then(response => response.json()) 823 .then(data => { 824 if (data.success) { 825 const membership = data.plans.find(plan => plan.id == membershipId); 826 if (!membership) { 827 Swal.fire('Error', 'Membership plan not found', 'error'); 828 return; 829 } 830 displayPaymentRetryModal(userId, membership, errorMessage); 831 } else { 832 Swal.fire('Error', 'Failed to load membership details', 'error'); 833 } 834 }) 835 .catch(error => { 836 Swal.fire('Error', 'Failed to load membership details. Please try again.', 'error'); 837 }); 838} 839 840function displayPaymentRetryModal(userId, membership, errorMessage) { 841 const isRecurring = Boolean(membership.recurring && membership.recurring !== "0" && membership.recurring !== 0); 842 const recurringText = isRecurring ? 'Yes (Cancel Anytime)' : 'No'; 843 844 Swal.fire({ 845 title: 'Payment Failed - Try Again', 846 html: ` 847 <div class="payment-retry-form"> 848 <div class="error-message" style="background-color: #ffebee; color: #c62828; padding: 15px; border-radius: 5px; margin-bottom: 20px;"> 849 <strong>Payment Error:</strong> ${errorMessage} 850 </div> 851 <div class="form-section"> 852 <div class="selected-plan-summary"> 853 <h4>Selected Plan: ${membership.title}</h4> 854 <div class="plan-summary-details"> 855 <span>${membership.days} ${membership.period} ⢠Recurring: ${recurringText}</span> 856 <span class="plan-summary-price">${membership.price}</span> 857 </div> 858 </div> 859 </div> 860 <div class="form-section"> 861 <h3>Choose Payment Method</h3> 862 <div class="payment-methods" id="retry-payment-methods-container"></div> 863 <div id="retry-stripe-fields" class="stripe-fields"> 864 <div class="form-group"><label for="retry-card-element">Card Information:</label><div id="retry-card-element" class="stripe-card-element"></div><div id="retry-card-errors" class="stripe-card-errors" role="alert"></div></div> 865 </div> 866 </div> 867 </div> 868 `, 869 showCancelButton: true, 870 confirmButtonText: 'Retry Payment', 871 cancelButtonText: 'Cancel', 872 confirmButtonColor: '#666633', 873 cancelButtonColor: '#999', 874 customClass: { popup: 'swal2-payment-retry-modal' }, 875 width: '90%', 876 maxWidth: '500px', 877 didOpen: async () => { 878 const submitBtn = Swal.getConfirmButton(); 879 if (submitBtn) { 880 submitBtn.disabled = true; 881 submitBtn.style.opacity = '0.5'; 882 submitBtn.style.cursor = 'not-allowed'; 883 } 884 populateRetryPaymentMethods(membership.id, membership.recurring); 885 initializeRetryStripeElements(); 886 }, 887 preConfirm: () => { 888 return processPaymentRetry(userId, membership.id); 889 } 890 }); 891} 892 893function populateRetryPaymentMethods(membershipId, isRecurring) { 894 const container = document.getElementById('retry-payment-methods-container'); 895 if (!container) return; 896 897 fetch('https://www.theteacherscorner.net/member/registration_api.php', { 898 method: 'POST', 899 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 900 credentials: 'include', // Fix for cross-subdomain session cookies 901 body: `action=get_gateways&membership_id=${membershipId}&recurring=${isRecurring}` 902 }) 903 .then(response => response.json()) 904 .then(data => { 905 if (data.success && data.gateways) { 906 let paymentMethodsHtml = ''; 907 data.gateways.forEach((gateway, index) => { 908 const isFirst = index === 0; 909 const checkedAttr = (data.gateways.length === 1 || isFirst) ? 'checked' : ''; 910 paymentMethodsHtml += ` 911 <div class="payment-method ${checkedAttr ? 'selected' : ''}" data-gateway-id="${gateway.id}" data-gateway-dir="${gateway.dir}"> 912 <input type="radio" name="retry_payment_method" value="${gateway.id}" id="retry_payment_${gateway.id}" ${checkedAttr}> 913 <label for="retry_payment_${gateway.id}">${gateway.displayname}</label> 914 <div style="font-size: 0.8em; color: #666; margin-top: 5px;">${gateway.description || ''}</div> 915 </div> 916 `; 917 }); 918 container.innerHTML = paymentMethodsHtml; 919 920 // Use event delegation for the retry modal as well 921 container.removeEventListener('click', handleRetryPaymentClick); 922 container.addEventListener('click', handleRetryPaymentClick); 923 924 function handleRetryPaymentClick(event) { 925 const target = event.target.closest('.payment-method'); 926 if (target) { 927 selectRetryPaymentMethod(target.dataset.gatewayId, target.dataset.gatewayDir); 928 } 929 } 930 931 if (data.gateways.length > 0) { 932 setTimeout(() => { 933 const firstGateway = data.gateways[0]; 934 selectRetryPaymentMethod(firstGateway.id, firstGateway.dir, true); 935 checkRetryFormValidity(); 936 }, 100); 937 } 938 } else { 939 container.innerHTML = '<div style="color: red;">No payment methods available for this plan.</div>'; 940 } 941 }) 942 .catch(error => { 943 console.error('Error loading payment methods:', error); 944 container.innerHTML = '<div style="color: red;">Error loading payment methods. Please try again.</div>'; 945 }); 946} 947 948function selectRetryPaymentMethod(methodId, gatewayDir, isAutoSelect = false) { 949 document.querySelectorAll('#retry-payment-methods-container .payment-method').forEach(el => el.classList.remove('selected')); 950 951 const selectedDiv = document.querySelector(`#retry-payment-methods-container .payment-method[data-gateway-id="${methodId}"]`); 952 if (selectedDiv) { 953 selectedDiv.classList.add('selected'); 954 } 955 956 const radioButton = document.getElementById('retry_payment_' + methodId); 957 if (radioButton) radioButton.checked = true; 958 959 const stripeFields = document.getElementById('retry-stripe-fields'); 960 if (stripeFields) { 961 if (gatewayDir === 'stripe') {
962 stripeFields.classList.add('active'); 963 } else { 964 stripeFields.classList.remove('active'); 965 } 966 } 967 checkRetryFormValidity(); 968} 969 970function checkRetryFormValidity() { 971 const submitBtn = Swal.getConfirmButton(); 972 if (!submitBtn) return; 973 974 const paymentMethod = document.querySelector('input[name="retry_payment_method"]:checked'); 975 if (!paymentMethod) { 976 submitBtn.disabled = true; 977 return; 978 } 979 980 const stripeFields = document.getElementById('retry-stripe-fields'); 981 if (stripeFields && stripeFields.classList.contains('active')) { 982 const cardErrors = document.getElementById('retry-card-errors'); 983 const hasErrors = cardErrors && cardErrors.textContent.trim() !== ''; 984 const cardElementContainer = document.getElementById('retry-card-element'); 985 if (hasErrors || !cardElementContainer || !cardElementContainer.hasChildNodes()) { 986 submitBtn.disabled = true; 987 return; 988 } 989 } 990 submitBtn.disabled = false; 991} 992 993async function processPaymentRetry(userId, membershipId) { 994 const paymentMethod = document.querySelector('input[name="retry_payment_method"]:checked'); 995 if (!paymentMethod) { 996 Swal.showValidationMessage('Please select a payment method'); 997 return false; 998 } 999 1000 const stripeFields = document.getElementById('retry-stripe-fields'); 1001 const isStripeWithFields = stripeFields && stripeFields.classList.contains('active'); 1002 1003 if (isStripeWithFields) { 1004 try { 1005 if (!retryStripe || !retryCardElement) { 1006 Swal.showValidationMessage('Stripe payment system not initialized'); 1007 return false; 1008 } 1009 const {token, error} = await retryStripe.createToken(retryCardElement); 1010 if (error) { 1011 Swal.showValidationMessage(error.message); 1012 return false; 1013 } 1014 if (!token) { 1015 Swal.showValidationMessage('Failed to create payment token'); 1016 return false; 1017 } 1018 processRetryStripePaymentWithToken(userId, membershipId, paymentMethod.value, token.id); 1019 } catch (error) { 1020 console.error('Stripe tokenization error:', error); 1021 Swal.showValidationMessage('Payment processing error. Please try again.'); 1022 return false; 1023 } 1024 } else { 1025 handleRetryPayPalPayment(userId, membershipId, paymentMethod.value); 1026 } 1027 return false; 1028} 1029 1030function handleRetryPayPalPayment(userId, membershipId, gatewayId) { 1031 Swal.fire({ 1032 title: 'Redirecting to PayPal...', 1033 text: 'Please wait while we prepare your payment.', 1034 allowOutsideClick: false, 1035 didOpen: () => Swal.showLoading() 1036 }); 1037 1038 fetch('https://www.theteacherscorner.net/member/registration_api.php', { 1039 method: 'POST', 1040 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 1041 credentials: 'include', // Fix for cross-subdomain session cookies 1042 body: `action=get_paypal_url&user_id=${userId}&membership_id=${membershipId}&gateway_id=${gatewayId}` 1043 }) 1044 .then(response => response.json()) 1045 .then(formData => { 1046 if (formData.success) { 1047 if (formData.paypal_form_html) { 1048 Swal.fire({ 1049 title: 'Redirecting to PayPal...', 1050 text: 'You are logged in and will be redirected to PayPal to complete payment...', 1051 icon: 'info', 1052 timer: 2000,
1053 showConfirmButton: false 1054 }).then(() => { 1055 const formContainer = document.createElement('div'); 1056 formContainer.style.display = 'none'; 1057 formContainer.innerHTML = formData.paypal_form_html; 1058 document.body.appendChild(formContainer); 1059 setTimeout(() => { 1060 const paypalForm = document.getElementById('pp_form'); 1061 if (paypalForm) paypalForm.submit(); 1062 }, 500); 1063 }); 1064 } else if (formData.paypal_url) { 1065 Swal.fire({ 1066 title: 'Redirecting to PayPal...', 1067 text: 'You are logged in and will be redirected to PayPal to complete payment...', 1068 icon: 'info', 1069 timer: 3000, 1070 showConfirmButton: false 1071 }).then(() => { 1072 window.location.href = formData.paypal_url; 1073 }); 1074 } else { 1075 Swal.fire('Error', 'Invalid PayPal response format', 'error'); 1076 } 1077 } else { 1078 Swal.fire('Error', formData.message || 'Failed to generate PayPal form', 'error'); 1079 } 1080 }) 1081 .catch(error => { 1082 console.error('PayPal form request failed:', error); 1083 Swal.fire('Error', 'Failed to setup PayPal payment. Please try again.', 'error'); 1084 }); 1085} 1086 1087 1088function pollForSessionAndReload(userId) { 1089 const pollInterval = 2000; // 2 seconds 1090 const pollTimeout = 40000; // 40 seconds 1091 let pollTimer; 1092 let timeoutTimer; 1093 1094 const cleanup = () => { 1095 clearInterval(pollTimer); 1096 clearTimeout(timeoutTimer); 1097 }; 1098 1099 const finalizeAndReload = () => { 1100 cleanup(); 1101 updatePageForLoggedInUser(); 1102 Swal.fire({ 1103 title: 'Account Activated!', 1104 text: 'Your subscription is active. You will now be logged in.', 1105 icon: 'success', 1106 timer: 3000,
1107 showConfirmButton: false 1108 }).then(() => { 1109 window.location.reload(); 1110 }); 1111 }; 1112 1113 // Show waiting modal 1114 Swal.fire({ 1115 title: 'Finalizing Your Account...', 1116 html: 'Waiting to receive credentials, please wait.', 1117 allowOutsideClick: false, 1118 allowEscapeKey: false, 1119 showConfirmButton: false, 1120 didOpen: () => { 1121 Swal.showLoading(); 1122 } 1123 }); 1124 1125 pollTimer = setInterval(() => { 1126 const payload = new URLSearchParams(); 1127 payload.append('action', 'check_payment_status'); 1128 payload.append('user_id', userId); 1129 1130 fetch('https://www.theteacherscorner.net/member/registration_api.php', { 1131 method: 'POST', 1132 headers: { 1133 'Content-Type': 'application/x-www-form-urlencoded', 1134 }, 1135 credentials: 'include', 1136 body: payload.toString() 1137 }) 1138 .then(res => res.json()) 1139 .then(data => { 1140 if (data && data.success && (data.paid || data.is_paid)) { 1141 finalizeAndReload(); 1142 } 1143 }) 1144 .catch(err => { 1145 console.error('Polling error:', err); 1146 // Continue polling and rely on timeout to notify the user 1147 }); 1148 }, pollInterval); 1149 1150 timeoutTimer = setTimeout(() => { 1151 cleanup(); 1152 Swal.fire({ 1153 title: 'It is taking longer than usual', 1154 html: 'It is taking longer than usual to receive your payment.<br><br>Please try to log in in 1 minute, then EMAIL so we can rectify: <a href="mailto:[email protected]">[email protected]</a>, THEN leave a message on our voicemail: +1 (720) 663-8322<br><br>We sincerely apologize for the inconvenience and appreciate your patience.', 1155 icon: 'info', 1156 confirmButtonText: 'OK', 1157 confirmButtonColor: '#666633' 1158 }); 1159 }, pollTimeout); 1160}
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.