# -*- 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), # 以及特约商户进件(applyment4sub)的发起方。在服务商商户平台 pay.weixin.qq.com/partner 查看。 WECHATPAY_SP_MCHID = os.environ.get("PINEAGENTS_WX_SP_MCHID", "") # 服务商 AppID(小程序/公众号):PERSONAL_OPENID 接收方的 openid 归属该 appid; # 在微信公众平台 mp.weixin.qq.com 查看,需与服务商商户号完成绑定授权。 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") ) # ── 服务商独立 API 证书(若服务商商户号 ≠ 直连商户号则必须独立配置,否则签名失败)── # 服务商商户号是独立商户号时,其 API 证书序列号 / 私钥 / APIv3 密钥 / 平台证书均与直连商户不同, # 必须从服务商商户平台【账户中心 > API 安全】下载并单独配置;未配置时回退使用直连商户证书(仅适用于 # 服务商与直连为同一商户号的场景)。 WECHATPAY_SP_PRIVATE_KEY_PATH = os.environ.get( "PINEAGENTS_WX_SP_PRIVATE_KEY_PATH", WECHATPAY_PRIVATE_KEY_PATH, ) WECHATPAY_SP_CERT_SERIAL_NO = os.environ.get( "PINEAGENTS_WX_SP_CERT_SERIAL_NO", WECHATPAY_CERT_SERIAL_NO, ) WECHATPAY_SP_APIV3_KEY = os.environ.get( "PINEAGENTS_WX_SP_APIV3_KEY", WECHATPAY_APIV3_KEY, ) WECHATPAY_SP_CERT_DIR = os.environ.get( "PINEAGENTS_WX_SP_CERT_DIR", WECHATPAY_CERT_DIR, ) WECHATPAY_SP_PUBLIC_KEY_PATH = os.environ.get( "PINEAGENTS_WX_SP_PUBLIC_KEY_PATH", WECHATPAY_PUBLIC_KEY_PATH, ) WECHATPAY_SP_PUBLIC_KEY_ID = os.environ.get( "PINEAGENTS_WX_SP_PUBLIC_KEY_ID", WECHATPAY_PUBLIC_KEY_ID, ) def profitsharing_enabled() -> bool: """服务商分账能力是否可用(sp_mchid + 服务商证书/密钥 + 回调地址齐备)。""" return bool( WECHATPAY_SP_MCHID and WECHATPAY_SP_CERT_SERIAL_NO and WECHATPAY_SP_APIV3_KEY and WECHATPAY_SP_PRIVATE_KEY_PATH and WECHATPAY_SPLIT_NOTIFY_URL )