Spaces:
Running
Running
Rename payment.html to payment.py
Browse files- payment.html +0 -74
- payment.py +64 -0
payment.html
DELETED
|
@@ -1,74 +0,0 @@
|
|
| 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>Buy Coins - Recap Studio</title>
|
| 7 |
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 8 |
-
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
| 9 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
|
| 10 |
-
<style>
|
| 11 |
-
*{margin:0;padding:0;box-sizing:border-box}
|
| 12 |
-
body{font-family:'Plus Jakarta Sans',sans-serif;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);min-height:100vh;padding:20px}
|
| 13 |
-
.container{max-width:500px;margin:0 auto}
|
| 14 |
-
.header{text-align:center;margin-bottom:24px}
|
| 15 |
-
.header h1{color:white;font-size:1.8rem}
|
| 16 |
-
.packages{display:grid;gap:12px;margin-bottom:24px}
|
| 17 |
-
.package{background:white;border-radius:16px;padding:16px;display:flex;align-items:center;justify-content:space-between;cursor:pointer;border:2px solid transparent;transition:all .2s}
|
| 18 |
-
.package.selected{border-color:#5b4cf5;background:#f5f3ff}
|
| 19 |
-
.package-info h3{font-size:1rem;margin-bottom:4px}
|
| 20 |
-
.package-info .coins{font-size:1.3rem;font-weight:800;color:#5b4cf5}
|
| 21 |
-
.package-info .price{font-size:.75rem;color:#666}
|
| 22 |
-
.radio{width:22px;height:22px;border-radius:50%;border:2px solid #ddd;background:white}
|
| 23 |
-
.package.selected .radio{border-color:#5b4cf5;background:#5b4cf5;box-shadow:inset 0 0 0 4px white}
|
| 24 |
-
.payment-info{background:white;border-radius:16px;padding:20px;margin-bottom:20px}
|
| 25 |
-
.payment-info h3{margin-bottom:12px;font-size:1rem}
|
| 26 |
-
.bank-details{background:#f8f9fa;border-radius:12px;padding:16px;margin-bottom:16px}
|
| 27 |
-
.bank-row{display:flex;justify-content:space-between;margin-bottom:8px;font-size:.85rem}
|
| 28 |
-
.bank-label{color:#666}
|
| 29 |
-
.bank-value{font-weight:600;color:#333}
|
| 30 |
-
.copy-btn{background:#e9ecef;border:none;padding:4px 10px;border-radius:6px;font-size:.7rem;cursor:pointer;margin-left:8px}
|
| 31 |
-
.slip-area{margin-top:16px}
|
| 32 |
-
.slip-label{font-size:.8rem;font-weight:600;margin-bottom:8px;display:block}
|
| 33 |
-
.slip-input{width:100%;padding:12px;border:1.5px solid #e2e4ee;border-radius:10px;font-family:inherit;cursor:pointer}
|
| 34 |
-
.slip-preview{margin-top:12px;max-width:100%;max-height:200px;display:none;border-radius:8px}
|
| 35 |
-
.submit-btn{width:100%;padding:14px;background:linear-gradient(135deg,#5b4cf5,#8b5cf6);border:none;border-radius:12px;color:white;font-weight:700;font-size:1rem;cursor:pointer;transition:.2s;margin-top:16px}
|
| 36 |
-
.submit-btn:disabled{opacity:.5;cursor:not-allowed}
|
| 37 |
-
.back-btn{background:transparent;border:1px solid white;color:white;padding:10px;border-radius:10px;width:100%;font-size:.85rem;cursor:pointer;margin-top:12px}
|
| 38 |
-
.toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:#333;color:white;padding:10px 20px;border-radius:30px;font-size:.8rem;opacity:0;transition:.2s;pointer-events:none}
|
| 39 |
-
.toast.show{opacity:1}
|
| 40 |
-
</style>
|
| 41 |
-
</head>
|
| 42 |
-
<body>
|
| 43 |
-
<div class="container">
|
| 44 |
-
<div class="header"><h1>💰 Buy Coins</h1></div>
|
| 45 |
-
<div class="packages" id="packages">
|
| 46 |
-
<div class="package" data-coins="10" data-price="10,000 MMK" onclick="selectPackage(this)"><div class="package-info"><h3>🥉 Starter</h3><div class="coins">10 Coins</div><div class="price">10,000 MMK</div></div><div class="radio"></div></div>
|
| 47 |
-
<div class="package" data-coins="30" data-price="28,000 MMK" onclick="selectPackage(this)"><div class="package-info"><h3>⭐ Basic</h3><div class="coins">30 Coins</div><div class="price">28,000 MMK</div></div><div class="radio"></div></div>
|
| 48 |
-
<div class="package" data-coins="60" data-price="58,000 MMK" onclick="selectPackage(this)"><div class="package-info"><h3>🔥 Pro</h3><div class="coins">60 Coins</div><div class="price">58,000 MMK</div></div><div class="radio"></div></div>
|
| 49 |
-
<div class="package" data-coins="100" data-price="95,000 MMK" onclick="selectPackage(this)"><div class="package-info"><h3>💎 Unlimited</h3><div class="coins">100 Coins</div><div class="price">95,000 MMK</div></div><div class="radio"></div></div>
|
| 50 |
-
</div>
|
| 51 |
-
<div class="payment-info">
|
| 52 |
-
<h3>💸 Payment Method</h3>
|
| 53 |
-
<div class="bank-details">
|
| 54 |
-
<div class="bank-row"><span class="bank-label">KBZ Pay</span><span class="bank-value">PHOE SHAN<button class="copy-btn" onclick="copyText('PHOE SHAN')">Copy</button></span></div>
|
| 55 |
-
<div class="bank-row"><span class="bank-label">Phone Number</span><span class="bank-value">09679871352<button class="copy-btn" onclick="copyText('09679871352')">Copy</button></span></div>
|
| 56 |
-
</div>
|
| 57 |
-
<div class="slip-area"><label class="slip-label">📸 Upload Payment Slip (Screenshot)</label><input type="file" id="slipImage" class="slip-input" accept="image/*"><img id="slipPreview" class="slip-preview"></div>
|
| 58 |
-
<button class="submit-btn" id="submitBtn" onclick="submitPayment()">📤 Submit Payment</button>
|
| 59 |
-
<button class="back-btn" onclick="window.location.href='/app'">← Back to App</button>
|
| 60 |
-
</div>
|
| 61 |
-
</div>
|
| 62 |
-
<div id="toast" class="toast"></div>
|
| 63 |
-
|
| 64 |
-
<script>
|
| 65 |
-
let selectedPackage=null,selectedCoins=null,selectedPrice=null;
|
| 66 |
-
function selectPackage(el){document.querySelectorAll('.package').forEach(p=>p.classList.remove('selected'));el.classList.add('selected');selectedPackage=el;selectedCoins=el.dataset.coins;selectedPrice=el.dataset.price;}
|
| 67 |
-
function copyText(t){navigator.clipboard.writeText(t);showToast('✅ Copied: '+t);}
|
| 68 |
-
function showToast(m){const t=document.getElementById('toast');t.textContent=m;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),2000);}
|
| 69 |
-
document.getElementById('slipImage').addEventListener('change',function(e){const f=e.target.files[0];if(f){const r=new FileReader();r.onload=function(ev){const p=document.getElementById('slipPreview');p.src=ev.target.result;p.style.display='block';};r.readAsDataURL(f);}});
|
| 70 |
-
async function submitPayment(){if(!selectedPackage){showToast('❌ Please select a package');return;}const slipFile=document.getElementById('slipImage').files[0];if(!slipFile){showToast('❌ Please upload payment slip');return;}const user=sessionStorage.getItem('recap_user');if(!user){showToast('❌ Please login first');window.location.href='/login';return;}const username=JSON.parse(user).u;const btn=document.getElementById('submitBtn');btn.disabled=true;btn.textContent='⏳ Submitting...';const reader=new FileReader();reader.onload=async function(ev){try{const res=await fetch('/api/payment/submit',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:username,coins:parseInt(selectedCoins),price:selectedPrice,slip_image:ev.target.result})});const data=await res.json();if(data.ok){showToast('✅ Payment submitted! Waiting for admin approval.');setTimeout(()=>{window.location.href='/payment-history';},1500);}else{showToast('❌ '+data.msg);btn.disabled=false;btn.textContent='📤 Submit Payment';}}catch(err){showToast('❌ Network error');btn.disabled=false;btn.textContent='📤 Submit Payment';}};reader.readAsDataURL(slipFile);}
|
| 71 |
-
setTimeout(()=>{const fp=document.querySelector('.package');if(fp)selectPackage(fp);},100);
|
| 72 |
-
</script>
|
| 73 |
-
</body>
|
| 74 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
payment.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# payment.py
|
| 2 |
+
import os, json, uuid
|
| 3 |
+
from datetime import datetime
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
PAYMENT_DB_FILE = str(Path(__file__).parent / 'payments_db.json')
|
| 7 |
+
|
| 8 |
+
def load_payments():
|
| 9 |
+
if not os.path.exists(PAYMENT_DB_FILE):
|
| 10 |
+
return {'payments': []}
|
| 11 |
+
try:
|
| 12 |
+
with open(PAYMENT_DB_FILE, encoding='utf-8') as f:
|
| 13 |
+
return json.load(f)
|
| 14 |
+
except:
|
| 15 |
+
return {'payments': []}
|
| 16 |
+
|
| 17 |
+
def save_payments(db):
|
| 18 |
+
with open(PAYMENT_DB_FILE, 'w', encoding='utf-8') as f:
|
| 19 |
+
json.dump(db, f, ensure_ascii=False, indent=2)
|
| 20 |
+
|
| 21 |
+
def create_payment(username, coins, price, slip_image_data=None):
|
| 22 |
+
db = load_payments()
|
| 23 |
+
payment_id = str(uuid.uuid4())[:8]
|
| 24 |
+
payment = {
|
| 25 |
+
'id': payment_id,
|
| 26 |
+
'username': username,
|
| 27 |
+
'coins': coins,
|
| 28 |
+
'price': price,
|
| 29 |
+
'status': 'pending',
|
| 30 |
+
'created_at': datetime.now().isoformat(),
|
| 31 |
+
'updated_at': datetime.now().isoformat(),
|
| 32 |
+
'slip_image': slip_image_data,
|
| 33 |
+
'admin_note': ''
|
| 34 |
+
}
|
| 35 |
+
db['payments'].insert(0, payment)
|
| 36 |
+
save_payments(db)
|
| 37 |
+
return payment_id
|
| 38 |
+
|
| 39 |
+
def update_payment_status(payment_id, status, admin_note=''):
|
| 40 |
+
db = load_payments()
|
| 41 |
+
for p in db['payments']:
|
| 42 |
+
if p['id'] == payment_id:
|
| 43 |
+
p['status'] = status
|
| 44 |
+
p['updated_at'] = datetime.now().isoformat()
|
| 45 |
+
if admin_note:
|
| 46 |
+
p['admin_note'] = admin_note
|
| 47 |
+
save_payments(db)
|
| 48 |
+
return p
|
| 49 |
+
return None
|
| 50 |
+
|
| 51 |
+
def get_user_payments(username, limit=50):
|
| 52 |
+
db = load_payments()
|
| 53 |
+
return [p for p in db['payments'] if p['username'] == username][:limit]
|
| 54 |
+
|
| 55 |
+
def get_pending_payments():
|
| 56 |
+
db = load_payments()
|
| 57 |
+
return [p for p in db['payments'] if p['status'] == 'pending']
|
| 58 |
+
|
| 59 |
+
def get_payment_by_id(payment_id):
|
| 60 |
+
db = load_payments()
|
| 61 |
+
for p in db['payments']:
|
| 62 |
+
if p['id'] == payment_id:
|
| 63 |
+
return p
|
| 64 |
+
return None
|