File size: 5,727 Bytes
6733714
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
"""Razorpay payment integration.

Routes
------
POST /api/payments/create-order   β€” create a Razorpay order
POST /api/payments/verify          β€” verify payment signature after checkout
GET  /api/payments/config          β€” return Razorpay key_id to frontend (safe)
"""
import os
import hmac
import hashlib
import logging
from datetime import datetime, timedelta, timezone
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from app.security import get_jwt_user
from app.database import get_user_collection

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/payments", tags=["payments"])

# ── Plans ──────────────────────────────────────────────────────────────────────
PLANS = {
    "1hr": {
        "name": "1-Hour Access",
        "description": "Full module access for 1 hour",
        "amount": 1000,         # paise (β‚Ή10 Γ— 100) β€” TEST AMOUNT
        "currency": "INR",
        "duration_hours": 1,
    },
    "3hr": {
        "name": "3-Hour Access",
        "description": "Full module access for 3 hours",
        "amount": 1000,         # paise (β‚Ή10 Γ— 100) β€” TEST AMOUNT
        "currency": "INR",
        "duration_hours": 3,
    },
}


def _razorpay_client():
    key_id = os.getenv("RAZORPAY_KEY_ID", "")
    key_secret = os.getenv("RAZORPAY_KEY_SECRET", "")
    if not key_id or not key_secret:
        raise HTTPException(
            status_code=503,
            detail="Payment system not yet configured. Please add Razorpay keys to activate."
        )
    import razorpay
    return razorpay.Client(auth=(key_id, key_secret))


# ── Schemas ────────────────────────────────────────────────────────────────────

class CreateOrderRequest(BaseModel):
    plan_id: str     # "1hr" or "3hr"
    module: str      # "gst" | "fema" | "company-law" | "income-tax"

class VerifyPaymentRequest(BaseModel):
    razorpay_order_id: str
    razorpay_payment_id: str
    razorpay_signature: str
    plan_id: str
    module: str


# ── Endpoints ─────────────────────────────────────────────────────────────────

@router.get("/config")
def get_payment_config():
    """Return the Razorpay publishable key to the frontend. Never exposes the secret."""
    key_id = os.getenv("RAZORPAY_KEY_ID", "")
    return {
        "key_id": key_id,
        "configured": bool(key_id),
        "plans": PLANS,
    }


@router.post("/create-order")
def create_order(req: CreateOrderRequest):
    plan = PLANS.get(req.plan_id)
    if not plan:
        raise HTTPException(status_code=400, detail=f"Unknown plan: {req.plan_id}")

    client = _razorpay_client()
    order = client.order.create({
        "amount": plan["amount"],
        "currency": plan["currency"],
        "receipt": f"{req.module}_{req.plan_id}",
        "notes": {
            "module": req.module,
            "plan_id": req.plan_id,
            "duration_hours": str(plan["duration_hours"]),
        },
    })
    logger.info(f"Razorpay order created: {order['id']} | {req.module}/{req.plan_id}")
    return {"order_id": order["id"], "amount": order["amount"], "currency": order["currency"]}


@router.post("/verify")
def verify_payment(
    req: VerifyPaymentRequest,
    current_user: dict = Depends(get_jwt_user),
):
    """Verify the HMAC signature returned by Razorpay and start the session timer."""
    key_secret = os.getenv("RAZORPAY_KEY_SECRET", "")
    if not key_secret:
        raise HTTPException(status_code=503, detail="Payment system not configured.")

    body = f"{req.razorpay_order_id}|{req.razorpay_payment_id}"
    expected = hmac.new(
        key_secret.encode(), body.encode(), hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected, req.razorpay_signature):
        raise HTTPException(status_code=400, detail="Payment verification failed.")

    plan_cfg       = PLANS.get(req.plan_id, {})
    duration_hours = plan_cfg.get("duration_hours", 1)
    now_utc        = datetime.now(timezone.utc)
    session_end_dt = now_utc + timedelta(hours=duration_hours)
    session_end_ms = int(session_end_dt.timestamp() * 1000)

    # Map plan_id to the internal plan name stored on the user record
    plan_name = "pro" if duration_hours >= 3 else "basic"

    username  = current_user.get("username")
    users_col = get_user_collection()
    if users_col and username:
        users_col.update_one(
            {"username": username},
            {"$set": {
                "plan":             plan_name,
                "session_end":      session_end_dt,   # UTC datetime β€” enforced by security.py
                "last_payment_id":  req.razorpay_payment_id,
                "last_payment_at":  now_utc,
            }},
        )
        logger.info(
            f"Session started: user={username} plan={plan_name} "
            f"payment={req.razorpay_payment_id} expires={session_end_dt.isoformat()}"
        )

    return {
        "verified":       True,
        "module":         req.module,
        "plan_id":        req.plan_id,
        "plan_name":      plan_name,        # "pro" | "basic" β€” frontend updates user.plan in session
        "duration_hours": duration_hours,
        "payment_id":     req.razorpay_payment_id,
        "session_end_ms": session_end_ms,  # frontend writes into localStorage β†’ SessionClock starts
    }