astrapay / app.py
GamerC0der's picture
Update app.py
2840c2c verified
raw
history blame
31.6 kB
from flask import Flask, render_template_string, request, redirect, url_for, jsonify
from flask_sqlalchemy import SQLAlchemy
import uuid
from datetime import datetime
import requests
import time
import threading
from cryptography.fernet import Fernet
import os
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///astrapay.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = 'your-secret-key-here'
db = SQLAlchemy(app)
def get_encryption_key():
"""Get or generate encryption key"""
key_file = 'encryption.key'
if os.path.exists(key_file):
with open(key_file, 'rb') as f:
return f.read()
else:
key = Fernet.generate_key()
with open(key_file, 'wb') as f:
f.write(key)
return key
ENCRYPTION_KEY = get_encryption_key()
cipher_suite = Fernet(ENCRYPTION_KEY)
def encrypt_value(value):
"""Encrypt a string value"""
if isinstance(value, str):
value = value.encode()
return cipher_suite.encrypt(value).decode()
def decrypt_value(encrypted_value):
"""Decrypt an encrypted string value"""
return cipher_suite.decrypt(encrypted_value.encode()).decode()
ENCRYPTED_CONNECT_SID = encrypt_value('s%3AeAVq_RB9TcaxBH_VSqYlYPiaYMlm1PbS.kFS%2BOEI4zH5ZS8VLv1dExjTe7dMiCXvNYeT4YG%2FoV04')
ENCRYPTED_API_URL = encrypt_value('https://astra-bank-moh1812.replit.app/api/transactions')
ENCRYPTED_REFERER = encrypt_value('https://astra-bank-moh1812.replit.app/')
ENCRYPTED_ETAG = encrypt_value('W/"6e3-L0zHI4rHMa4nHmyewyA/4y+lL6c"')
class PaymentLink(db.Model):
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
amount = db.Column(db.Integer, nullable=False)
description = db.Column(db.String(500), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
paid = db.Column(db.Boolean, default=False)
def __repr__(self):
return f'<PaymentLink {self.id}: {self.amount} Astras>'
def get_transactions():
url = decrypt_value(ENCRYPTED_API_URL)
referer = decrypt_value(ENCRYPTED_REFERER)
etag = decrypt_value(ENCRYPTED_ETAG)
connect_sid = decrypt_value(ENCRYPTED_CONNECT_SID)
headers = {
'accept': '*/*',
'accept-language': 'en-US,en;q=0.9',
'if-none-match': etag,
'priority': 'u=1, i',
'referer': referer,
'sec-ch-ua': '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'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'
}
cookies = {
'sidebar_state': 'true',
'connect.sid': connect_sid
}
try:
response = requests.get(url, headers=headers, cookies=cookies, timeout=10)
return response.json()
except Exception as e:
print(f"Error fetching transactions: {e}")
return []
def check_for_payment(amount, existing_transaction_ids):
"""Check for new transactions matching the payment amount"""
try:
transactions = get_transactions()
for transaction in transactions:
transaction_id = transaction.get('id')
if (transaction_id not in existing_transaction_ids and
transaction.get('amount') == amount and
transaction.get('transactionType') == 'received'):
return transaction
return None
except Exception as e:
print(f"Error checking for payment: {e}")
return None
with app.app_context():
db.create_all()
HOME_TEMPLATE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AstraPay</title>
<style>
body {
margin: 0;
padding: 0;
height: 100vh;
background-color: #000000;
display: flex;
justify-content: center;
align-items: center;
font-family: Arial, sans-serif;
}
.container {
text-align: center;
}
.title {
color: #ffffff;
font-size: 4rem;
font-weight: bold;
text-shadow: 2px 2px 4px rgba(255, 255, 255, 0.3);
margin-bottom: 2rem;
}
.button {
background-color: #ffffff;
color: #000000;
border: none;
padding: 1rem 2rem;
font-size: 1.2rem;
font-weight: bold;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-block;
}
.button:hover {
background-color: #f0f0f0;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(255, 255, 255, 0.2);
}
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
}
.modal-content {
background-color: #1a1a1a;
margin: 15% auto;
padding: 2rem;
border-radius: 8px;
width: 90%;
max-width: 500px;
border: 1px solid #333;
}
.modal-header {
color: #ffffff;
margin-bottom: 1.5rem;
font-size: 1.5rem;
font-weight: bold;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-label {
display: block;
color: #ffffff;
margin-bottom: 0.5rem;
font-weight: bold;
}
.form-input {
width: 100%;
padding: 0.75rem;
border: 1px solid #555;
border-radius: 4px;
background-color: #2a2a2a;
color: #ffffff;
font-size: 1rem;
}
.form-input:focus {
outline: none;
border-color: #ffffff;
}
.form-textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #555;
border-radius: 4px;
background-color: #2a2a2a;
color: #ffffff;
font-size: 1rem;
min-height: 100px;
resize: vertical;
}
.form-textarea:focus {
outline: none;
border-color: #ffffff;
}
.modal-buttons {
display: flex;
gap: 1rem;
justify-content: flex-end;
margin-top: 2rem;
}
.btn-secondary {
background-color: #555;
color: #ffffff;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
}
.btn-secondary:hover {
background-color: #666;
}
.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;
max-width: 500px;
margin-left: auto;
margin-right: auto;
}
</style>
</head>
<body>
<div class="container">
<h1 class="title">AstraPay</h1>
<div class="banner">
Payments May Reset Daily. Ensure your link works.
</div>
<button class="button" onclick="openModal()">Create Payment Link</button>
</div>
<!-- Modal -->
<div id="paymentModal" class="modal">
<div class="modal-content">
<div class="modal-header">Create Payment Link</div>
<div class="form-group">
<label class="form-label" for="astras">Number of Astras</label>
<input type="number" id="astras" class="form-input" min="1" placeholder="Enter number of astras" required>
</div>
<div class="form-group">
<label class="form-label" for="description">Description</label>
<textarea id="description" class="form-textarea" placeholder="Enter payment description" required></textarea>
</div>
<div class="modal-buttons">
<button class="btn-secondary" onclick="closeModal()">Cancel</button>
<button class="button" onclick="createPaymentLink()">Create Link</button>
</div>
</div>
</div>
<script>
function openModal() {
document.getElementById('paymentModal').style.display = 'block';
}
function closeModal() {
document.getElementById('paymentModal').style.display = 'none';
// Clear form
document.getElementById('astras').value = '';
document.getElementById('description').value = '';
}
async function createPaymentLink() {
const astras = document.getElementById('astras').value;
const description = document.getElementById('description').value;
if (!astras || !description.trim()) {
alert('Please fill in all fields');
return;
}
try {
const response = await fetch('/create-payment-link', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: parseInt(astras),
description: description.trim()
})
});
const data = await response.json();
if (response.ok) {
// Redirect to the payment link
window.location.href = data.payment_url;
} else {
alert('Error creating payment link: ' + data.error);
}
} catch (error) {
alert('Error creating payment link: ' + error.message);
}
}
// Close modal when clicking outside
window.onclick = function(event) {
const modal = document.getElementById('paymentModal');
if (event.target == modal) {
closeModal();
}
}
</script>
</body>
</html>
"""
PAYMENT_TEMPLATE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Payment Link - AstraPay</title>
<style>
body {
margin: 0;
padding: 0;
min-height: 100vh;
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%);
display: flex;
justify-content: center;
align-items: center;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
color: #ffffff;
line-height: 1.6;
}
.container {
text-align: center;
max-width: 500px;
padding: 2rem;
}
.title {
font-size: 2.2rem;
font-weight: 600;
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;
}
.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);
}
.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);
}
.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 {
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;
}
</style>
</head>
<body>
<div class="container">
<h1 class="title">AstraPay</h1>
<div class="banner">
Payments May Reset Daily. Ensure your link works.
</div>
<div class="payment-card">
<div class="amount">{{ amount }} Astra{{ 's' if amount > 1 else '' }}</div>
<div class="description">{{ description }}</div>
<div class="status {{ 'paid' if paid else 'pending' }}">
{{ 'Paid' if paid else 'Pending Payment' }}
</div>
{% if not paid %}
<button class="pay-button" onclick="processPayment()">
Pay {{ amount }} Astra{{ 's' if amount > 1 else '' }}
</button>
{% else %}
<div style="font-size: 1.2rem; color: #00ff88;">✓ Payment Completed</div>
{% endif %}
</div>
<a href="/" class="back-link">← Back to AstraPay</a>
</div>
<!-- Payment Modal -->
<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 {{ amount }} Astra{{ 's' if amount > 1 else '' }} to the following address:
</div>
<div class="email-address">matthew@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 {{ amount }} Astra{{ 's' if 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()">Cancel</button>
<button id="confirm-btn" class="pay-button" onclick="confirmPayment()" style="display: none;">Confirm Payment</button>
<button id="verify-btn" class="pay-button" onclick="verifyEmail()" style="display: none;">Verify Email</button>
</div>
</div>
</div>
<script>
let paymentCheckInterval;
let timerInterval;
let existingTransactionIds = [];
let timeLeft = 300; // 5 minutes in seconds
let paymentTransaction = null;
function processPayment() {
document.getElementById('paymentModal').style.display = 'block';
timeLeft = 300; // Reset timer
paymentTransaction = null; // Reset payment data
// Initialize payment checking
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');
existingTransactionIds = [];
}
async function initPaymentCheck() {
try {
const response = await fetch('/init-payment-check/{{ payment_id }}');
const data = await response.json();
if (data.existing_transaction_ids) {
existingTransactionIds = data.existing_transaction_ids;
startPaymentCheck();
} else {
console.error('Failed to initialize payment check');
}
} catch (error) {
console.error('Error initializing payment check:', error);
}
}
function startPaymentCheck() {
paymentCheckInterval = setInterval(async () => {
if (timeLeft <= 0) return; // Don't check if expired
try {
const existingIdsParam = existingTransactionIds.join(',');
const response = await fetch(`/check-payment/{{ payment_id }}?existing_ids=${existingIdsParam}`);
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 = 'inline-block';
stopPaymentCheck();
}
} catch (error) {
console.log('Error checking payment status:', error);
}
}, 2000); // Check every 2 seconds
}
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 = 'inline-block';
} 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) {
// Hide all other elements and show confirmation
document.getElementById('payment-received').style.display = 'none';
document.getElementById('confirm-btn').style.display = 'none';
document.getElementById('confirmation-complete').style.display = 'block';
// Show sender email if available
if (paymentTransaction && paymentTransaction.from_email) {
document.getElementById('sender-email-display').textContent = `From: ${paymentTransaction.from_email}`;
}
stopTimer();
// Redirect after a delay
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';
}
}
// Close modal when clicking outside
window.onclick = function(event) {
const modal = document.getElementById('paymentModal');
if (event.target == modal) {
closePaymentModal();
}
}
// Stop checking when page unloads
window.onbeforeunload = function() {
stopPaymentCheck();
stopTimer();
};
</script>
</body>
</html>
"""
@app.route('/')
def home():
return render_template_string(HOME_TEMPLATE)
@app.route('/create-payment-link', methods=['POST'])
def create_payment_link():
try:
data = request.get_json()
if not data or 'amount' not in data or 'description' not in data:
return jsonify({'error': 'Missing required fields'}), 400
amount = data['amount']
description = data['description']
if not isinstance(amount, int) or amount <= 0:
return jsonify({'error': 'Invalid amount'}), 400
if not description or len(description.strip()) == 0:
return jsonify({'error': 'Description cannot be empty'}), 400
payment_link = PaymentLink(
amount=amount,
description=description.strip()
)
db.session.add(payment_link)
db.session.commit()
payment_url = url_for('view_payment', payment_id=payment_link.id, _external=True)
return jsonify({
'success': True,
'payment_id': payment_link.id,
'payment_url': payment_url
})
except Exception as e:
db.session.rollback()
return jsonify({'error': str(e)}), 500
@app.route('/init-payment-check/<payment_id>')
def init_payment_check(payment_id):
"""Initialize payment checking by storing current transaction state"""
try:
payment_link = PaymentLink.query.get_or_404(payment_id)
transactions = get_transactions()
existing_ids = [tx.get('id') for tx in transactions]
return jsonify({
'existing_transaction_ids': existing_ids,
'amount': payment_link.amount
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/check-payment/<payment_id>')
def check_payment_status(payment_id):
"""Check if payment has been received"""
try:
payment_link = PaymentLink.query.get_or_404(payment_id)
existing_ids_str = request.args.get('existing_ids', '')
if existing_ids_str:
existing_ids = existing_ids_str.split(',')
else:
transactions = get_transactions()
existing_ids = [tx.get('id') for tx in transactions]
payment_transaction = check_for_payment(payment_link.amount, existing_ids)
if payment_transaction:
return jsonify({
'payment_received': True,
'transaction': {
'id': payment_transaction.get('id'),
'amount': payment_transaction.get('amount'),
'from_email': payment_transaction.get('counterpartEmail'),
'description': payment_transaction.get('description')
}
})
else:
return jsonify({'payment_received': False})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/pay/<payment_id>', methods=['POST'])
def process_payment(payment_id):
try:
payment_link = PaymentLink.query.get_or_404(payment_id)
if payment_link.paid:
return jsonify({'error': 'Payment already completed'}), 400
payment_link.paid = True
db.session.commit()
return jsonify({'success': True, 'message': 'Payment confirmed successfully'})
except Exception as e:
db.session.rollback()
return jsonify({'error': str(e)}), 500
@app.route('/pay/<payment_id>')
def view_payment(payment_id):
payment_link = PaymentLink.query.get_or_404(payment_id)
return render_template_string(PAYMENT_TEMPLATE,
amount=payment_link.amount,
description=payment_link.description,
paid=payment_link.paid,
payment_id=payment_link.id)
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=7860)