f1147dced3
- app/pay 独立支付子应用(config/wxpay/models/repository/service/routers),便于后期拆分微服务 - 充值订单表 0026_compute_recharge;套餐支持折扣(discount)与上架开关(enabled),DB system_configs 优先、env 兜底 - 统一小程序支付:桌面端出小程序码→扫码进小程序确认页按 openid 发起 JSAPI→回调幂等到账(引擎 adjust_user_quota+镜像回写) - 内嵌 wechatpayv3(含 async_),响应验签失败降级告警;新增 aiofiles 依赖 Co-Authored-By: Claude <noreply@anthropic.com>
69 lines
3.3 KiB
Python
69 lines
3.3 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
|
||
)
|