feat(wallet): 钱包系统替代配额系统,新增支付与退款机制

- 新增钱包余额管理、充值、扣费、退款完整流程
- 新增支付网关抽象层(支持手动/支付宝/微信)
- 用量计费从配额系统迁移到钱包余额扣费
- 新增管理员钱包管理与支付订单管理页面
- 新增用户钱包中心页面
- 移除独立 Key 锁定机制,统一由钱包余额控制
- 新增相关 API 路由、序列化器与数据库迁移
- 新增钱包、支付、退款相关测试
This commit is contained in:
LewisPen
2026-03-08 00:05:48 +08:00
committed by fawney19
parent 9cdcce1b5f
commit 783f654953
108 changed files with 13152 additions and 3372 deletions

View File

@@ -1304,6 +1304,9 @@ async def test_record_usage_batch_updates_when_status_completed_billing_pending(
def filter(self, *args: Any, **kwargs: Any) -> "DummyQuery":
return self
def with_for_update(self) -> "DummyQuery":
return self
def all(self) -> list[Any]:
return self._all_result
@@ -1362,10 +1365,10 @@ async def test_record_usage_batch_updates_when_status_completed_billing_pending(
@pytest.mark.asyncio
async def test_record_usage_batch_uses_bulk_insert_mappings_for_new_records(
async def test_record_usage_batch_uses_orm_insert_for_new_records(
monkeypatch: Any,
) -> None:
"""确保批量新建 Usage 走 bulk_insert_mappings"""
"""确保批量新建 Usage 走 ORM add 路径,以便复用统一结算逻辑"""
from src.models.database import Usage
from src.services.usage.service import UsageService
@@ -1380,27 +1383,18 @@ async def test_record_usage_batch_uses_bulk_insert_mappings_for_new_records(
def filter(self, *args: Any, **kwargs: Any) -> "DummyQuery":
return self
def with_for_update(self) -> "DummyQuery":
return self
def all(self) -> list[Any]:
return self._all_result
inserted = Usage(
request_id="req-usage-batch-new",
provider_name="openai",
model="gpt-4",
status="completed",
billing_status="settled",
)
usage_query_calls = {"count": 0}
def _query_side_effect(model: Any) -> Any:
if model is Usage:
usage_query_calls["count"] += 1
if usage_query_calls["count"] == 1:
# existing_records
return DummyQuery([])
# inserted_records
return DummyQuery([inserted])
return DummyQuery([])
return DummyQuery([])
db = MagicMock()
@@ -1431,13 +1425,10 @@ async def test_record_usage_batch_uses_bulk_insert_mappings_for_new_records(
],
)
db.bulk_insert_mappings.assert_called_once()
args, _kwargs = db.bulk_insert_mappings.call_args
assert args[0] is Usage
mappings = args[1]
assert isinstance(mappings, list) and len(mappings) == 1
assert mappings[0]["request_id"] == "req-usage-batch-new"
assert mappings[0]["billing_status"] == "settled"
assert mappings[0].get("finalized_at") is not None
db.add.assert_called_once()
db.bulk_insert_mappings.assert_not_called()
assert result and result[0] is inserted
assert result and isinstance(result[0], Usage)
assert result[0].request_id == "req-usage-batch-new"
assert result[0].billing_status == "settled"
assert result[0].finalized_at is not None