0fd210ac1f
- 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 幂等加列)+ 兜底迁移脚本 + 冒烟测试
92 lines
4.6 KiB
Python
92 lines
4.6 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""支付子应用配置(微信支付 V3 / 算力充值)。
|
||
|
||
独立成模块便于后期整体拆分为支付微服务;凭据只放服务器 .env,勿提交。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import os
|
||
|
||
from .. import config
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 微信支付 V3 商户配置(未配置时支付功能整体降级不可用,接口返回 enabled=False)
|
||
# ---------------------------------------------------------------------------
|
||
WECHATPAY_MCHID = os.environ.get("PINEAGENTS_WECHATPAY_MCHID", "")
|
||
WECHATPAY_CERT_SERIAL_NO = os.environ.get("PINEAGENTS_WECHATPAY_CERT_SERIAL_NO", "")
|
||
WECHATPAY_APIV3_KEY = os.environ.get("PINEAGENTS_WECHATPAY_APIV3_KEY", "")
|
||
WECHATPAY_PRIVATE_KEY_PATH = os.environ.get(
|
||
"PINEAGENTS_WECHATPAY_PRIVATE_KEY_PATH", str(config.KEYS_DIR / "wechatpay" / "apiclient_key.pem"),
|
||
)
|
||
# 平台证书目录(微信支付平台证书/公钥自动更新落盘,属运行时数据)
|
||
WECHATPAY_CERT_DIR = os.environ.get(
|
||
"PINEAGENTS_WECHATPAY_CERT_DIR", str(config.KEYS_DIR / "wechatpay" / "certs"),
|
||
)
|
||
# 回调通知地址:须为微信可达的公网 HTTPS(如 https://opc.pinesound.cn/opc/pay/notify)
|
||
WECHATPAY_NOTIFY_URL = os.environ.get("PINEAGENTS_WECHATPAY_NOTIFY_URL", "")
|
||
# 微信支付平台公钥(可选:新商户用公钥模式时配置;否则走平台证书自动更新)
|
||
WECHATPAY_PUBLIC_KEY_PATH = os.environ.get("PINEAGENTS_WECHATPAY_PUBLIC_KEY_PATH", "")
|
||
# 平台公钥 ID(公钥模式时与上面成对配置,如 PUB_KEY_ID_0111...)
|
||
WECHATPAY_PUBLIC_KEY_ID = os.environ.get("PINEAGENTS_WECHATPAY_PUBLIC_KEY_ID", "")
|
||
|
||
# 双 appid:Native 扫码用「绑定到商户号的公众号/开放平台 appid」;
|
||
# JSAPI(小程序) 下单 appid 必须是小程序 appid(wechatpayv3 pay() 支持按单覆盖)。
|
||
WECHATPAY_NATIVE_APPID = (
|
||
os.environ.get("PINEAGENTS_WECHATPAY_NATIVE_APPID", "") or config.WECHAT_OPEN_APPID
|
||
)
|
||
WECHATPAY_JSAPI_APPID = (
|
||
os.environ.get("PINEAGENTS_WECHATPAY_JSAPI_APPID", "") or config.WECHAT_APPID
|
||
)
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 充值规则
|
||
# ---------------------------------------------------------------------------
|
||
# 人民币实际计费口径:1 元 = 1,000,000 微元(与引擎 meter 的 QUOTA_PER_YUAN 一致)
|
||
QUOTA_PER_YUAN = 1_000_000
|
||
|
||
RECHARGE_MIN_YUAN = float(os.environ.get("PINEAGENTS_RECHARGE_MIN_YUAN", "1"))
|
||
RECHARGE_MAX_YUAN = float(os.environ.get("PINEAGENTS_RECHARGE_MAX_YUAN", "5000"))
|
||
RECHARGE_EXPIRE_MINUTES = int(os.environ.get("PINEAGENTS_RECHARGE_EXPIRE_MINUTES", "15"))
|
||
|
||
# 充值套餐兜底(JSON 数组字符串):DB system_configs 的 recharge_packages 优先
|
||
DEFAULT_PACKAGES_JSON = os.environ.get(
|
||
"PINEAGENTS_WECHATPAY_PACKAGES",
|
||
json.dumps([
|
||
{"id": "p10", "amount": 10, "bonus": 0, "label": "10 元"},
|
||
{"id": "p50", "amount": 50, "bonus": 5, "label": "50 元"},
|
||
{"id": "p100", "amount": 100, "bonus": 15, "label": "100 元"},
|
||
], ensure_ascii=False),
|
||
)
|
||
|
||
|
||
def pay_enabled() -> bool:
|
||
"""商户关键凭据齐备才启用支付。"""
|
||
return bool(
|
||
WECHATPAY_MCHID and WECHATPAY_CERT_SERIAL_NO and WECHATPAY_APIV3_KEY
|
||
and WECHATPAY_PRIVATE_KEY_PATH and WECHATPAY_NOTIFY_URL
|
||
)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 微信支付服务商(分账 / 资金托管)配置 —— 与直连商户配置并存,分账以其为开关
|
||
# ---------------------------------------------------------------------------
|
||
# 服务商商户号(10 位):分账调用 /v3/profitsharing/orders 的请求主体(sp_mchid)
|
||
WECHATPAY_SP_MCHID = os.environ.get("PINEAGENTS_WX_SP_MCHID", "")
|
||
# 服务商 AppID(小程序/公众号):PERSONAL_OPENID 接收方的 openid 归属该 appid
|
||
WECHATPAY_SP_APPID = os.environ.get("PINEAGENTS_WX_SP_APPID", "")
|
||
# 分账结果回调通知地址(公网 HTTPS,如 https://opc.pinesound.cn/opc/pay/profitsharing/notify)
|
||
WECHATPAY_SPLIT_NOTIFY_URL = os.environ.get("PINEAGENTS_WX_SPLIT_NOTIFY_URL", "")
|
||
# 平台佣金比例:分账给服务商商户号的部分(默认 5%,与 settlement COMMISSION_RATE 一致)
|
||
WECHATPAY_SPLIT_COMMISSION_RATE = float(
|
||
os.environ.get("PINEAGENTS_WX_SPLIT_COMMISSION_RATE", "0.05")
|
||
)
|
||
|
||
|
||
def profitsharing_enabled() -> bool:
|
||
"""服务商分账能力是否可用(sp_mchid + 证书/密钥 + 回调地址齐备)。"""
|
||
return bool(
|
||
WECHATPAY_SP_MCHID and WECHATPAY_CERT_SERIAL_NO and WECHATPAY_APIV3_KEY
|
||
and WECHATPAY_PRIVATE_KEY_PATH and WECHATPAY_SPLIT_NOTIFY_URL
|
||
)
|