Files
server-core/app/pay/config.py
T
Pine 0fd210ac1f 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 幂等加列)+ 兜底迁移脚本 + 冒烟测试
2026-09-01 18:39:41 +08:00

92 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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 必须是小程序 appidwechatpayv3 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
)