"""模拟交易网关路由(PRD FR-1 · 薄路由,不含业务)。 鉴权:依赖 T-01 JWT(risk_demo 或客户本人 customer_id == subject); `get_auth_context` B6 落地后在此挂 `Depends`,本路由签名不变(开发计划 B5/B6 边界)。 trace:B7 中间件贯通;B7 前由 service 层 ensure_trace 兜底。 挂载:B7 集成 main.py(当前仅 TestClient 独立挂 router 验证)。 统一响应外壳:utils/response.py 为占位(P1 任务),落地点挂账 B7(届时 simulate/risk 一并包裹,本路由返回体不变)。 """ from __future__ import annotations from decimal import Decimal from fastapi import APIRouter, HTTPException from pydantic import BaseModel, Field from app.gateway.trade_gateway import UnsupportedTradeType, submit_trade router = APIRouter(prefix="/api/simulate", tags=["simulate"]) class TradeRequest(BaseModel): customer_id: str = Field(..., min_length=1) product_id: str = Field(..., min_length=1) trade_type: str = Field(..., max_length=16, description="subscribe | redeem;convert 显式拒绝") amount: Decimal = Field(..., gt=0, description="交易金额(元),必须为正数") @router.post("/trade") def submit_trade_api(req: TradeRequest) -> dict: """模拟交易(FR-1):适当性阻断或放行+引擎判定,返回 blocked + trade_id。""" try: return submit_trade(req.model_dump()) except UnsupportedTradeType as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc except LookupError as exc: # KeyError 亦为其子类;B6 统一 NotFoundError 时收敛为精确类型 raise HTTPException(status_code=404, detail=str(exc)) from exc