from typing import Annotated, Any from fastapi import APIRouter, Depends, Header, Request, Response, status from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from pydantic import BaseModel, Field from app.api.responses import success_response from app.core.config import Settings from app.core.errors import AppError from app.domains.enrollment.service import EnrollmentService from app.domains.identity.models import H5User from app.domains.identity.service import H5AuthService enrollment_bearer_scheme = HTTPBearer(auto_error=False) class InsuredInput(BaseModel): age: int = Field(ge=0, le=120) region_code: str = Field(min_length=6, max_length=12) occupation_code: str = Field(min_length=1, max_length=32) class CreateQuoteRequest(BaseModel): product_id: str = Field(min_length=26, max_length=26) plan_id: str = Field(min_length=26, max_length=26) insured: InsuredInput relationship: str = Field(pattern="^(SELF|PARENT|SPOUSE|CHILD)$") class PersonInput(BaseModel): name: str = Field(min_length=1, max_length=64) id_no: str = Field(min_length=6, max_length=32) class ContactInput(BaseModel): mobile: str = Field(min_length=11, max_length=11) class CreateDraftRequest(BaseModel): quote_id: str = Field(min_length=26, max_length=26) applicant: PersonInput insured: PersonInput contact: ContactInput class CreateOrderRequest(BaseModel): draft_id: str = Field(min_length=26, max_length=26) confirmation_token: str = Field(min_length=32, max_length=128) class MockPaymentCallbackRequest(BaseModel): callback_no: str = Field(min_length=1, max_length=64) provider_transaction_no: str = Field(min_length=1, max_length=64) payment_no: str = Field(min_length=1, max_length=40) status: str = Field(pattern="^(SUCCEEDED|FAILED)$") amount_cents: int = Field(gt=0) occurred_at: str = Field(min_length=10, max_length=64) def create_enrollment_router( service: EnrollmentService, h5_auth_service: H5AuthService, settings: Settings, ) -> APIRouter: router = APIRouter(tags=["enrollment"]) def current_h5_user( credentials: Annotated[ HTTPAuthorizationCredentials | None, Depends(enrollment_bearer_scheme), ], ) -> H5User: if credentials is None: raise AppError("AUTH_REQUIRED", "请先登录", 401) return h5_auth_service.authenticate_access(credentials.credentials) @router.post("/h5/quotes", status_code=status.HTTP_201_CREATED) def create_quote( payload: CreateQuoteRequest, request: Request, user: Annotated[H5User, Depends(current_h5_user)], ) -> dict[str, Any]: return success_response( request, service.create_quote( user, product_id=payload.product_id, plan_id=payload.plan_id, age=payload.insured.age, region_code=payload.insured.region_code, occupation_code=payload.insured.occupation_code, relationship=payload.relationship, ), ) @router.post("/h5/enrollment-drafts", status_code=status.HTTP_201_CREATED) def create_draft( payload: CreateDraftRequest, request: Request, user: Annotated[H5User, Depends(current_h5_user)], ) -> dict[str, Any]: return success_response( request, service.create_draft( user, quote_id=payload.quote_id, applicant=payload.applicant.model_dump(), insured=payload.insured.model_dump(), contact=payload.contact.model_dump(), ), ) @router.post( "/h5/enrollment-drafts/{draft_id}/confirmation", status_code=status.HTTP_201_CREATED, ) def confirm_draft( draft_id: str, request: Request, user: Annotated[H5User, Depends(current_h5_user)], ) -> dict[str, Any]: return success_response(request, service.confirm_draft(user, draft_id)) @router.post("/h5/orders", status_code=status.HTTP_201_CREATED) def create_order( payload: CreateOrderRequest, request: Request, response: Response, user: Annotated[H5User, Depends(current_h5_user)], idempotency_key: Annotated[ str, Header(alias="Idempotency-Key", min_length=1, max_length=64), ], ) -> dict[str, Any]: data, created = service.create_order( user, draft_id=payload.draft_id, confirmation_token=payload.confirmation_token, idempotency_key=idempotency_key, ) response.status_code = status.HTTP_201_CREATED if created else status.HTTP_200_OK return success_response(request, data) @router.post( "/h5/orders/{order_id}/payments", status_code=status.HTTP_201_CREATED, ) def create_payment( order_id: str, request: Request, response: Response, user: Annotated[H5User, Depends(current_h5_user)], idempotency_key: Annotated[ str, Header(alias="Idempotency-Key", min_length=1, max_length=64), ], ) -> dict[str, Any]: data, created = service.create_payment( user, order_id=order_id, idempotency_key=idempotency_key, ) response.status_code = status.HTTP_201_CREATED if created else status.HTTP_200_OK return success_response(request, data) @router.post("/dev/mock-payments/{payment_id}/complete") def complete_mock_payment( payment_id: str, request: Request, user: Annotated[H5User, Depends(current_h5_user)], ) -> dict[str, Any]: if not settings.dev_tools_enabled or settings.app_env == "production": raise AppError("DEV_TOOL_DISABLED", "本地模拟支付未启用", 404) return success_response( request, service.complete_mock_payment(user, payment_id=payment_id), ) @router.post("/callbacks/mock-payment") def mock_payment_callback( payload: MockPaymentCallbackRequest, request: Request, signature: Annotated[ str, Header(alias="X-Mock-Pay-Signature", min_length=1, max_length=128), ], ) -> dict[str, Any]: return success_response( request, service.handle_mock_callback(payload.model_dump(), signature), ) @router.get("/h5/policies") def list_policies( request: Request, user: Annotated[H5User, Depends(current_h5_user)], ) -> dict[str, Any]: return success_response(request, service.list_policies(user)) @router.get("/h5/orders") def list_orders( request: Request, user: Annotated[H5User, Depends(current_h5_user)], ) -> dict[str, Any]: return success_response(request, service.list_orders(user.id)) return router