1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Checkout - Smart Clinician</title> 7 <style> 8 * { 9 margin: 0; 10 padding: 0; 11 box-sizing: border-box; 12 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 13 } 14 15 body { 16 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 17 min-height: 100vh; 18 padding: 40px 20px; 19 } 20 21 .container { 22 max-width: 900px; 23 margin: 0 auto; 24 background: white; 25 border-radius: 20px; 26 box-shadow: 0 20px 60px rgba(0,0,0,0.3); 27 overflow: hidden; 28 } 29 30 .header { 31 background: linear-gradient(135deg, #1a73e8 0%, #4285f4 100%); 32 color: white; 33 padding: 30px 40px; 34 text-align: center; 35 } 36 37 .header h1 { 38 font-size: 32px; 39 margin-bottom: 10px; 40 } 41 42 .header p { 43 opacity: 0.9; 44 font-size: 16px; 45 } 46 47 .content { 48 display: grid; 49 grid-template-columns: 1fr 1fr; 50 gap: 40px; 51 padding: 40px; 52 } 53 54 .plan-summary { 55 border-right: 2px solid #e8eaed; 56 padding-right: 40px; 57 } 58 59 .plan-summary h2 { 60 font-size: 24px; 61 margin-bottom: 20px; 62 color: #202124; 63 } 64 65 .plan-card { 66 background: #f8f9fa; 67 border-radius: 12px; 68 padding: 25px; 69 margin-bottom: 20px; 70 } 71 72 .plan-name { 73 font-size: 28px; 74 font-weight: 700; 75 color: #1a73e8; 76 margin-bottom: 10px; 77 } 78 79 .plan-price { 80 font-size: 42px; 81 font-weight: 700; 82 color: #202124; 83 margin-bottom: 5px; 84 } 85 86 .plan-period { 87 color: #5f6368; 88 margin-bottom: 20px; 89 } 90 91 .features-list { 92 list-style: none; 93 margin-top: 20px; 94 } 95 96 .features-list li { 97 padding: 10px 0; 98 border-bottom: 1px solid #e8eaed; 99 display: flex; 100 align-items: center; 101 gap: 10px; 102 } 103 104 .features-list li:last-child { 105 border-bottom: none; 106 } 107 108 .check-icon { 109 color: #34a853; 110 font-weight: bold; 111 } 112 113 .payment-section h2 { 114 font-size: 24px; 115 margin-bottom: 20px; 116 color: #202124; 117 } 118 119 .discount-section { 120 background: #e8f0fe; 121 border-radius: 12px; 122 padding: 20px; 123 margin-bottom: 25px; 124 } 125 126 .discount-section h3 { 127 font-size: 16px; 128 margin-bottom: 15px; 129 color: #1a73e8; 130 } 131 132 .discount-input-group { 133 display: flex; 134 gap: 10px; 135 } 136 137 .discount-input-group input { 138 flex: 1; 139 padding: 12px; 140 border: 1px solid #dadce0; 141 border-radius: 8px; 142 font-size: 14px; 143 text-transform: uppercase; 144 } 145 146 .discount-input-group button { 147 padding: 12px 24px; 148 background: #1a73e8; 149 color: white; 150 border: none; 151 border-radius: 8px; 152 font-weight: 600; 153 cursor: pointer; 154 transition: all 0.3s; 155 } 156 157 .discount-input-group button:hover { 158 background: #0d47a1; 159 } 160 161 .discount-applied { 162 background: #e6f4ea; 163 border: 1px solid #34a853; 164 border-radius: 8px; 165 padding: 15px; 166 margin-top: 15px; 167 display: none; 168 } 169 170 .discount-applied.show { 171 display: block; 172 } 173 174 .price-breakdown { 175 background: #f8f9fa; 176 border-radius: 12px; 177 padding: 20px; 178 margin-bottom: 25px; 179 } 180 181 .price-row { 182 display: flex; 183 justify-content: space-between; 184 padding: 10px 0; 185 font-size: 16px; 186 } 187 188 .price-row.total { 189 border-top: 2px solid #dadce0; 190 margin-top: 10px; 191 padding-top: 15px; 192 font-size: 24px; 193 font-weight: 700; 194 color: #1a73e8; 195 } 196 197 .discount-row { 198 color: #34a853; 199 font-weight: 600; 200 } 201 202 #paypal-button-container { 203 margin: 30px 0; 204 } 205 206 .security-note { 207 text-align: center; 208 color: #5f6368; 209 font-size: 13px; 210 margin-top: 20px; 211 } 212 213 .loading { 214 text-align: center; 215 padding: 40px; 216 color: #5f6368; 217 } 218 219 .spinner { 220 border: 3px solid #f3f3f3; 221 border-top: 3px solid #1a73e8; 222 border-radius: 50%; 223 width: 40px; 224 height: 40px; 225 animation: spin 1s linear infinite; 226 margin: 0 auto 20px; 227 } 228 229 @keyframes spin { 230 0% { transform: rotate(0deg); } 231 100% { transform: rotate(360deg); } 232 } 233 234 .alert { 235 padding: 15px; 236 border-radius: 8px; 237 margin-bottom: 20px; 238 } 239 240 .alert-error { 241 background: #fce8e6; 242 color: #d93025; 243 border: 1px solid #d93025; 244 } 245 246 .alert-success { 247 background: #e6f4ea; 248 color: #1e8e3e; 249 border: 1px solid #1e8e3e; 250 } 251 252 @media (max-width: 768px) { 253 .content { 254 grid-template-columns: 1fr; 255 gap: 30px; 256 } 257 258 .plan-summary {
259 border-right: none; 260 border-bottom: 2px solid #e8eaed; 261 padding-right: 0; 262 padding-bottom: 30px; 263 } 264 } 265 </style> 266</head> 267<body> 268 <div class="container"> 269 <div class="header"> 270 <h1>𩺠Smart Clinician</h1> 271 <p>Complete your subscription</p> 272 </div> 273 274 <div class="content"> 275 <!-- Plan Summary --> 276 <div class="plan-summary"> 277 <h2>Plan Summary</h2> 278 <div id="plan-info" class="loading"> 279 <div class="spinner"></div> 280 <p>Loading plan details...</p> 281 </div> 282 </div> 283 284 <!-- Payment Section --> 285 <div class="payment-section"> 286 <h2>Payment Details</h2> 287 288 <div id="alert"></div> 289 290 <!-- Discount Code --> 291 <div class="discount-section"> 292 <h3>ð Have a discount code?</h3> 293 <div class="discount-input-group"> 294 <input type="text" id="discount-code" placeholder="Enter code (e.g., LAUNCH50)" maxlength="50"> 295 <button onclick="applyDiscount()">Apply</button> 296 </div> 297 <div id="discount-applied" class="discount-applied"> 298 <strong>â Discount applied:</strong> <span id="discount-details"></span> 299 </div> 300 </div> 301 302 <!-- Price Breakdown --> 303 <div class="price-breakdown"> 304 <div class="price-row"> 305 <span>Subtotal:</span> 306 <span id="subtotal">$0.00</span> 307 </div> 308 <div class="price-row discount-row" id="discount-row" style="display: none;"> 309 <span>Discount:</span> 310 <span id="discount-amount">-$0.00</span> 311 </div> 312 <div class="price-row total"> 313 <span>Total:</span> 314 <span id="total-price">$0.00</span> 315 </div> 316 </div> 317 318 <!-- PayPal Button --> 319 <div id="paypal-button-container"></div> 320 321 <div class="security-note"> 322 ð Secure payment powered by PayPal<br> 323 Your payment information is encrypted and secure 324 </div> 325 </div> 326 </div> 327 </div> 328 329 <!-- PayPal SDK will be loaded dynamically --> 330
330<script> 331 // Use global API_URL if available, otherwise set it 332 if (typeof window.API_URL === 'undefined') { 333 window.API_URL = 'https://smartclinician.ai/api'; 334 } 335 let currentPlan = null; 336 let currentBilling = 'monthly'; 337 let appliedDiscount = null; 338 let paypalClientId = null; 339 340 // Get plan from URL 341 const urlParams = new URLSearchParams(window.location.search); 342 const selectedTier = urlParams.get('plan') || 'intermediate'; 343 const selectedBilling = urlParams.get('billing') || 'monthly'; 344 345 // Check authentication on page load 346 document.addEventListener('DOMContentLoaded', async () => { 347 // Check if user is logged in 348 const token = localStorage.getItem('token'); 349 if (!token) { 350 // Save current URL to return after login 351 sessionStorage.setItem('redirectAfterLogin', window.location.href); 352 window.location.href = 'login.html?redirect=checkout'; 353 return; 354 } 355 356 // Load PayPal configuration first 357 await loadPayPalConfig(); 358 await loadPlanDetails(); 359 }); 360 361 async function loadPayPalConfig() { 362 try { 363 const response = await fetch(`${API_URL}/payment/config`); 364 const data = await response.json(); 365 366 if (!data.success || !data.clientId) { 367 throw new Error('PayPal not configured'); 368 } 369 370 paypalClientId = data.clientId; 371 372 // Load PayPal SDK dynamically 373 const script = document.createElement('script'); 374 script.src = `https://www.paypal.com/sdk/js?client-id=${data.clientId}&vault=true&intent=subscription`; 375 script.async = true; 376 script.onload = () => { 377 console.log('PayPal SDK loaded'); 378 if (currentPlan) { 379 updatePricing(); 380 } 381 }; 382 script.onerror = () => { 383 console.error('Failed to load PayPal SDK'); 384 showAlert('Failed to load PayPal. Please refresh the page.', 'error'); 385 }; 386 document.head.appendChild(script); 387 388 } catch (error) { 389 console.error('Load PayPal config error:', error); 390 showAlert('Payment system not available. Please contact support.', 'error'); 391 } 392 } 393 394 async function loadPlanDetails() { 395 try { 396 const response = await fetch(`${API_URL}/payment/pricing`); 397 const data = await response.json(); 398 399 if (!data.success) { 400 throw new Error('Failed to load pricing'); 401 } 402 403 console.log('Available tiers:', data.tiers.map(t => t.tier)); 404 console.log('Looking for tier:', selectedTier); 405 406 // Find selected tier 407 currentPlan = data.tiers.find(t => t.tier === selectedTier); 408 currentBilling = selectedBilling; 409 410 if (!currentPlan) { 411 showAlert(`Invalid plan selected: "${selectedTier}". Available plans: ${data.tiers.map(t => t.tier).join(', ')}`, 'error'); 412 // Default to professional if invalid 413 currentPlan = data.tiers.find(t => t.tier === 'professional') || data.tiers[0]; 414 if (!currentPlan) { 415 throw new Error('No pricing plans available'); 416 } 417 } 418 419 // Display plan info 420 displayPlanInfo(); 421 updatePricing(); 422 423 } catch (error) { 424 console.error('Load plan error:', error); 425 showAlert('Failed to load plan details', 'error'); 426 } 427 } 428 429 function isEnterprisePlan() { 430 return currentPlan && String(currentPlan.tier).toLowerCase() === 'enterprise'; 431 } 432 433 function displayPlanInfo() { 434 const price = currentBilling === 'annual' ? currentPlan.annual_price : currentPlan.monthly_price; 435 const period = currentBilling === 'annual' ? 'year' : 'month'; 436 const savings = currentBilling === 'annual' 437 ? ` (Save $${(parseFloat(currentPlan.monthly_price) * 12 - parseFloat(currentPlan.annual_price)).toFixed(2)}/year)` 438 : ''; 439
440 const credits = Number(currentPlan.cases_per_month) || 0; 441 const creditsLabel = credits >= 999999 ? 'Unlimited credits' : `${credits.toLocaleString()} credits/month`; 442 const heavyCaseEstimate = credits >= 999999 ? 'Unlimited intensive cases' : `~${Math.max(1, Math.round(credits / 64))} intensive cases`; 443 444 const creditUsageGuide = ` 445 <div style="margin-top: 20px; padding: 16px; border: 1px solid #e0e0e0; border-radius: 12px; background: #f8f9fa;"> 446 <h4 style="margin-top: 0; margin-bottom: 12px;">Credit Usage Guide</h4> 447 <ul style="list-style: none; padding: 0; margin: 0; font-size: 14px;"> 448 <li style="margin-bottom: 8px;"><strong>ð¢ Light Case:</strong> Diagnosis + Knowledge Base â 10â25 credits</li> 449 <li style="margin-bottom: 8px;"><strong>ð¡ Standard Case:</strong> Diagnosis + Treatment + Research â 25â45 credits</li> 450 <li><strong>ð´ Intensive Case:</strong> All agents + refinements â 45â65 credits (worst-case ~64 credits)</li> 451 </ul> 452 </div> 453 `; 454 455 const priceLine = isEnterprisePlan() 456 ? `Contact Us` 457 : `$${price}<span style="font-size: 18px; font-weight: 400;">/${period}</span>`; 458 const periodLine = isEnterprisePlan() 459 ? `Custom pricing & SLAs` 460 : `Billed ${currentBilling}${savings}`; 461 462 const html = ` 463 <div class="plan-card"> 464 <div class="plan-name">${currentPlan.display_name}</div> 465 <div class="plan-price">${priceLine}</div> 466 <div class="plan-period">${periodLine}</div> 467 <ul class="features-list"> 468 <li><span class="check-icon">â</span> ${creditsLabel} (${heavyCaseEstimate})</li> 469 ${currentPlan.features.map(f => `<li><span class="check-icon">â</span> ${f}</li>`).join('')} 470 </ul> 471 ${creditUsageGuide} 472 </div> 473 `; 474 475 document.getElementById('plan-info').innerHTML = html; 476 } 477 478 function updatePricing() { 479 if (isEnterprisePlan()) { 480 document.getElementById('subtotal').textContent = 'Contact Us'; 481 document.getElementById('discount-row').style.display = 'none'; 482 document.getElementById('total-price').textContent = 'Contact Us'; 483 document.getElementById('paypal-button-container').innerHTML = ` 484 <a href="contact.html?interest=Enterprise" class="btn btn-outline" style="width: 100%; text-align: center; display: inline-block; margin-top: 10px;">Contact Us</a> 485 <a href="contact.html?interest=Enterprise#request-form" class="btn btn-outline" style="width: 100%; text-align: center; display: inline-block; margin-top: 10px;">Request Form</a> 486 `; 487 return; 488 } 489 const basePrice = parseFloat(currentBilling === 'annual' ? currentPlan.annual_price : currentPlan.monthly_price); 490 let discountAmount = 0; 491 let finalPrice = basePrice; 492 493 if (appliedDiscount) { 494 if (appliedDiscount.type === 'percentage') { 495 discountAmount = (basePrice * appliedDiscount.discount_value) / 100; 496 } else { 497 discountAmount = appliedDiscount.discount_value; 498 } 499 finalPrice = Math.max(0, basePrice - discountAmount); 500 } 501 502 document.getElementById('subtotal').textContent = `$${basePrice.toFixed(2)}`; 503 document.getElementById('discount-amount').textContent = `-$${discountAmount.toFixed(2)}`; 504 document.getElementById('total-price').textContent = `$${finalPrice.toFixed(2)}`; 505 506 if (discountAmount > 0) { 507 document.getElementById('discount-row').style.display = 'flex'; 508 } else { 509 document.getElementById('discount-row').style.display = 'none'; 510 } 511 512 // Update PayPal button 513 renderPayPalButton(finalPrice); 514 } 515 516 async function applyDiscount() { 517 const code = document.getElementById('discount-code').value.trim().toUpperCase(); 518 519 if (!code) { 520 showAlert('Please enter a discount code', 'error'); 521 return; 522 } 523 524 try { 525 // Validate discount code with backend 526 const response = await fetch(`${API_URL}
526/payment/validate-discount`, { 527 method: 'POST', 528 headers: { 529 'Content-Type': 'application/json' 530 }, 531 body: JSON.stringify({ 532 code: code, 533 tier: selectedTier, 534 billing: currentBilling 535 }) 536 }); 537 538 const data = await response.json(); 539 540 if (data.success && data.valid) { 541 appliedDiscount = data.discount; 542 document.getElementById('discount-details').textContent = 543 `${code} - ${data.discount.discount_value}% off`; 544 document.getElementById('discount-applied').classList.add('show'); 545 showAlert(`Discount code "${code}" applied successfully!`, 'success'); 546 updatePricing(); 547 } else { 548 showAlert(data.message || 'Invalid discount code', 'error'); 549 } 550 } catch (error) { 551 console.error('Apply discount error:', error); 552 showAlert('Failed to apply discount code', 'error'); 553 } 554 } 555 556 function renderPayPalButton(amount) { 557 if (isEnterprisePlan()) { 558 return; 559 } 560 // Clear existing button 561 document.getElementById('paypal-button-container').innerHTML = ''; 562 563 // Check if PayPal SDK is loaded 564 if (typeof paypal === 'undefined' || !paypalClientId) { 565 document.getElementById('paypal-button-container').innerHTML = ` 566 <div style="background: #f8f9fa; border-radius: 12px; padding: 30px; text-align: center;"> 567 <div class="spinner"></div> 568 <p style="color: #5f6368; margin-top: 15px;">Loading PayPal...</p> 569 <button onclick="proceedToPayment()" style="margin-top: 20px; padding: 15px 40px; background: #0070ba; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer;"> 570 Continue to PayPal 571 </button> 572 </div> 573 `; 574 return; 575 } 576 577 // Render PayPal subscription button 578 try { 579 paypal.Buttons({ 580 style: { 581 layout: 'vertical', 582 color: 'blue', 583 shape: 'rect', 584 label: 'subscribe' 585 }, 586 createSubscription: function(data, actions) { 587 return fetch(`${API_URL}/payment/create-subscription`, { 588 method: 'POST', 589 headers: { 590 'Authorization': `Bearer ${localStorage.getItem('token')}`, 591 'Content-Type': 'application/json' 592 }, 593 body: JSON.stringify({ 594 tier: selectedTier, 595 billing: currentBilling, 596 discountCode: appliedDiscount?.code || null 597 }) 598 }) 599 .then(res => res.json()) 600 .then(data => { 601 if (data.success && data.subscriptionId) { 602 return data.subscriptionId; 603 } else { 604 throw new Error(data.error || 'Failed to create subscription'); 605 } 606 }); 607 }, 608 onApprove: function(data, actions) { 609 // Redirect to success page 610 window.location.href = `payment-success.html?subscription=${data.subscriptionID}&tier=${selectedTier}&billing=${currentBilling}`; 611 }, 612 onCancel: function(data) { 613 // Redirect to cancel page 614 window.location.href = `payment-cancel.html?tier=${selectedTier}`; 615 }, 616 onError: function(err) { 617 console.error('PayPal error:', err); 618 showAlert('Payment failed. Please try again.', 'error'); 619 // Fallback to manual redirect 620 setTimeout(() => { 621 proceedToPayment(); 622 }, 2000); 623 } 624 }).render('#paypal-button-container'); 625 } catch (error) { 626 console.error('PayPal button render error:', error); 627 // Fallback button 628 document.getElementById('paypal-button-container').innerHTML = ` 629 <button onclick="proceedToPayment()" style="width: 100%; padding: 15px 40px; background: #0070ba; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer;"> 630 Pay $${amount.toFixed(2)} with PayPal 631 </button> 632 `; 633 } 634 } 635 636 async function proceedToPayment() { 637 showAlert('Connecting to PayPal...', 'success'); 638 639 try { 640 const token = localStorage.getItem('token'); 641 if (!token) { 642 window.location.href = 'login.html'; 643 return; 644 } 645 646 const response = await fetch(`${API_URL}
646/payment/create-subscription`, { 647 method: 'POST', 648 headers: { 649 'Authorization': `Bearer ${token}`, 650 'Content-Type': 'application/json' 651 }, 652 body: JSON.stringify({ 653 tier: selectedTier, 654 billing: currentBilling, 655 discountCode: appliedDiscount?.code || null 656 }) 657 }); 658 659 const data = await response.json(); 660 661 if (data.success && data.approvalUrl) { 662 // Redirect to PayPal 663 window.location.href = data.approvalUrl; 664 } else { 665 showAlert(data.error || 'Failed to create subscription', 'error'); 666 } 667 } catch (error) { 668 console.error('Payment error:', error); 669 showAlert('Failed to process payment', 'error'); 670 } 671 } 672 673 function showAlert(message, type) { 674 const alertDiv = document.getElementById('alert'); 675 alertDiv.innerHTML = `<div class="alert alert-${type}">${message}</div>`; 676 setTimeout(() => { 677 alertDiv.innerHTML = ''; 678 }, 5000); 679 } 680 </script>
680 681</body> 682</html> 683
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.