#!/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"https://nocturne.thetempleofdoom.com/?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()) url = data.get("checkoutLink") or data.get("checkout_url") if url: # API returns a LAN address; rewrite to the public BTCPay domain # so customers outside the network can reach the checkout. url = url.replace("10.30.20.140", "btcpay.thetempleofdoom.com") return 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)