Files
gaokao-volunteer-system/data/orders/public_flow.py
Hermes Agent c1e1d3e82d
Some checks failed
CI / pytest (Python 3.10) (push) Has been cancelled
CI / pytest (Python 3.11) (push) Has been cancelled
CI / pytest (Python 3.12) (push) Has been cancelled
fix(public): T2-03 add order idempotency and rate limit
2026-07-05 22:35:13 +08:00

120 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""用户端 Web 自助订单创建流程T12.3)。"""
from __future__ import annotations
from typing import Literal, Optional
from pydantic import BaseModel, Field, model_validator
from data.orders.dao import OrdersDAO
from data.orders.models import Order, generate_order_id
ServiceVersion = Literal["audit", "basic", "standard", "premium"]
_SERVICE_PRICES: dict[ServiceVersion, int] = {
"audit": 4900,
"basic": 4900,
"standard": 9900,
"premium": 19900,
}
_SUPPORTED_PUBLIC_PROVINCES = {
"北京",
"天津",
"上海",
"重庆",
"河北",
"河南",
"山东",
"山西",
"陕西",
"辽宁",
"吉林",
"黑龙江",
"江苏",
"浙江",
"安徽",
"福建",
"江西",
"湖北",
"湖南",
"广东",
"海南",
"四川",
"贵州",
"云南",
"甘肃",
"青海",
"新疆",
}
def service_price_for(service_version: ServiceVersion) -> int:
return _SERVICE_PRICES[service_version]
class PublicOrderCreate(BaseModel):
service_version: ServiceVersion
amount_cents: int = Field(ge=0)
customer_name: Optional[str] = None
customer_phone: Optional[str] = Field(default=None, min_length=1)
customer_wechat: Optional[str] = None
customer_email: Optional[str] = None
idempotency_key: Optional[str] = Field(default=None, min_length=1, max_length=128)
candidate_name: str = Field(min_length=1)
candidate_province: Optional[str] = None
notes: Optional[str] = None
@model_validator(mode="after")
def _validate_minimal_checkout(self) -> "PublicOrderCreate":
expected_amount = service_price_for(self.service_version)
if self.amount_cents != expected_amount:
raise ValueError("amount_cents 与套餐价格不一致")
if not self.customer_phone and not self.customer_wechat:
raise ValueError("customer_phone / customer_wechat 至少填写一个")
if self.candidate_province and self.candidate_province not in _SUPPORTED_PUBLIC_PROVINCES:
raise ValueError("candidate_province 当前暂不支持")
return self
class PublicOrderCreated(BaseModel):
order_id: str
source: str
status: str
service_version: str
amount_cents: int
next_step: str
def create_public_order(dao: OrdersDAO, request: PublicOrderCreate) -> Order:
order = Order(
id=generate_order_id(),
source="web",
external_id=f"idempotency:{request.idempotency_key}" if request.idempotency_key else None,
service_version=request.service_version,
amount_cents=service_price_for(request.service_version),
status="pending",
customer_name=request.customer_name,
customer_phone=request.customer_phone,
customer_wechat=request.customer_wechat,
customer_email=request.customer_email,
candidate_name=request.candidate_name,
candidate_province=request.candidate_province,
notes=request.notes,
tags=["web-self-service", f"package:{request.service_version}"],
)
return dao.create(order, actor="public_web", reason="public_create")
def to_created_payload(order: Order) -> PublicOrderCreated:
return PublicOrderCreated(
order_id=order.id,
source=order.source,
status=order.status,
service_version=order.service_version,
amount_cents=order.amount_cents,
next_step="payment",
)