Files
gaokao-volunteer-system/data/payments/models.py
Hermes 2893a45d8b
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
feat(6/19): 保留期门禁 + 支付失败持久化 + 文档校准
A1 删除/匿名化 180 天保留期门禁 (P0):
- admin/errors/codes.py + BIZ_ORDER_RETENTION_NOT_EXPIRED (E02002) -> HTTP 409
- admin/config.py + retention_days / GAOKAO_RETENTION_DAYS (默认 180)
- admin/routes/orders.py + _assert_retention_expired 守卫
- data/orders/deletion_service.py + RETENTION_GUARDED_STATUSES
- admin/tests/test_order_deletion.py + _expire_retention_window + 3 测试

B1 支付失败 webhook 持久化 (P1):
- data/payments/dao.py + failed_at/failure_reason/provider_trade_no/callback_payload
- data/payments/models.py + PaymentRecord 新字段
- data/payments/service.py handle_webhook 失败分支持久化
- data/payments/tests/test_webhook.py test_handle_webhook_persists_failed_status

A2 文档校准 (P1):
- README / PRD / ROADMAP / TECH_ARCHITECTURE 顶部对齐 6/19

B2 真相源分层 (P1):
- 6/13 整改板/执行板加历史快照前缀
- 新建 6/19 整改板/执行板
- CURRENT_STATE 顶部加 6/19 增量段 (在 6/13 真相源之上叠加)

dev-verify: 1175 passed (A1+B1+A2+B2) -> 1179 passed (+ T12-C),
coverage overall=85.05% / core=100%
2026-06-20 00:27:15 +08:00

56 lines
1.3 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from typing import Optional
from data.orders.models import utc_now_iso
@dataclass
class PaymentRecord:
id: str
order_id: str
provider: str
amount_cents: int
currency: str = "CNY"
status: str = "pending"
provider_trade_no: Optional[str] = None
checkout_token: Optional[str] = None # payment-bound return key, never portal token
callback_payload: Optional[str] = None
refund_reason: Optional[str] = None
failure_reason: Optional[str] = None # 6/19: 失败 webhook 持久化原因
created_at: str = ""
updated_at: str = ""
paid_at: Optional[str] = None
refunded_at: Optional[str] = None
failed_at: Optional[str] = None # 6/19: 失败 webhook 时间戳
def __post_init__(self) -> None:
now = utc_now_iso()
if not self.created_at:
self.created_at = now
if not self.updated_at:
self.updated_at = self.created_at
@dataclass
class PaymentCheckout:
payment_id: str
provider: str
checkout_url: str
status: str
@dataclass
class WebhookHandleResult:
payment_id: str
processed: bool
idempotent: bool
order_status: str
@dataclass
class RefundRequestResult:
payment_id: str
status: str