"""파이썬(Flask) 가맹점 연동 예: 충전 신청 → 결제 팝업 → 웹훅으로 보유금 지급. GW_URL, GW_API_KEY, GW_WEBHOOK_SECRET, GW_XPUB(B/C) 환경변수. 표는 sqlite 로 단순화.""" import os import secrets import sqlite3 from decimal import Decimal import requests from flask import Flask, abort, jsonify, request, session from gateway_sdk import check_order, verify_webhook GW = os.environ["GW_URL"].rstrip("/") app = Flask(__name__) app.secret_key = os.environ["SESSION_SECRET"] db = sqlite3.connect("shop.db", isolation_level=None, check_same_thread=False) db.executescript(""" CREATE TABLE IF NOT EXISTS charges(order_id TEXT PRIMARY KEY, user_id TEXT, gw_id TEXT, currency TEXT, amount TEXT, status TEXT DEFAULT 'pending', credit TEXT); CREATE TABLE IF NOT EXISTS credits(event_id TEXT PRIMARY KEY, order_id TEXT, amount TEXT); CREATE TABLE IF NOT EXISTS balances(user_id TEXT, currency TEXT, amount TEXT DEFAULT '0', PRIMARY KEY(user_id, currency)); """) @app.post("/charge") def charge(): uid = session.get("user_id") or abort(401) b = request.get_json(silent=True) or {} cur = (b.get("currency") or "KRW").upper() oid = f"chg-{uid}-{secrets.token_hex(6)}" body = {"order_id": oid, "amount": str(b.get("amount")), "customer_ref": str(uid)} if cur != "USDT": body["currency"] = cur r = requests.post(f"{GW}/v1/orders", json=body, headers={"Authorization": "Bearer " + os.environ["GW_API_KEY"]}, timeout=15) if r.status_code != 201: return jsonify(error=r.json().get("error", "gateway")), 502 o = check_order(r.json(), xpub=os.environ["GW_XPUB"]) # 주소가 내 키에서 나온 것인지 (서버 털림 대비) db.execute("INSERT INTO charges(order_id,user_id,gw_id,currency,amount) VALUES(?,?,?,?,?)", (oid, uid, o["id"], o["currency"], o["fiat_amount"] or o["amount"])) return jsonify(id=o["id"]) @app.post("/gw/webhook") def webhook(): raw = request.get_data() if not verify_webhook(os.environ["GW_WEBHOOK_SECRET"], request.headers, raw): abort(400) ev = request.get_json() o = ev["data"] # 받은 만큼 지급: 입금마다 credit_delta 를 웹훅(id)당 한 번 더함 (부족=받은 만큼, 추가=그만큼 더, 초과=넘친 만큼) if ev["type"] in ("order.partial", "order.paid") and Decimal(o["credit_delta"]) > 0: try: db.execute("BEGIN IMMEDIATE") if db.execute("INSERT OR IGNORE INTO credits(event_id,order_id,amount) VALUES(?,?,?)", (ev["id"], o["order_id"], o["credit_delta"])).rowcount == 1: uid, cur = db.execute("SELECT user_id, currency FROM charges WHERE order_id=?", (o["order_id"],)).fetchone() # ponytail: 금액을 문자열로 저장, 실서비스는 정수(원·센트 단위) 열 권장 old = db.execute("SELECT amount FROM balances WHERE user_id=? AND currency=?", (uid, cur)).fetchone() new = Decimal(old[0] if old else "0") + Decimal(o["credit_delta"]) db.execute("INSERT OR REPLACE INTO balances(user_id,currency,amount) VALUES(?,?,?)", (uid, cur, str(new))) db.execute("UPDATE charges SET status=?, credit=? WHERE order_id=?", (o["status"], o["credit"], o["order_id"])) db.execute("COMMIT") except Exception: db.execute("ROLLBACK") raise elif ev["type"] == "order.expired": db.execute("UPDATE charges SET status='expired' WHERE order_id=?", (o["order_id"],)) return "", 200