Idk / app /api /billing.py
Himankpro's picture
Upload 93 files
da003c3 verified
Raw History Blame Contribute Delete
5.11 kB
"""Razorpay order + verify; coupon waivers mint the same billing_token as paid checkout."""
from __future__ import annotations
import traceback
import uuid
from flask import jsonify, request
from . import billing_bp
from ..config import Config
from ..services.supabase_jobs import sync_enabled
from ..utils.billing_token import coupon_codes_set, mint_billing_token
from ..utils.logger import get_logger
from ..utils.supabase_auth import resolve_pipeline_user_id
logger = get_logger("mirofish.api.billing")
def _razorpay_enabled() -> bool:
return bool(Config.RAZORPAY_KEY_ID and Config.RAZORPAY_KEY_SECRET)
def _require_pipeline_user_when_sync():
"""Same identity as /api/pipeline/* so billing cannot succeed while start would 401."""
if request.method == "OPTIONS":
return None
if not sync_enabled():
return None
uid, err = resolve_pipeline_user_id()
if err or not uid:
return (
jsonify(
{
"success": False,
"error": err
or "Unauthorized — sign in and ensure VITE_PIPELINE_CLIENT_SECRET matches server PIPELINE_CLIENT_SECRET (required to record runs in Supabase).",
}
),
401,
)
return None
@billing_bp.route("/order", methods=["POST"])
def billing_create_order():
"""
JSON body: { "coupon": "optional" }
- Valid coupon → { waived, billing_token }
- Razorpay configured → { waived: false, key_id, order_id, amount, currency }
- Razorpay not configured → dev bypass { waived, billing_token, dev_bypass }
"""
try:
gate = _require_pipeline_user_when_sync()
if gate:
return gate
data = request.get_json(silent=True) or {}
coupon = (data.get("coupon") or "").strip().upper()
codes = coupon_codes_set()
if coupon and coupon in codes:
tok = mint_billing_token(source="coupon")
return jsonify({"success": True, "data": {"waived": True, "billing_token": tok}})
if not _razorpay_enabled():
tok = mint_billing_token(source="dev_bypass")
return jsonify(
{
"success": True,
"data": {"waived": True, "billing_token": tok, "dev_bypass": True},
}
)
import razorpay
client = razorpay.Client(auth=(Config.RAZORPAY_KEY_ID, Config.RAZORPAY_KEY_SECRET))
receipt = "mf_" + str(uuid.uuid4()).replace("-", "")[:24]
order = client.order.create(
{
"amount": Config.BILLING_AMOUNT_MINOR,
"currency": Config.BILLING_CURRENCY,
"receipt": receipt,
"payment_capture": 1,
}
)
return jsonify(
{
"success": True,
"data": {
"waived": False,
"key_id": Config.RAZORPAY_KEY_ID,
"order_id": order["id"],
"amount": int(order["amount"]),
"currency": str(order["currency"]),
},
}
)
except Exception as e:
logger.error(traceback.format_exc())
return jsonify({"success": False, "error": str(e)}), 500
@billing_bp.route("/verify", methods=["POST"])
def billing_verify():
"""JSON: razorpay_payment_id, razorpay_order_id, razorpay_signature → billing_token."""
try:
gate = _require_pipeline_user_when_sync()
if gate:
return gate
if not _razorpay_enabled():
return jsonify({"success": False, "error": "Razorpay is not configured"}), 503
import razorpay
from razorpay.errors import SignatureVerificationError
data = request.get_json(silent=True) or {}
payment_id = (data.get("razorpay_payment_id") or "").strip()
order_id = (data.get("razorpay_order_id") or "").strip()
signature = (data.get("razorpay_signature") or "").strip()
if not payment_id or not order_id or not signature:
return jsonify({"success": False, "error": "Missing payment fields"}), 400
client = razorpay.Client(auth=(Config.RAZORPAY_KEY_ID, Config.RAZORPAY_KEY_SECRET))
try:
client.utility.verify_payment_signature(
{
"razorpay_order_id": order_id,
"razorpay_payment_id": payment_id,
"razorpay_signature": signature,
}
)
except SignatureVerificationError:
return jsonify({"success": False, "error": "Invalid payment signature"}), 400
tok = mint_billing_token(source="razorpay")
return jsonify({"success": True, "data": {"billing_token": tok}})
except Exception as e:
logger.error(traceback.format_exc())
return jsonify({"success": False, "error": str(e)}), 500