GamerC0der commited on
Commit
f37257e
·
verified ·
1 Parent(s): 6496952

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +920 -0
app.py ADDED
@@ -0,0 +1,920 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template_string, request, redirect, url_for, jsonify
2
+ from flask_sqlalchemy import SQLAlchemy
3
+ import uuid
4
+ from datetime import datetime
5
+ import requests
6
+ import time
7
+ import threading
8
+ from cryptography.fernet import Fernet
9
+ import os
10
+
11
+ app = Flask(__name__)
12
+ app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///astrapay.db'
13
+ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
14
+ app.config['SECRET_KEY'] = 'your-secret-key-here'
15
+
16
+ db = SQLAlchemy(app)
17
+
18
+ def get_encryption_key():
19
+ """Get or generate encryption key"""
20
+ key_file = 'encryption.key'
21
+ if os.path.exists(key_file):
22
+ with open(key_file, 'rb') as f:
23
+ return f.read()
24
+ else:
25
+ key = Fernet.generate_key()
26
+ with open(key_file, 'wb') as f:
27
+ f.write(key)
28
+ return key
29
+
30
+ ENCRYPTION_KEY = get_encryption_key()
31
+ cipher_suite = Fernet(ENCRYPTION_KEY)
32
+
33
+ def encrypt_value(value):
34
+ """Encrypt a string value"""
35
+ if isinstance(value, str):
36
+ value = value.encode()
37
+ return cipher_suite.encrypt(value).decode()
38
+
39
+ def decrypt_value(encrypted_value):
40
+ """Decrypt an encrypted string value"""
41
+ return cipher_suite.decrypt(encrypted_value.encode()).decode()
42
+
43
+ ENCRYPTED_CONNECT_SID = encrypt_value('s%3AeAVq_RB9TcaxBH_VSqYlYPiaYMlm1PbS.kFS%2BOEI4zH5ZS8VLv1dExjTe7dMiCXvNYeT4YG%2FoV04')
44
+ ENCRYPTED_API_URL = encrypt_value('https://astra-bank-moh1812.replit.app/api/transactions')
45
+ ENCRYPTED_REFERER = encrypt_value('https://astra-bank-moh1812.replit.app/')
46
+ ENCRYPTED_ETAG = encrypt_value('W/"6e3-L0zHI4rHMa4nHmyewyA/4y+lL6c"')
47
+
48
+ class PaymentLink(db.Model):
49
+ id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
50
+ amount = db.Column(db.Integer, nullable=False)
51
+ description = db.Column(db.String(500), nullable=False)
52
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
53
+ paid = db.Column(db.Boolean, default=False)
54
+
55
+ def __repr__(self):
56
+ return f'<PaymentLink {self.id}: {self.amount} Astras>'
57
+
58
+ def get_transactions():
59
+ url = decrypt_value(ENCRYPTED_API_URL)
60
+ referer = decrypt_value(ENCRYPTED_REFERER)
61
+ etag = decrypt_value(ENCRYPTED_ETAG)
62
+ connect_sid = decrypt_value(ENCRYPTED_CONNECT_SID)
63
+
64
+ headers = {
65
+ 'accept': '*/*',
66
+ 'accept-language': 'en-US,en;q=0.9',
67
+ 'if-none-match': etag,
68
+ 'priority': 'u=1, i',
69
+ 'referer': referer,
70
+ 'sec-ch-ua': '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"',
71
+ 'sec-ch-ua-mobile': '?0',
72
+ 'sec-ch-ua-platform': '"macOS"',
73
+ 'sec-fetch-dest': 'empty',
74
+ 'sec-fetch-mode': 'cors',
75
+ 'sec-fetch-site': 'same-origin',
76
+ 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36'
77
+ }
78
+
79
+ cookies = {
80
+ 'sidebar_state': 'true',
81
+ 'connect.sid': connect_sid
82
+ }
83
+
84
+ try:
85
+ response = requests.get(url, headers=headers, cookies=cookies, timeout=10)
86
+ return response.json()
87
+ except Exception as e:
88
+ print(f"Error fetching transactions: {e}")
89
+ return []
90
+
91
+ def check_for_payment(amount, existing_transaction_ids):
92
+ """Check for new transactions matching the payment amount"""
93
+ try:
94
+ transactions = get_transactions()
95
+
96
+ for transaction in transactions:
97
+ transaction_id = transaction.get('id')
98
+ if (transaction_id not in existing_transaction_ids and
99
+ transaction.get('amount') == amount and
100
+ transaction.get('transactionType') == 'received'):
101
+ return transaction
102
+
103
+ return None
104
+ except Exception as e:
105
+ print(f"Error checking for payment: {e}")
106
+ return None
107
+
108
+ with app.app_context():
109
+ db.create_all()
110
+
111
+ HOME_TEMPLATE = """
112
+ <!DOCTYPE html>
113
+ <html lang="en">
114
+ <head>
115
+ <meta charset="UTF-8">
116
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
117
+ <title>AstraPay</title>
118
+ <style>
119
+ body {
120
+ margin: 0;
121
+ padding: 0;
122
+ height: 100vh;
123
+ background-color: #000000;
124
+ display: flex;
125
+ justify-content: center;
126
+ align-items: center;
127
+ font-family: Arial, sans-serif;
128
+ }
129
+ .container {
130
+ text-align: center;
131
+ }
132
+ .title {
133
+ color: #ffffff;
134
+ font-size: 4rem;
135
+ font-weight: bold;
136
+ text-shadow: 2px 2px 4px rgba(255, 255, 255, 0.3);
137
+ margin-bottom: 2rem;
138
+ }
139
+ .button {
140
+ background-color: #ffffff;
141
+ color: #000000;
142
+ border: none;
143
+ padding: 1rem 2rem;
144
+ font-size: 1.2rem;
145
+ font-weight: bold;
146
+ border-radius: 8px;
147
+ cursor: pointer;
148
+ transition: all 0.3s ease;
149
+ text-decoration: none;
150
+ display: inline-block;
151
+ }
152
+ .button:hover {
153
+ background-color: #f0f0f0;
154
+ transform: translateY(-2px);
155
+ box-shadow: 0 4px 8px rgba(255, 255, 255, 0.2);
156
+ }
157
+ .modal {
158
+ display: none;
159
+ position: fixed;
160
+ z-index: 1;
161
+ left: 0;
162
+ top: 0;
163
+ width: 100%;
164
+ height: 100%;
165
+ background-color: rgba(0, 0, 0, 0.8);
166
+ }
167
+ .modal-content {
168
+ background-color: #1a1a1a;
169
+ margin: 15% auto;
170
+ padding: 2rem;
171
+ border-radius: 8px;
172
+ width: 90%;
173
+ max-width: 500px;
174
+ border: 1px solid #333;
175
+ }
176
+ .modal-header {
177
+ color: #ffffff;
178
+ margin-bottom: 1.5rem;
179
+ font-size: 1.5rem;
180
+ font-weight: bold;
181
+ }
182
+ .form-group {
183
+ margin-bottom: 1.5rem;
184
+ }
185
+ .form-label {
186
+ display: block;
187
+ color: #ffffff;
188
+ margin-bottom: 0.5rem;
189
+ font-weight: bold;
190
+ }
191
+ .form-input {
192
+ width: 100%;
193
+ padding: 0.75rem;
194
+ border: 1px solid #555;
195
+ border-radius: 4px;
196
+ background-color: #2a2a2a;
197
+ color: #ffffff;
198
+ font-size: 1rem;
199
+ }
200
+ .form-input:focus {
201
+ outline: none;
202
+ border-color: #ffffff;
203
+ }
204
+ .form-textarea {
205
+ width: 100%;
206
+ padding: 0.75rem;
207
+ border: 1px solid #555;
208
+ border-radius: 4px;
209
+ background-color: #2a2a2a;
210
+ color: #ffffff;
211
+ font-size: 1rem;
212
+ min-height: 100px;
213
+ resize: vertical;
214
+ }
215
+ .form-textarea:focus {
216
+ outline: none;
217
+ border-color: #ffffff;
218
+ }
219
+ .modal-buttons {
220
+ display: flex;
221
+ gap: 1rem;
222
+ justify-content: flex-end;
223
+ margin-top: 2rem;
224
+ }
225
+ .btn-secondary {
226
+ background-color: #555;
227
+ color: #ffffff;
228
+ border: none;
229
+ padding: 0.75rem 1.5rem;
230
+ border-radius: 4px;
231
+ cursor: pointer;
232
+ font-size: 1rem;
233
+ }
234
+ .btn-secondary:hover {
235
+ background-color: #666;
236
+ }
237
+ .banner {
238
+ background: rgba(255, 193, 7, 0.15);
239
+ border: 1px solid rgba(255, 193, 7, 0.3);
240
+ border-radius: 8px;
241
+ padding: 0.75rem 1.5rem;
242
+ margin-bottom: 2rem;
243
+ color: #ffc107;
244
+ font-size: 0.9rem;
245
+ font-weight: 500;
246
+ text-align: center;
247
+ max-width: 500px;
248
+ margin-left: auto;
249
+ margin-right: auto;
250
+ }
251
+ </style>
252
+ </head>
253
+ <body>
254
+ <div class="container">
255
+ <h1 class="title">AstraPay</h1>
256
+ <div class="banner">
257
+ Payments May Reset Daily. Ensure your link works.
258
+ </div>
259
+ <button class="button" onclick="openModal()">Create Payment Link</button>
260
+ </div>
261
+
262
+ <!-- Modal -->
263
+ <div id="paymentModal" class="modal">
264
+ <div class="modal-content">
265
+ <div class="modal-header">Create Payment Link</div>
266
+
267
+ <div class="form-group">
268
+ <label class="form-label" for="astras">Number of Astras</label>
269
+ <input type="number" id="astras" class="form-input" min="1" placeholder="Enter number of astras" required>
270
+ </div>
271
+
272
+ <div class="form-group">
273
+ <label class="form-label" for="description">Description</label>
274
+ <textarea id="description" class="form-textarea" placeholder="Enter payment description" required></textarea>
275
+ </div>
276
+
277
+ <div class="modal-buttons">
278
+ <button class="btn-secondary" onclick="closeModal()">Cancel</button>
279
+ <button class="button" onclick="createPaymentLink()">Create Link</button>
280
+ </div>
281
+ </div>
282
+ </div>
283
+
284
+ <script>
285
+ function openModal() {
286
+ document.getElementById('paymentModal').style.display = 'block';
287
+ }
288
+
289
+ function closeModal() {
290
+ document.getElementById('paymentModal').style.display = 'none';
291
+ // Clear form
292
+ document.getElementById('astras').value = '';
293
+ document.getElementById('description').value = '';
294
+ }
295
+
296
+ async function createPaymentLink() {
297
+ const astras = document.getElementById('astras').value;
298
+ const description = document.getElementById('description').value;
299
+
300
+ if (!astras || !description.trim()) {
301
+ alert('Please fill in all fields');
302
+ return;
303
+ }
304
+
305
+ try {
306
+ const response = await fetch('/create-payment-link', {
307
+ method: 'POST',
308
+ headers: {
309
+ 'Content-Type': 'application/json',
310
+ },
311
+ body: JSON.stringify({
312
+ amount: parseInt(astras),
313
+ description: description.trim()
314
+ })
315
+ });
316
+
317
+ const data = await response.json();
318
+
319
+ if (response.ok) {
320
+ // Redirect to the payment link
321
+ window.location.href = data.payment_url;
322
+ } else {
323
+ alert('Error creating payment link: ' + data.error);
324
+ }
325
+ } catch (error) {
326
+ alert('Error creating payment link: ' + error.message);
327
+ }
328
+ }
329
+
330
+ // Close modal when clicking outside
331
+ window.onclick = function(event) {
332
+ const modal = document.getElementById('paymentModal');
333
+ if (event.target == modal) {
334
+ closeModal();
335
+ }
336
+ }
337
+ </script>
338
+ </body>
339
+ </html>
340
+ """
341
+
342
+ PAYMENT_TEMPLATE = """
343
+ <!DOCTYPE html>
344
+ <html lang="en">
345
+ <head>
346
+ <meta charset="UTF-8">
347
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
348
+ <title>Payment Link - AstraPay</title>
349
+ <style>
350
+ body {
351
+ margin: 0;
352
+ padding: 0;
353
+ min-height: 100vh;
354
+ background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%);
355
+ display: flex;
356
+ justify-content: center;
357
+ align-items: center;
358
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
359
+ color: #ffffff;
360
+ line-height: 1.6;
361
+ }
362
+ .container {
363
+ text-align: center;
364
+ max-width: 500px;
365
+ padding: 2rem;
366
+ }
367
+ .title {
368
+ font-size: 2.2rem;
369
+ font-weight: 600;
370
+ margin-bottom: 1.5rem;
371
+ letter-spacing: -0.02em;
372
+ }
373
+ .payment-card {
374
+ background: rgba(255, 255, 255, 0.02);
375
+ backdrop-filter: blur(20px);
376
+ border: 1px solid rgba(255, 255, 255, 0.1);
377
+ border-radius: 16px;
378
+ padding: 2.5rem;
379
+ margin-bottom: 2rem;
380
+ position: relative;
381
+ overflow: hidden;
382
+ }
383
+ .payment-card::before {
384
+ content: '';
385
+ position: absolute;
386
+ top: 0;
387
+ left: 0;
388
+ right: 0;
389
+ height: 1px;
390
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
391
+ }
392
+ .amount {
393
+ font-size: 3.2rem;
394
+ font-weight: 700;
395
+ background: linear-gradient(135deg, #ffffff 0%, #e0e0e0 100%);
396
+ -webkit-background-clip: text;
397
+ -webkit-text-fill-color: transparent;
398
+ background-clip: text;
399
+ margin-bottom: 0.5rem;
400
+ letter-spacing: -0.02em;
401
+ }
402
+ .description {
403
+ font-size: 1.1rem;
404
+ color: rgba(255, 255, 255, 0.7);
405
+ margin-bottom: 2rem;
406
+ font-weight: 400;
407
+ }
408
+ .status {
409
+ font-size: 0.95rem;
410
+ padding: 0.5rem 1.2rem;
411
+ border-radius: 20px;
412
+ display: inline-block;
413
+ margin-bottom: 2rem;
414
+ font-weight: 500;
415
+ letter-spacing: 0.01em;
416
+ }
417
+ .status.pending {
418
+ background: rgba(255, 255, 255, 0.1);
419
+ color: rgba(255, 255, 255, 0.8);
420
+ border: 1px solid rgba(255, 255, 255, 0.2);
421
+ }
422
+ .status.paid {
423
+ background: rgba(0, 255, 136, 0.1);
424
+ color: #00ff88;
425
+ border: 1px solid rgba(0, 255, 136, 0.3);
426
+ }
427
+ .pay-button {
428
+ background: linear-gradient(135deg, #ffffff 0%, #f8f8f8 100%);
429
+ color: #000000;
430
+ border: none;
431
+ padding: 1rem 2.5rem;
432
+ font-size: 1rem;
433
+ font-weight: 600;
434
+ border-radius: 12px;
435
+ cursor: pointer;
436
+ transition: all 0.3s ease;
437
+ letter-spacing: 0.01em;
438
+ box-shadow: 0 4px 20px rgba(255, 255, 255, 0.1);
439
+ }
440
+ .pay-button:hover:not(:disabled) {
441
+ transform: translateY(-2px);
442
+ box-shadow: 0 8px 30px rgba(255, 255, 255, 0.2);
443
+ }
444
+ .pay-button:disabled {
445
+ background: rgba(255, 255, 255, 0.3);
446
+ color: rgba(255, 255, 255, 0.5);
447
+ cursor: not-allowed;
448
+ transform: none;
449
+ box-shadow: none;
450
+ }
451
+ .confirmation-text {
452
+ font-size: 1.2rem;
453
+ color: #00ff88;
454
+ font-weight: 500;
455
+ margin-top: 1rem;
456
+ }
457
+ .sender-email {
458
+ font-size: 0.9rem;
459
+ color: rgba(255, 255, 255, 0.6);
460
+ margin-top: 0.5rem;
461
+ font-family: 'Monaco', 'Consolas', monospace;
462
+ }
463
+ .timer {
464
+ font-size: 0.8rem;
465
+ color: rgba(255, 255, 255, 0.5);
466
+ margin-top: 1rem;
467
+ font-family: 'Monaco', 'Consolas', monospace;
468
+ }
469
+ .expired {
470
+ color: #ff6b6b;
471
+ }
472
+ .back-link {
473
+ color: rgba(255, 255, 255, 0.6);
474
+ text-decoration: none;
475
+ font-size: 0.9rem;
476
+ margin-top: 2rem;
477
+ display: inline-block;
478
+ font-weight: 500;
479
+ transition: color 0.3s ease;
480
+ }
481
+ .back-link:hover {
482
+ color: rgba(255, 255, 255, 0.9);
483
+ }
484
+ .banner {
485
+ background: rgba(255, 193, 7, 0.15);
486
+ border: 1px solid rgba(255, 193, 7, 0.3);
487
+ border-radius: 8px;
488
+ padding: 0.75rem 1.5rem;
489
+ margin-bottom: 2rem;
490
+ color: #ffc107;
491
+ font-size: 0.9rem;
492
+ font-weight: 500;
493
+ text-align: center;
494
+ }
495
+ .payment-modal {
496
+ display: none;
497
+ position: fixed;
498
+ z-index: 1;
499
+ left: 0;
500
+ top: 0;
501
+ width: 100%;
502
+ height: 100%;
503
+ background-color: rgba(0, 0, 0, 0.9);
504
+ }
505
+ .payment-modal-content {
506
+ background-color: #1a1a1a;
507
+ margin: 15% auto;
508
+ padding: 2rem;
509
+ border-radius: 8px;
510
+ width: 90%;
511
+ max-width: 500px;
512
+ border: 1px solid #333;
513
+ text-align: center;
514
+ }
515
+ .payment-modal-title {
516
+ color: #ffffff;
517
+ font-size: 1.5rem;
518
+ font-weight: bold;
519
+ margin-bottom: 1.5rem;
520
+ }
521
+ .payment-instruction {
522
+ color: #cccccc;
523
+ font-size: 1.1rem;
524
+ margin-bottom: 2rem;
525
+ line-height: 1.6;
526
+ }
527
+ .email-address {
528
+ background-color: #2a2a2a;
529
+ border: 1px solid #555;
530
+ border-radius: 4px;
531
+ padding: 1rem;
532
+ margin: 1rem 0;
533
+ color: #00ff88;
534
+ font-family: monospace;
535
+ font-size: 1.1rem;
536
+ word-break: break-all;
537
+ display: inline-block;
538
+ }
539
+ .payment-modal-buttons {
540
+ display: flex;
541
+ gap: 1rem;
542
+ justify-content: center;
543
+ margin-top: 2rem;
544
+ }
545
+ </style>
546
+ </head>
547
+ <body>
548
+ <div class="container">
549
+ <h1 class="title">AstraPay</h1>
550
+
551
+ <div class="banner">
552
+ Payments May Reset Daily. Ensure your link works.
553
+ </div>
554
+
555
+ <div class="payment-card">
556
+ <div class="amount">{{ amount }} Astra{{ 's' if amount > 1 else '' }}</div>
557
+ <div class="description">{{ description }}</div>
558
+ <div class="status {{ 'paid' if paid else 'pending' }}">
559
+ {{ 'Paid' if paid else 'Pending Payment' }}
560
+ </div>
561
+
562
+ {% if not paid %}
563
+ <button class="pay-button" onclick="processPayment()">
564
+ Pay {{ amount }} Astra{{ 's' if amount > 1 else '' }}
565
+ </button>
566
+ {% else %}
567
+ <div style="font-size: 1.2rem; color: #00ff88;">✓ Payment Completed</div>
568
+ {% endif %}
569
+ </div>
570
+
571
+ <a href="/" class="back-link">← Back to AstraPay</a>
572
+ </div>
573
+
574
+ <!-- Payment Modal -->
575
+ <div id="paymentModal" class="payment-modal">
576
+ <div class="payment-modal-content">
577
+ <div class="payment-modal-title">Complete Payment</div>
578
+ <div class="payment-instruction">
579
+ Please send {{ amount }} Astra{{ 's' if amount > 1 else '' }} to the following address:
580
+ </div>
581
+ <div class="email-address">matthew@astranova.org</div>
582
+
583
+ <div id="payment-status" class="payment-instruction">
584
+ <div id="waiting-message">
585
+ Waiting for payment confirmation...
586
+ <div id="timer" class="timer">5:00</div>
587
+ </div>
588
+ <div id="payment-received" style="display: none;">
589
+ ✓ Payment of {{ amount }} Astra{{ 's' if amount > 1 else '' }} received!
590
+ <br><br>
591
+ Please enter your email to confirm:
592
+ <br>
593
+ <input type="email" id="user-email" class="form-input" placeholder="Enter your email" style="margin-top: 1rem; width: 100%; max-width: 300px;">
594
+ </div>
595
+ <div id="payment-expired" style="display: none;">
596
+ Payment window expired.
597
+ <br><br>
598
+ Please create a new payment link.
599
+ </div>
600
+ <div id="confirmation-complete" style="display: none;">
601
+ <div class="confirmation-text">Payment Confirmed ✓</div>
602
+ <div class="sender-email" id="sender-email-display"></div>
603
+ </div>
604
+ </div>
605
+
606
+ <div class="payment-modal-buttons">
607
+ <button class="btn-secondary" onclick="closePaymentModal()">Cancel</button>
608
+ <button id="confirm-btn" class="pay-button" onclick="confirmPayment()" style="display: none;">Confirm Payment</button>
609
+ <button id="verify-btn" class="pay-button" onclick="verifyEmail()" style="display: none;">Verify Email</button>
610
+ </div>
611
+ </div>
612
+ </div>
613
+
614
+ <script>
615
+ let paymentCheckInterval;
616
+ let timerInterval;
617
+ let existingTransactionIds = [];
618
+ let timeLeft = 300; // 5 minutes in seconds
619
+ let paymentTransaction = null;
620
+
621
+ function processPayment() {
622
+ document.getElementById('paymentModal').style.display = 'block';
623
+ timeLeft = 300; // Reset timer
624
+ paymentTransaction = null; // Reset payment data
625
+ // Initialize payment checking
626
+ initPaymentCheck();
627
+ startTimer();
628
+ }
629
+
630
+ function closePaymentModal() {
631
+ document.getElementById('paymentModal').style.display = 'none';
632
+ stopPaymentCheck();
633
+ stopTimer();
634
+ document.getElementById('waiting-message').style.display = 'block';
635
+ document.getElementById('payment-received').style.display = 'none';
636
+ document.getElementById('payment-expired').style.display = 'none';
637
+ document.getElementById('confirmation-complete').style.display = 'none';
638
+ document.getElementById('confirm-btn').style.display = 'none';
639
+ document.getElementById('verify-btn').style.display = 'none';
640
+ document.getElementById('user-email').value = '';
641
+ document.getElementById('timer').classList.remove('expired');
642
+ existingTransactionIds = [];
643
+ }
644
+
645
+ async function initPaymentCheck() {
646
+ try {
647
+ const response = await fetch('/init-payment-check/{{ payment_id }}');
648
+ const data = await response.json();
649
+
650
+ if (data.existing_transaction_ids) {
651
+ existingTransactionIds = data.existing_transaction_ids;
652
+ startPaymentCheck();
653
+ } else {
654
+ console.error('Failed to initialize payment check');
655
+ }
656
+ } catch (error) {
657
+ console.error('Error initializing payment check:', error);
658
+ }
659
+ }
660
+
661
+ function startPaymentCheck() {
662
+ paymentCheckInterval = setInterval(async () => {
663
+ if (timeLeft <= 0) return; // Don't check if expired
664
+
665
+ try {
666
+ const existingIdsParam = existingTransactionIds.join(',');
667
+ const response = await fetch(`/check-payment/{{ payment_id }}?existing_ids=${existingIdsParam}`);
668
+ const data = await response.json();
669
+
670
+ if (data.payment_received) {
671
+ paymentTransaction = data.transaction;
672
+ document.getElementById('waiting-message').style.display = 'none';
673
+ document.getElementById('payment-received').style.display = 'block';
674
+ document.getElementById('verify-btn').style.display = 'inline-block';
675
+ stopPaymentCheck();
676
+ }
677
+ } catch (error) {
678
+ console.log('Error checking payment status:', error);
679
+ }
680
+ }, 2000); // Check every 2 seconds
681
+ }
682
+
683
+ function stopPaymentCheck() {
684
+ if (paymentCheckInterval) {
685
+ clearInterval(paymentCheckInterval);
686
+ paymentCheckInterval = null;
687
+ }
688
+ }
689
+
690
+ function startTimer() {
691
+ timerInterval = setInterval(() => {
692
+ timeLeft--;
693
+ updateTimerDisplay();
694
+
695
+ if (timeLeft <= 0) {
696
+ stopTimer();
697
+ stopPaymentCheck();
698
+ document.getElementById('waiting-message').style.display = 'none';
699
+ document.getElementById('payment-expired').style.display = 'block';
700
+ document.getElementById('confirm-btn').style.display = 'none';
701
+ document.getElementById('verify-btn').style.display = 'none';
702
+ }
703
+ }, 1000);
704
+ }
705
+
706
+ function stopTimer() {
707
+ if (timerInterval) {
708
+ clearInterval(timerInterval);
709
+ timerInterval = null;
710
+ }
711
+ }
712
+
713
+ function updateTimerDisplay() {
714
+ const minutes = Math.floor(timeLeft / 60);
715
+ const seconds = timeLeft % 60;
716
+ const timerElement = document.getElementById('timer');
717
+
718
+ timerElement.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
719
+
720
+ if (timeLeft <= 60) {
721
+ timerElement.classList.add('expired');
722
+ }
723
+ }
724
+
725
+ function verifyEmail() {
726
+ const userEmail = document.getElementById('user-email').value.trim();
727
+ const senderEmail = paymentTransaction ? paymentTransaction.from_email : '';
728
+
729
+ if (!userEmail) {
730
+ alert('Please enter your email address');
731
+ return;
732
+ }
733
+
734
+ if (userEmail.toLowerCase() === senderEmail.toLowerCase()) {
735
+ document.getElementById('verify-btn').style.display = 'none';
736
+ document.getElementById('confirm-btn').style.display = 'inline-block';
737
+ } else {
738
+ alert('Email address does not match the payment sender. Please use the email you sent the payment from.');
739
+ document.getElementById('user-email').focus();
740
+ }
741
+ }
742
+
743
+ async function confirmPayment() {
744
+ const confirmButton = document.getElementById('confirm-btn');
745
+ confirmButton.disabled = true;
746
+ confirmButton.textContent = 'Confirming...';
747
+
748
+ try {
749
+ const response = await fetch('/pay/{{ payment_id }}', {
750
+ method: 'POST',
751
+ headers: {
752
+ 'Content-Type': 'application/json',
753
+ }
754
+ });
755
+
756
+ const data = await response.json();
757
+
758
+ if (response.ok) {
759
+ // Hide all other elements and show confirmation
760
+ document.getElementById('payment-received').style.display = 'none';
761
+ document.getElementById('confirm-btn').style.display = 'none';
762
+ document.getElementById('confirmation-complete').style.display = 'block';
763
+
764
+ // Show sender email if available
765
+ if (paymentTransaction && paymentTransaction.from_email) {
766
+ document.getElementById('sender-email-display').textContent = `From: ${paymentTransaction.from_email}`;
767
+ }
768
+
769
+ stopTimer();
770
+ // Redirect after a delay
771
+ setTimeout(() => {
772
+ location.reload();
773
+ }, 3000);
774
+ } else {
775
+ alert('Payment confirmation failed: ' + data.error);
776
+ confirmButton.disabled = false;
777
+ confirmButton.textContent = 'Confirm Payment';
778
+ }
779
+ } catch (error) {
780
+ alert('Payment confirmation failed: ' + error.message);
781
+ confirmButton.disabled = false;
782
+ confirmButton.textContent = 'Confirm Payment';
783
+ }
784
+ }
785
+
786
+ // Close modal when clicking outside
787
+ window.onclick = function(event) {
788
+ const modal = document.getElementById('paymentModal');
789
+ if (event.target == modal) {
790
+ closePaymentModal();
791
+ }
792
+ }
793
+
794
+ // Stop checking when page unloads
795
+ window.onbeforeunload = function() {
796
+ stopPaymentCheck();
797
+ stopTimer();
798
+ };
799
+ </script>
800
+ </body>
801
+ </html>
802
+ """
803
+
804
+ @app.route('/')
805
+ def home():
806
+ return render_template_string(HOME_TEMPLATE)
807
+
808
+ @app.route('/create-payment-link', methods=['POST'])
809
+ def create_payment_link():
810
+ try:
811
+ data = request.get_json()
812
+
813
+ if not data or 'amount' not in data or 'description' not in data:
814
+ return jsonify({'error': 'Missing required fields'}), 400
815
+
816
+ amount = data['amount']
817
+ description = data['description']
818
+
819
+ if not isinstance(amount, int) or amount <= 0:
820
+ return jsonify({'error': 'Invalid amount'}), 400
821
+
822
+ if not description or len(description.strip()) == 0:
823
+ return jsonify({'error': 'Description cannot be empty'}), 400
824
+
825
+ payment_link = PaymentLink(
826
+ amount=amount,
827
+ description=description.strip()
828
+ )
829
+
830
+ db.session.add(payment_link)
831
+ db.session.commit()
832
+
833
+ payment_url = url_for('view_payment', payment_id=payment_link.id, _external=True)
834
+
835
+ return jsonify({
836
+ 'success': True,
837
+ 'payment_id': payment_link.id,
838
+ 'payment_url': payment_url
839
+ })
840
+
841
+ except Exception as e:
842
+ db.session.rollback()
843
+ return jsonify({'error': str(e)}), 500
844
+
845
+ @app.route('/init-payment-check/<payment_id>')
846
+ def init_payment_check(payment_id):
847
+ """Initialize payment checking by storing current transaction state"""
848
+ try:
849
+ payment_link = PaymentLink.query.get_or_404(payment_id)
850
+
851
+ transactions = get_transactions()
852
+ existing_ids = [tx.get('id') for tx in transactions]
853
+
854
+ return jsonify({
855
+ 'existing_transaction_ids': existing_ids,
856
+ 'amount': payment_link.amount
857
+ })
858
+
859
+ except Exception as e:
860
+ return jsonify({'error': str(e)}), 500
861
+
862
+ @app.route('/check-payment/<payment_id>')
863
+ def check_payment_status(payment_id):
864
+ """Check if payment has been received"""
865
+ try:
866
+ payment_link = PaymentLink.query.get_or_404(payment_id)
867
+
868
+ existing_ids_str = request.args.get('existing_ids', '')
869
+ if existing_ids_str:
870
+ existing_ids = existing_ids_str.split(',')
871
+ else:
872
+ transactions = get_transactions()
873
+ existing_ids = [tx.get('id') for tx in transactions]
874
+
875
+ payment_transaction = check_for_payment(payment_link.amount, existing_ids)
876
+
877
+ if payment_transaction:
878
+ return jsonify({
879
+ 'payment_received': True,
880
+ 'transaction': {
881
+ 'id': payment_transaction.get('id'),
882
+ 'amount': payment_transaction.get('amount'),
883
+ 'from_email': payment_transaction.get('counterpartEmail'),
884
+ 'description': payment_transaction.get('description')
885
+ }
886
+ })
887
+ else:
888
+ return jsonify({'payment_received': False})
889
+
890
+ except Exception as e:
891
+ return jsonify({'error': str(e)}), 500
892
+
893
+ @app.route('/pay/<payment_id>', methods=['POST'])
894
+ def process_payment(payment_id):
895
+ try:
896
+ payment_link = PaymentLink.query.get_or_404(payment_id)
897
+
898
+ if payment_link.paid:
899
+ return jsonify({'error': 'Payment already completed'}), 400
900
+
901
+ payment_link.paid = True
902
+ db.session.commit()
903
+
904
+ return jsonify({'success': True, 'message': 'Payment confirmed successfully'})
905
+
906
+ except Exception as e:
907
+ db.session.rollback()
908
+ return jsonify({'error': str(e)}), 500
909
+
910
+ @app.route('/pay/<payment_id>')
911
+ def view_payment(payment_id):
912
+ payment_link = PaymentLink.query.get_or_404(payment_id)
913
+ return render_template_string(PAYMENT_TEMPLATE,
914
+ amount=payment_link.amount,
915
+ description=payment_link.description,
916
+ paid=payment_link.paid,
917
+ payment_id=payment_link.id)
918
+
919
+ if __name__ == '__main__':
920
+ app.run(debug=True, host='0.0.0.0', port=5000)