94 lines
3.5 KiB
Python
94 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
# Pest Nocturne — WSU EM019 study suite, freemium paywall (BTCPay)
|
|
import os, json, uuid, time, sqlite3, hmac, hashlib
|
|
from flask import Flask, request, jsonify, send_from_directory, redirect
|
|
|
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
DB_PATH = os.path.join(BASE_DIR, "orders.db")
|
|
HTML = os.path.join(BASE_DIR, "index.html")
|
|
|
|
# BTCPay config (store FDT6DHWEaA7DF5WFePkp4pufgQ9yy1G6JfzTCwey1jYn)
|
|
BTCPAY_URL = os.environ.get("BTCPAY_URL", "https://10.30.20.140")
|
|
BTCPAY_STORE = os.environ.get("BTCPAY_STORE", "FDT6DHWEaA7DF5WFePkp4pufgQ9yy1G6JfzTCwey1jYn")
|
|
BTCPAY_KEY = os.environ.get("BTCPAY_KEY", "206f08d0a8efc4cfbe524188756c5bd1b2fd277a")
|
|
PRICE_USD = os.environ.get("PRICE_USD", "5.00")
|
|
WEBHOOK_SECRET = os.environ.get("WEBHOOK_SECRET", "pn-nocturne-webhook-secret-1788251891")
|
|
|
|
app = Flask(__name__)
|
|
|
|
def db():
|
|
c = sqlite3.connect(DB_PATH)
|
|
c.execute("CREATE TABLE IF NOT EXISTS orders (id TEXT PRIMARY KEY, created REAL, paid INTEGER DEFAULT 0)")
|
|
c.commit()
|
|
return c
|
|
|
|
def create_btcpay_invoice(order_id):
|
|
"""Create a BTCPay invoice and return its checkout URL, or None."""
|
|
import urllib.request, ssl
|
|
ctx = ssl.create_default_context()
|
|
ctx.check_hostname = False
|
|
ctx.verify_mode = ssl.CERT_NONE # BTCPay uses a self-signed cert
|
|
payload = json.dumps({
|
|
"amount": PRICE_USD,
|
|
"currency": "USD",
|
|
"metadata": {"orderId": order_id},
|
|
"checkout": {"redirectURL": f"/?paid=1&order={order_id}"},
|
|
}).encode()
|
|
req = urllib.request.Request(
|
|
f"{BTCPAY_URL}/api/v1/stores/{BTCPAY_STORE}/invoices",
|
|
data=payload, method="POST",
|
|
headers={"Authorization": f"token {BTCPAY_KEY}", "Content-Type": "application/json"},
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=20, context=ctx) as r:
|
|
data = json.loads(r.read())
|
|
return data.get("checkoutLink") or data.get("checkout_url")
|
|
except Exception as e:
|
|
app.logger.error(f"BTCPay invoice error: {e}")
|
|
return None
|
|
|
|
@app.route("/")
|
|
def index():
|
|
return send_from_directory(BASE_DIR, "index.html")
|
|
|
|
@app.route("/api/status")
|
|
def api_status():
|
|
oid = request.args.get("order", "")
|
|
c = db()
|
|
row = c.execute("SELECT paid FROM orders WHERE id=?", (oid,)).fetchone()
|
|
c.close()
|
|
return jsonify({"paid": bool(row and row[0])})
|
|
|
|
@app.route("/unlock", methods=["POST"])
|
|
def unlock():
|
|
order_id = uuid.uuid4().hex[:16]
|
|
c = db()
|
|
c.execute("INSERT INTO orders (id, created, paid) VALUES (?,?,0)", (order_id, time.time()))
|
|
c.commit()
|
|
c.close()
|
|
checkout_url = create_btcpay_invoice(order_id)
|
|
if not checkout_url:
|
|
return jsonify({"error": "could not create invoice"}), 502
|
|
return jsonify({"order_id": order_id, "checkout_url": checkout_url})
|
|
|
|
@app.route("/webhook/btcpay", methods=["POST"])
|
|
def webhook():
|
|
raw = request.get_data()
|
|
sig = request.headers.get("BTCPay-Sig", "")
|
|
exp = "sha256=" + hmac.new(WEBHOOK_SECRET.encode(), raw, hashlib.sha256).hexdigest()
|
|
if not hmac.compare_digest(exp, sig):
|
|
return "bad sig", 401
|
|
data = json.loads(raw) if raw else {}
|
|
etype = data.get("type", "")
|
|
order_id = (data.get("metadata") or {}).get("orderId")
|
|
if etype == "InvoiceSettled" and order_id:
|
|
c = db()
|
|
c.execute("UPDATE orders SET paid=1 WHERE id=?", (order_id,))
|
|
c.commit()
|
|
c.close()
|
|
app.logger.info(f"order {order_id} settled")
|
|
return jsonify({"ok": True})
|
|
|
|
if __name__ == "__main__":
|
|
app.run(host="0.0.0.0", port=5000)
|