feat(pay): 微信服务商分账+资金托管改造——OPC个人/商户双绑定收款、验收95/5分账、回调确认、escrow资金托管字段扩展

- profitsharing.py:服务商分账客户端(添加接收方/发起分账/查分账/退款/回调验签)
- payment_bindings + settlement_logs:OPC 收款绑定(personal openid / merchant 子商户号)与分账流水镜像
- settlement_service.release_escrow:验收后优先发起微信分账(95%接单者/5%平台),未配置时降级台账结算(channel=manual)
- handle_split_notify:分账回调确认 escrow released(幂等)
- 路由:分账回调 + 绑定创建/列表/停用
- 迁移:alembic 0039(新表 + escrows 幂等加列)+ 兜底迁移脚本 + 冒烟测试
This commit is contained in:
Pine
2026-09-01 18:39:41 +08:00
parent 496c006ba0
commit 0fd210ac1f
12 changed files with 992 additions and 7 deletions
+60
View File
@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
"""幂等迁移:为 ``escrows`` 表补充微信服务商分账扩展列(非运行态)。
用法:uv run python scripts/db/migrate_escrow_columns.py
- 通过 SQLAlchemy Inspector 检查缺失列,对缺失列执行 ``ALTER TABLE ... ADD COLUMN``
- 幂等:已存在列自动跳过,可重复执行;兼容 SQLite(默认)与 MySQL(生产)。
- 新增表 ``payment_bindings`` / ``settlement_logs`` 由应用启动时 ``create_all`` 幂等创建,
无需在此处理(本脚本只负责对既有表加列)。
"""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent.parent
sys.path.insert(0, str(ROOT))
from sqlalchemy import create_engine, inspect # noqa: E402
from app.infrastructure.db import sync_database_url # noqa: E402
# escrows 需补充的扩展列(列名, 方言通用 DDL)
ESCROW_COLUMNS = [
("channel", "VARCHAR(32) NOT NULL DEFAULT 'wx_split'"),
("transaction_id", "VARCHAR(64) NOT NULL DEFAULT ''"),
("share_order_no", "VARCHAR(64) NOT NULL DEFAULT ''"),
("share_status", "VARCHAR(32) NOT NULL DEFAULT 'pending'"),
("receiver_binding_id", "VARCHAR(64) NOT NULL DEFAULT ''"),
]
def main() -> None:
url = sync_database_url()
engine = create_engine(url)
insp = inspect(engine)
if "escrows" not in insp.get_table_names():
print("escrows 表不存在(未建库),无需迁移")
return
existing = {c["name"] for c in insp.get_columns("escrows")}
added, skipped = [], []
for name, ddl in ESCROW_COLUMNS:
if name in existing:
skipped.append(name)
continue
with engine.begin() as conn:
conn.exec_driver_sql(f"ALTER TABLE escrows ADD COLUMN {name} {ddl}")
added.append(name)
print(f"新增列: {added or ''}")
print(f"已存在跳过: {skipped or ''}")
engine.dispose()
if added:
print("迁移完成。")
else:
print("无新增列,escrows 已是最新。")
print("注:新表 payment_bindings / settlement_logs 由应用启动时 create_all 幂等创建。")
if __name__ == "__main__":
main()