from dataclasses import dataclass from datetime import datetime from typing import Literal from fastapi import Query from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from app.common.enums import QueueEnum from app.core.base_params import BaseQueryParam from app.core.base_schema import BaseSchema, TenantBySchema class OrderCreateInternalSchema(BaseModel): """订单创建(内部 CRUD 用,包含所有业务字段)""" order_no: str = Field(..., description="订单号") tenant_id: int = Field(..., description="租户ID") package_id: int | None = Field(default=None, description="套餐ID") plugin_id: int | None = Field(default=None, description="插件ID") order_type: str = Field(..., description="订单类型") amount: int = Field(..., description="订单金额(分)") period_count: int = Field(default=1, description="时长(月)") pay_method: str | None = Field(default=None, description="支付方式") pay_time: datetime | None = Field(default=None, description="支付时间") expire_time: datetime = Field(..., description="过期时间") status: int = Field(default=0, description="订单状态") description: str | None = Field(default=None, description="备注") class OrderUpdateInternalSchema(BaseModel): """订单更新(内部 CRUD 用)""" status: int | None = Field(default=None, description="订单状态") pay_method: str | None = Field(default=None, description="支付方式") pay_time: datetime | None = Field(default=None, description="支付时间") description: str | None = Field(default=None, description="备注") class PaymentRecordCreateSchema(BaseModel): """支付记录创建""" order_id: int = Field(..., description="订单ID") transaction_id: str | None = Field(default=None, description="交易流水号") pay_method: str = Field(..., description="支付方式") amount: int = Field(..., description="支付金额(分)") status: int = Field(default=1, description="支付状态") raw_response: str | None = Field(default=None, description="原始响应") pay_time: datetime | None = Field(default=None, description="支付时间") description: str | None = Field(default=None, description="备注") class RefundCreateSchema(BaseModel): """退款记录创建""" order_id: int = Field(..., description="订单ID") refund_no: str = Field(..., description="退款单号") amount: int = Field(..., description="退款金额(分)") reason: str = Field(..., description="退款原因") refund_transaction_id: str | None = Field(default=None, description="退款交易流水号") reviewer_id: int | None = Field(default=None, description="审核人ID") review_time: datetime | None = Field(default=None, description="审核时间") reject_reason: str | None = Field(default=None, description="驳回原因") status: int = Field(default=1, description="退款状态") description: str | None = Field(default=None, description="备注") class RefundUpdateSchema(BaseModel): """退款记录更新""" status: int | None = Field(default=None, description="退款状态") reviewer_id: int | None = Field(default=None, description="审核人ID") review_time: datetime | None = Field(default=None, description="审核时间") reject_reason: str | None = Field(default=None, description="驳回原因") refund_transaction_id: str | None = Field(default=None, description="退款交易流水号") description: str | None = Field(default=None, description="备注") class OrderCreateSchema(BaseModel): """创建订单(套餐或插件)""" tenant_id: int = Field(..., ge=1, description="租户ID") package_id: int | None = Field(default=None, ge=1, description="套餐ID(套餐订单必填)") plugin_id: int | None = Field(default=None, ge=1, description="插件ID(插件订单必填)") order_type: Literal["new", "renew", "upgrade", "downgrade", "plugin"] = Field( ..., description="订单类型(new:新购 renew:续费 upgrade:升级 downgrade:降级 plugin:插件)", ) pay_method: Literal["alipay", "wxpay", "free"] | None = Field(default=None, description="支付方式(留空=自动)") @field_validator("tenant_id") @classmethod def positive(cls, v: int) -> int: if v <= 0: raise ValueError("必须为正整数") return v @model_validator(mode="after") def check_target(self) -> None: if self.order_type == "plugin": if not self.plugin_id or self.plugin_id <= 0: raise ValueError("插件订单必须指定 plugin_id") else: if not self.package_id or self.package_id <= 0: raise ValueError("套餐订单必须指定 package_id") class OrderOutSchema(BaseSchema, TenantBySchema): """订单输出""" model_config = ConfigDict(from_attributes=True) order_no: str = Field(..., description="订单号") package_id: int | None = Field(default=None, description="套餐ID") plugin_id: int | None = Field(default=None, description="插件ID") order_type: str = Field(..., description="订单类型") amount: int = Field(..., description="订单金额(分)") period_count: int = Field(..., description="时长(月)") pay_method: str | None = Field(default=None, description="支付方式") pay_time: datetime | None = Field(default=None, description="支付时间") expire_time: datetime = Field(..., description="过期时间") status: int = Field(..., description="订单状态(0:待支付 1:已支付 2:已取消 3:已退款)") description: str | None = Field(default=None, description="备注") @dataclass class OrderQueryParam(BaseQueryParam): """订单查询参数""" tenant_id: int | None = Query(None, description="租户ID") status: int | None = Query(None, description="订单状态(0:待支付 1:已支付 2:已取消 3:已退款)") order_type: str | None = Query(None, description="订单类型") order_no: str | None = Query(None, description="订单号") def __post_init__(self) -> None: if self.tenant_id is not None: self.tenant_id = (QueueEnum.eq.value, self.tenant_id) if self.status is not None: self.status = (QueueEnum.eq.value, self.status) if self.order_type: self.order_type = (QueueEnum.eq.value, self.order_type) if self.order_no: self.order_no = (QueueEnum.like.value, self.order_no) class PaymentCallbackSchema(BaseModel): """支付回调数据""" transaction_id: str | None = Field(default=None, description="交易流水号") amount: int = Field(..., description="支付金额(分)") order_id: int | None = Field(default=None, description="订单ID") raw_data: dict | None = Field(default=None, description="原始数据") class PaymentRecordOutSchema(BaseSchema, TenantBySchema): """支付记录输出""" model_config = ConfigDict(from_attributes=True) order_id: int = Field(..., description="订单ID") transaction_id: str | None = Field(default=None, description="交易流水号") pay_method: str = Field(..., description="支付方式") amount: int = Field(..., description="支付金额(分)") pay_time: datetime | None = Field(default=None, description="支付时间") status: int = Field(..., description="支付状态") description: str | None = Field(default=None, description="备注") class PaymentCreateOut(BaseModel): """创建支付结果""" pay_url: str = Field(..., description="支付链接") qr_code_url: str = Field(..., description="二维码链接") trade_no: str = Field(..., description="交易流水号") order_id: int = Field(..., description="订单ID") order_no: str = Field(..., description="订单号") amount: int = Field(..., description="支付金额(分)") class PaymentStatusOut(BaseModel): """支付状态查询结果""" exists: bool = Field(..., description="是否存在") order_id: int | None = Field(default=None, description="订单ID") status: int | None = Field(default=None, description="支付状态") paid: bool = Field(default=False, description="是否已支付") pay_method: str | None = Field(default=None, description="支付方式") pay_time: str | None = Field(default=None, description="支付时间") class OrderStatusMessage(BaseModel): """订单/退款操作结果消息""" id: int = Field(..., description="订单/退款ID") status: int = Field(..., description="状态") message: str = Field(..., description="消息") class RefundApplySchema(BaseModel): """退款申请""" reason: str = Field(..., min_length=1, max_length=500, description="退款原因") class RefundReviewSchema(BaseModel): """退款审核""" reject_reason: str | None = Field(default=None, max_length=500, description="驳回原因(审核通过时可不填)") class RefundOutSchema(BaseSchema, TenantBySchema): """退款记录输出""" model_config = ConfigDict(from_attributes=True) order_id: int = Field(..., description="订单ID") refund_no: str = Field(..., description="退款单号") amount: int = Field(..., description="退款金额(分)") reason: str = Field(..., description="退款原因") refund_transaction_id: str | None = Field(default=None, description="退款交易流水号") reviewer_id: int | None = Field(default=None, description="审核人ID") review_time: datetime | None = Field(default=None, description="审核时间") reject_reason: str | None = Field(default=None, description="驳回原因") status: int = Field(..., description="退款状态") description: str | None = Field(default=None, description="备注")