b59dc3ac19
提现从「每次跳微信确认」升级为免确认模式:首次授权一次,之后审核通过直接到账,用户零跳转。 方式一(首单转账顺带授权 pre-transfer-with-authorization)+ 方式二(显式开启 user-confirm-authorization)。 - 新增 wechat_transfer_authorization 表(user 1:1,out_authorization_no/authorization_id/state) + 迁移 - wxpay.py 新增 apply/query/close_transfer_authorization + pre_transfer_with_authorization + transfer_with_authorization - repositories/wallet.py 授权 CRUD + sync/_refresh_active_auth + execute_withdraw_transfer 三分叉 (active->免确认转账 / 无授权且配了回调->方式一 / 未配->退化原确认模式);失效回查授权权威判定,绝不盲退 - /wallet/transfer-auth(开启)/status/close 路由 + withdraw-info 增 transfer_auth_enabled - 授权结果回调 stub /api/v1/wxpay/transfer-auth-notify(一期仅应答不验签,真值靠 query 轮询) - config 增 WXPAY_AUTH_NOTIFY_URL 一期无回调验签;二期补 V3 平台证书验签 + AEAD 解密。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Reviewed-on: #16
49 lines
2.1 KiB
Python
49 lines
2.1 KiB
Python
"""wechat_transfer_authorization 表(免确认收款授权)
|
|
|
|
商家转账「用户授权免确认收款模式」:用户授权一次后,后续提现免逐笔确认直接到账。
|
|
一个用户一条(user_id 主键)。out_authorization_no 我方生成,authorization_id 微信 active 后返回。
|
|
|
|
Revision ID: wx_transfer_auth
|
|
Revises: withdraw_review_ad_watch
|
|
Create Date: 2026-06-06 12:00:00.000000
|
|
|
|
"""
|
|
from typing import Sequence, Union
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
|
|
# revision identifiers, used by Alembic.
|
|
revision: str = 'wx_transfer_auth'
|
|
down_revision: Union[str, Sequence[str], None] = 'withdraw_review_ad_watch'
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.create_table(
|
|
'wechat_transfer_authorization',
|
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
|
sa.Column('openid', sa.String(length=64), nullable=False),
|
|
sa.Column('out_authorization_no', sa.String(length=64), nullable=False),
|
|
sa.Column('authorization_id', sa.String(length=64), nullable=True),
|
|
sa.Column('state', sa.String(length=16), server_default='pending', nullable=False),
|
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
|
|
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
|
|
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
|
sa.PrimaryKeyConstraint('user_id'),
|
|
)
|
|
with op.batch_alter_table('wechat_transfer_authorization', schema=None) as batch_op:
|
|
batch_op.create_index(
|
|
batch_op.f('ix_wechat_transfer_authorization_out_authorization_no'),
|
|
['out_authorization_no'],
|
|
unique=True,
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
with op.batch_alter_table('wechat_transfer_authorization', schema=None) as batch_op:
|
|
batch_op.drop_index(batch_op.f('ix_wechat_transfer_authorization_out_authorization_no'))
|
|
op.drop_table('wechat_transfer_authorization')
|