Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Payment Link - AstraPay</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Erica+One&display=swap" rel="stylesheet"> | |
| <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | |
| <style> | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| min-height: 100vh; | |
| background: linear-gradient(180deg, | |
| #0f0514 0%, | |
| #1a0a1a 15%, | |
| #2d1a3d 35%, | |
| #4a2a5a 50%, | |
| #3d2a4a 60%, | |
| #2d1a3d 70%, | |
| #1a0a1a 80%, | |
| #0f0514 85%, | |
| #000000 100% | |
| ); | |
| background-attachment: fixed; | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| color: #ffffff; | |
| line-height: 1.6; | |
| } | |
| .main-content { | |
| margin-top: 0; | |
| min-height: 100vh; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| padding: 2rem 0; | |
| } | |
| .container { | |
| text-align: center; | |
| max-width: 500px; | |
| padding: 2rem; | |
| } | |
| .title { | |
| font-size: 2.2rem; | |
| font-weight: normal; | |
| font-family: 'Erica One', cursive; | |
| margin-bottom: 1.5rem; | |
| letter-spacing: 0.02em; | |
| } | |
| .payment-card { | |
| background: rgba(255, 255, 255, 0.02); | |
| backdrop-filter: blur(20px); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| border-radius: 16px; | |
| padding: 2.5rem; | |
| margin-bottom: 2rem; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .payment-card::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| height: 1px; | |
| background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); | |
| } | |
| .amount { | |
| font-size: 3.2rem; | |
| font-weight: 700; | |
| background: linear-gradient(135deg, #ffffff 0%, #e0e0e0 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| margin-bottom: 0.5rem; | |
| letter-spacing: -0.02em; | |
| } | |
| .description { | |
| font-size: 1.1rem; | |
| color: rgba(255, 255, 255, 0.7); | |
| margin-bottom: 2rem; | |
| font-weight: 400; | |
| } | |
| .description h1, .description h2, .description h3 { | |
| color: rgba(255, 255, 255, 0.9); | |
| font-family: 'Erica One', cursive; | |
| font-weight: normal; | |
| margin-top: 1rem; | |
| margin-bottom: 0.5rem; | |
| letter-spacing: 0.02em; | |
| } | |
| .description p { | |
| margin: 0.5rem 0; | |
| } | |
| .description ul, .description ol { | |
| margin: 0.5rem 0; | |
| padding-left: 1.5rem; | |
| } | |
| .description code { | |
| background: rgba(255, 255, 255, 0.1); | |
| padding: 0.2rem 0.4rem; | |
| border-radius: 4px; | |
| font-family: monospace; | |
| } | |
| .description pre { | |
| background: rgba(255, 255, 255, 0.05); | |
| padding: 1rem; | |
| border-radius: 8px; | |
| overflow-x: auto; | |
| margin: 1rem 0; | |
| } | |
| .description pre code { | |
| background: transparent; | |
| padding: 0; | |
| } | |
| .description a { | |
| color: rgba(255, 255, 255, 0.8); | |
| text-decoration: underline; | |
| } | |
| .description strong { | |
| color: rgba(255, 255, 255, 0.9); | |
| font-weight: 600; | |
| } | |
| .description em { | |
| font-style: italic; | |
| } | |
| .description u { | |
| text-decoration: underline; | |
| color: rgba(255, 255, 255, 0.8); | |
| } | |
| .description del, | |
| .description s, | |
| .description strike { | |
| text-decoration: line-through; | |
| color: rgba(255, 255, 255, 0.6); | |
| } | |
| .description blockquote { | |
| border-left: 3px solid rgba(255, 255, 255, 0.3); | |
| padding-left: 1rem; | |
| margin: 1rem 0; | |
| color: rgba(255, 255, 255, 0.8); | |
| font-style: italic; | |
| } | |
| .description table { | |
| border-collapse: collapse; | |
| margin: 1rem 0; | |
| width: 100%; | |
| } | |
| .description table th, | |
| .description table td { | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| padding: 0.5rem; | |
| } | |
| .description table th { | |
| background-color: rgba(255, 255, 255, 0.1); | |
| font-weight: 600; | |
| } | |
| .description hr { | |
| border: none; | |
| border-top: 1px solid rgba(255, 255, 255, 0.2); | |
| margin: 1.5rem 0; | |
| } | |
| .description img { | |
| max-width: 100%; | |
| height: auto; | |
| border-radius: 4px; | |
| margin: 1rem 0; | |
| } | |
| .status { | |
| font-size: 0.95rem; | |
| padding: 0.5rem 1.2rem; | |
| border-radius: 20px; | |
| display: inline-block; | |
| margin-bottom: 2rem; | |
| font-weight: 500; | |
| letter-spacing: 0.01em; | |
| } | |
| .status.pending { | |
| background: rgba(255, 255, 255, 0.1); | |
| color: rgba(255, 255, 255, 0.8); | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| } | |
| .status.paid { | |
| background: rgba(0, 255, 136, 0.1); | |
| color: #00ff88; | |
| border: 1px solid rgba(0, 255, 136, 0.3); | |
| } | |
| .pay-button { | |
| background: linear-gradient(135deg, #ffffff 0%, #f8f8f8 100%); | |
| color: #000000; | |
| border: none; | |
| padding: 1rem 2.5rem; | |
| font-size: 1rem; | |
| font-weight: 600; | |
| border-radius: 12px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| letter-spacing: 0.01em; | |
| box-shadow: 0 4px 20px rgba(255, 255, 255, 0.1); | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| justify-content: center; | |
| } | |
| .pay-button:hover:not(:disabled) { | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 30px rgba(255, 255, 255, 0.2); | |
| } | |
| .pay-button:disabled { | |
| background: rgba(255, 255, 255, 0.3); | |
| color: rgba(255, 255, 255, 0.5); | |
| cursor: not-allowed; | |
| transform: none; | |
| box-shadow: none; | |
| } | |
| .confirmation-text { | |
| font-size: 1.2rem; | |
| color: #00ff88; | |
| font-weight: 500; | |
| margin-top: 1rem; | |
| } | |
| .sender-email { | |
| font-size: 0.9rem; | |
| color: rgba(255, 255, 255, 0.6); | |
| margin-top: 0.5rem; | |
| font-family: 'Monaco', 'Consolas', monospace; | |
| } | |
| .timer { | |
| font-size: 0.8rem; | |
| color: rgba(255, 255, 255, 0.5); | |
| margin-top: 1rem; | |
| font-family: 'Monaco', 'Consolas', monospace; | |
| } | |
| .expired { | |
| color: #ff6b6b; | |
| } | |
| .back-link { | |
| color: rgba(255, 255, 255, 0.6); | |
| text-decoration: none; | |
| font-size: 0.9rem; | |
| margin-top: 2rem; | |
| display: inline-block; | |
| font-weight: 500; | |
| transition: color 0.3s ease; | |
| } | |
| .back-link:hover { | |
| color: rgba(255, 255, 255, 0.9); | |
| } | |
| .top-banner { | |
| background: rgba(100, 100, 255, 0.15); | |
| border: 1px solid rgba(100, 100, 255, 0.3); | |
| border-radius: 8px; | |
| padding: 0.75rem 1.5rem; | |
| margin-bottom: 2rem; | |
| color: #6464ff; | |
| font-size: 0.9rem; | |
| font-weight: 500; | |
| text-align: center; | |
| } | |
| .banner { | |
| background: rgba(255, 193, 7, 0.15); | |
| border: 1px solid rgba(255, 193, 7, 0.3); | |
| border-radius: 8px; | |
| padding: 0.75rem 1.5rem; | |
| margin-bottom: 2rem; | |
| color: #ffc107; | |
| font-size: 0.9rem; | |
| font-weight: 500; | |
| text-align: center; | |
| } | |
| .payment-modal { | |
| display: none; | |
| position: fixed; | |
| z-index: 1; | |
| left: 0; | |
| top: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0, 0, 0, 0.9); | |
| } | |
| .payment-modal-content { | |
| background-color: #1a1a1a; | |
| margin: 15% auto; | |
| padding: 2rem; | |
| border-radius: 8px; | |
| width: 90%; | |
| max-width: 500px; | |
| border: 1px solid #333; | |
| text-align: center; | |
| } | |
| .payment-modal-title { | |
| font-family: 'Erica One', cursive; | |
| font-weight: normal; | |
| letter-spacing: 0.02em; | |
| color: #ffffff; | |
| font-size: 1.5rem; | |
| font-weight: bold; | |
| margin-bottom: 1.5rem; | |
| } | |
| .payment-instruction { | |
| color: #cccccc; | |
| font-size: 1.1rem; | |
| margin-bottom: 2rem; | |
| line-height: 1.6; | |
| } | |
| .email-address { | |
| background-color: #2a2a2a; | |
| border: 1px solid #555; | |
| border-radius: 4px; | |
| padding: 1rem; | |
| margin: 1rem 0; | |
| color: #00ff88; | |
| font-family: monospace; | |
| font-size: 1.1rem; | |
| word-break: break-all; | |
| display: inline-block; | |
| } | |
| .payment-modal-buttons { | |
| display: flex; | |
| gap: 1rem; | |
| justify-content: center; | |
| margin-top: 2rem; | |
| } | |
| .btn-secondary { | |
| background-color: rgba(255, 255, 255, 0.05); | |
| color: rgba(255, 255, 255, 0.9); | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| padding: 0.75rem 1.5rem; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-size: 0.9375rem; | |
| font-weight: 500; | |
| transition: all 0.2s ease; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .btn-secondary:hover { | |
| background-color: rgba(255, 255, 255, 0.1); | |
| border-color: rgba(255, 255, 255, 0.3); | |
| transform: translateY(-1px); | |
| } | |
| .btn-secondary:active { | |
| transform: translateY(0); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="main-content"> | |
| <div class="container"> | |
| <h1 class="title">AstraPay</h1> | |
| {% if recipient_email %} | |
| <div class="banner" style="background: rgba(0, 255, 136, 0.15); border: 1px solid rgba(0, 255, 136, 0.3); color: #00ff88;"> | |
| Your astras will go to {{ recipient_email }} | |
| </div> | |
| {% endif %} | |
| <div class="payment-card"> | |
| <div class="amount">{{ amount }} Astra{{ 's' if amount > 1 else '' }}</div> | |
| {% if fee > 0 %} | |
| <div style="font-size: 0.9rem; color: rgba(255, 255, 255, 0.6); margin-bottom: 0.5rem;"> | |
| Astratrader Fee: {{ fee }} Astra{{ 's' if fee > 1 else '' }} | |
| </div> | |
| <div style="font-size: 1rem; color: rgba(255, 255, 255, 0.8); margin-bottom: 1rem; font-weight: 500;"> | |
| Total to send: {{ total_amount }} Astra{{ 's' if total_amount > 1 else '' }} | |
| </div> | |
| {% endif %} | |
| <div class="description" id="description-content">{{ description }}</div> | |
| <div class="status {{ 'paid' if paid else 'pending' }}"> | |
| {{ 'Paid' if paid else 'Pending Payment' }} | |
| </div> | |
| {% if not paid %} | |
| <div style="display: flex; justify-content: center; margin-top: 1rem;"> | |
| <button class="pay-button" onclick="processPayment()" style="display: flex; align-items: center; gap: 0.5rem; justify-content: center;"> | |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect> | |
| <line x1="1" y1="10" x2="23" y2="10"></line> | |
| </svg> | |
| Pay {{ total_amount }} Astra{{ 's' if total_amount > 1 else '' }} | |
| </button> | |
| </div> | |
| {% else %} | |
| <div style="font-size: 1.2rem; color: #00ff88;">✓ Payment Completed</div> | |
| {% endif %} | |
| </div> | |
| <a href="/" class="back-link">← Back to AstraPay</a> | |
| </div> | |
| <div id="paymentModal" class="payment-modal"> | |
| <div class="payment-modal-content"> | |
| <div class="payment-modal-title">Complete Payment</div> | |
| <div class="payment-instruction"> | |
| Please send {{ total_amount }} Astra{{ 's' if total_amount > 1 else '' }} to the following address: | |
| </div> | |
| {% if fee > 0 %} | |
| <div style="font-size: 0.85rem; color: rgba(255, 255, 255, 0.6); margin: 0.5rem 0;"> | |
| Payment: {{ amount }} Astra{{ 's' if amount > 1 else '' }} + Fee: {{ fee }} Astra{{ 's' if fee > 1 else '' }} | |
| </div> | |
| {% endif %} | |
| <div class="email-address">astrapay@astranova.org</div> | |
| <div id="payment-status" class="payment-instruction"> | |
| <div id="waiting-message"> | |
| Waiting for payment confirmation... | |
| <div id="timer" class="timer">5:00</div> | |
| </div> | |
| <div id="payment-received" style="display: none;"> | |
| ✓ Payment of {{ total_amount }} Astra{{ 's' if total_amount > 1 else '' }} received! | |
| <br><br> | |
| Please enter your email to confirm: | |
| <br> | |
| <input type="email" id="user-email" class="form-input" placeholder="Enter your email" style="margin-top: 1rem; width: 100%; max-width: 300px;"> | |
| </div> | |
| <div id="payment-expired" style="display: none;"> | |
| Payment window expired. | |
| <br><br> | |
| Please create a new payment link. | |
| </div> | |
| <div id="confirmation-complete" style="display: none;"> | |
| <div class="confirmation-text">Payment Confirmed ✓</div> | |
| <div class="sender-email" id="sender-email-display"></div> | |
| </div> | |
| </div> | |
| <div class="payment-modal-buttons"> | |
| <button class="btn-secondary" onclick="closePaymentModal()"> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <line x1="18" y1="6" x2="6" y2="18"></line> | |
| <line x1="6" y1="6" x2="18" y2="18"></line> | |
| </svg> | |
| Cancel | |
| </button> | |
| <button id="confirm-btn" class="pay-button" onclick="confirmPayment()" style="display: none;"> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <polyline points="20 6 9 17 4 12"></polyline> | |
| </svg> | |
| Confirm Payment | |
| </button> | |
| <button id="verify-btn" class="pay-button" onclick="verifyEmail()" style="display: none;"> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path> | |
| <polyline points="22,6 12,13 2,6"></polyline> | |
| </svg> | |
| Verify Email | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| marked.setOptions({ | |
| breaks: true, | |
| gfm: true | |
| }); | |
| const renderer = new marked.Renderer(); | |
| renderer.strong = function(text) { | |
| return '<strong>' + text + '</strong>'; | |
| }; | |
| renderer.em = function(text) { | |
| return '<em>' + text + '</em>'; | |
| }; | |
| renderer.codespan = function(code) { | |
| return '<code>' + code + '</code>'; | |
| }; | |
| renderer.del = function(text) { | |
| return '<del>' + text + '</del>'; | |
| }; | |
| marked.use({ | |
| renderer: { | |
| paragraph(text) { | |
| return '<p>' + text + '</p>'; | |
| }, | |
| list(body, ordered) { | |
| const tag = ordered ? 'ol' : 'ul'; | |
| return '<' + tag + '>' + body + '</' + tag + '>'; | |
| }, | |
| listitem(text) { | |
| return '<li>' + text + '</li>'; | |
| }, | |
| blockquote(quote) { | |
| return '<blockquote>' + quote + '</blockquote>'; | |
| }, | |
| code(code, language) { | |
| return '<pre><code>' + code + '</code></pre>'; | |
| }, | |
| link(href, title, text) { | |
| return '<a href="' + href + '">' + text + '</a>'; | |
| }, | |
| image(href, title, text) { | |
| return '<img src="' + href + '" alt="' + text + '">'; | |
| }, | |
| hr() { | |
| return '<hr>'; | |
| }, | |
| table(header, body) { | |
| return '<table><thead>' + header + '</thead><tbody>' + body + '</tbody></table>'; | |
| }, | |
| tablerow(content) { | |
| return '<tr>' + content + '</tr>'; | |
| }, | |
| tablecell(content, flags) { | |
| const tag = flags.header ? 'th' : 'td'; | |
| return '<' + tag + '>' + content + '</' + tag + '>'; | |
| } | |
| } | |
| }); | |
| const descriptionElement = document.getElementById('description-content'); | |
| if (descriptionElement) { | |
| let markdownText = descriptionElement.textContent; | |
| markdownText = markdownText.replace(/__([^_\n]+)__/g, '<u>$1</u>'); | |
| let html = marked.parse(markdownText, { renderer: renderer }); | |
| html = html.replace(/<u>(.*?)<\/u>/g, '<u>$1</u>'); | |
| descriptionElement.innerHTML = html; | |
| } | |
| let paymentCheckInterval; | |
| let timerInterval; | |
| let timeLeft = 300; | |
| let paymentTransaction = null; | |
| let totalAmount = {{ total_amount }}; | |
| let fee = {{ fee }}; | |
| function processPayment() { | |
| document.getElementById('paymentModal').style.display = 'block'; | |
| timeLeft = 300; | |
| paymentTransaction = null; | |
| initPaymentCheck(); | |
| startTimer(); | |
| } | |
| function closePaymentModal() { | |
| document.getElementById('paymentModal').style.display = 'none'; | |
| stopPaymentCheck(); | |
| stopTimer(); | |
| document.getElementById('waiting-message').style.display = 'block'; | |
| document.getElementById('payment-received').style.display = 'none'; | |
| document.getElementById('payment-expired').style.display = 'none'; | |
| document.getElementById('confirmation-complete').style.display = 'none'; | |
| document.getElementById('confirm-btn').style.display = 'none'; | |
| document.getElementById('verify-btn').style.display = 'none'; | |
| document.getElementById('user-email').value = ''; | |
| document.getElementById('timer').classList.remove('expired'); | |
| } | |
| async function initPaymentCheck() { | |
| try { | |
| const response = await fetch('/init-payment-check/{{ payment_id }}'); | |
| const data = await response.json(); | |
| if (data.total_amount) { | |
| totalAmount = data.total_amount; | |
| } | |
| if (data.fee !== undefined) { | |
| fee = data.fee; | |
| } | |
| startPaymentCheck(); | |
| } catch (error) { | |
| console.error('Error initializing payment check:', error); | |
| } | |
| } | |
| function startPaymentCheck() { | |
| paymentCheckInterval = setInterval(async () => { | |
| if (timeLeft <= 0) return; | |
| try { | |
| const response = await fetch(`/check-payment/{{ payment_id }}`); | |
| const data = await response.json(); | |
| if (data.payment_received) { | |
| paymentTransaction = data.transaction; | |
| document.getElementById('waiting-message').style.display = 'none'; | |
| document.getElementById('payment-received').style.display = 'block'; | |
| document.getElementById('verify-btn').style.display = 'flex'; | |
| stopPaymentCheck(); | |
| } | |
| } catch (error) { | |
| console.log('Error checking payment status:', error); | |
| } | |
| }, 2000); | |
| } | |
| function stopPaymentCheck() { | |
| if (paymentCheckInterval) { | |
| clearInterval(paymentCheckInterval); | |
| paymentCheckInterval = null; | |
| } | |
| } | |
| function startTimer() { | |
| timerInterval = setInterval(() => { | |
| timeLeft--; | |
| updateTimerDisplay(); | |
| if (timeLeft <= 0) { | |
| stopTimer(); | |
| stopPaymentCheck(); | |
| document.getElementById('waiting-message').style.display = 'none'; | |
| document.getElementById('payment-expired').style.display = 'block'; | |
| document.getElementById('confirm-btn').style.display = 'none'; | |
| document.getElementById('verify-btn').style.display = 'none'; | |
| } | |
| }, 1000); | |
| } | |
| function stopTimer() { | |
| if (timerInterval) { | |
| clearInterval(timerInterval); | |
| timerInterval = null; | |
| } | |
| } | |
| function updateTimerDisplay() { | |
| const minutes = Math.floor(timeLeft / 60); | |
| const seconds = timeLeft % 60; | |
| const timerElement = document.getElementById('timer'); | |
| timerElement.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`; | |
| if (timeLeft <= 60) { | |
| timerElement.classList.add('expired'); | |
| } | |
| } | |
| function verifyEmail() { | |
| const userEmail = document.getElementById('user-email').value.trim(); | |
| const senderEmail = paymentTransaction ? paymentTransaction.from_email : ''; | |
| if (!userEmail) { | |
| alert('Please enter your email address'); | |
| return; | |
| } | |
| if (userEmail.toLowerCase() === senderEmail.toLowerCase()) { | |
| document.getElementById('verify-btn').style.display = 'none'; | |
| document.getElementById('confirm-btn').style.display = 'flex'; | |
| } else { | |
| alert('Email address does not match the payment sender. Please use the email you sent the payment from.'); | |
| document.getElementById('user-email').focus(); | |
| } | |
| } | |
| async function confirmPayment() { | |
| const confirmButton = document.getElementById('confirm-btn'); | |
| confirmButton.disabled = true; | |
| confirmButton.textContent = 'Confirming...'; | |
| try { | |
| const response = await fetch('/pay/{{ payment_id }}', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| } | |
| }); | |
| const data = await response.json(); | |
| if (response.ok) { | |
| document.getElementById('payment-received').style.display = 'none'; | |
| document.getElementById('confirm-btn').style.display = 'none'; | |
| document.getElementById('confirmation-complete').style.display = 'block'; | |
| if (paymentTransaction && paymentTransaction.from_email) { | |
| document.getElementById('sender-email-display').textContent = `From: ${paymentTransaction.from_email}`; | |
| } | |
| stopTimer(); | |
| setTimeout(() => { | |
| location.reload(); | |
| }, 3000); | |
| } else { | |
| alert('Payment confirmation failed: ' + data.error); | |
| confirmButton.disabled = false; | |
| confirmButton.textContent = 'Confirm Payment'; | |
| } | |
| } catch (error) { | |
| alert('Payment confirmation failed: ' + error.message); | |
| confirmButton.disabled = false; | |
| confirmButton.textContent = 'Confirm Payment'; | |
| } | |
| } | |
| window.onclick = function(event) { | |
| const modal = document.getElementById('paymentModal'); | |
| if (event.target == modal) { | |
| closePaymentModal(); | |
| } | |
| } | |
| window.onbeforeunload = function() { | |
| stopPaymentCheck(); | |
| stopTimer(); | |
| }; | |
| </script> | |
| </div> | |
| </body> | |
| </html> | |