""" @router PaymentRouter (Backend/app/api/v1/routers/PaymentRouter.py) @purpose API router handling Razorpay order initiation, cryptographic verification, status checks, and webhook ingestion. """ from fastapi import APIRouter, Depends, HTTPException, Request, Header, status from sqlalchemy.orm import Session from pydantic import BaseModel from typing import Optional from app.core.database.db_session import get_db from app.api.v1.routers.CustomerProfileRouter import get_current_customer from app.models.EcomCustomerModel import EcomCustomer from app.models.PaymentLedgerModel import PaymentLedger from app.core.payment_orchestrator import payment_orchestrator router = APIRouter(prefix="/api/v1/payment", tags=["Razorpay Payment Engine"]) # --- Request / Response Models --- class PaymentInitiateRequest(BaseModel): order_id: str class PaymentInitiateResponse(BaseModel): order_id: str rzp_order_id: str rzp_key_id: str amount: int currency: str security_token: str class PaymentVerifyRequest(BaseModel): order_id: str razorpay_order_id: str razorpay_payment_id: str razorpay_signature: str @router.post("/initiate", response_model=PaymentInitiateResponse) def initiate_payment( payload: PaymentInitiateRequest, request: Request, customer: EcomCustomer = Depends(get_current_customer), db: Session = Depends(get_db) ): """ Step 1: Initiate payment intent for an e-commerce order (creates Razorpay Order). """ client_ip = request.client.host if request.client else None user_agent = request.headers.get("user-agent") result = payment_orchestrator.initiate_payment( db, payload.order_id, client_ip=client_ip, user_agent=user_agent ) if "error" in result: raise HTTPException(status_code=result["status_code"], detail=result["error"]) return result @router.post("/verify") def verify_payment( payload: PaymentVerifyRequest, customer: EcomCustomer = Depends(get_current_customer), db: Session = Depends(get_db) ): """ Step 2: Cryptographic signature verification + secondary server-side provider fetch. """ result = payment_orchestrator.verify_payment( db, order_id=payload.order_id, rzp_order_id=payload.razorpay_order_id, rzp_payment_id=payload.razorpay_payment_id, rzp_signature=payload.razorpay_signature ) if "error" in result: raise HTTPException(status_code=result["status_code"], detail=result["error"]) return result @router.get("/status/{order_id}") def get_payment_status( order_id: str, customer: EcomCustomer = Depends(get_current_customer), db: Session = Depends(get_db) ): """ Returns latest payment ledger status for an order. """ entry = ( db.query(PaymentLedger) .filter(PaymentLedger.order_id == order_id) .order_by(PaymentLedger.created_at.desc()) .first() ) if not entry: raise HTTPException(status_code=404, detail="No payment session found for this order") return { "order_id": order_id, "payment_id": entry.payment_id, "provider": entry.provider, "status": entry.status, "transaction_ref": entry.transaction_ref, "amount": float(entry.amount), "currency": entry.currency, "created_at": entry.created_at } @router.post("/webhook") async def razorpay_webhook( request: Request, db: Session = Depends(get_db) ): """ Asynchronous Webhook receiver with HMAC verification and idempotency check. """ raw_body = await request.body() signature = request.headers.get("X-Razorpay-Signature") or request.headers.get("x-razorpay-signature") if not signature: raise HTTPException(status_code=400, detail="Missing X-Razorpay-Signature header") success = payment_orchestrator.process_webhook(db, raw_body, signature) if not success: return {"status": "ignored", "reason": "invalid_signature_or_payload"} return {"status": "SUCCESS"}