Files
life-echo/api/payment/config.py
iammm0 44b405d647 refactor: 优化后端支付与微信支付
- 优化payment/config.py、wechat_pay.py
- 优化routers/payment.py、plans.py、quota.py、websocket.py
- 更新main.py、.env.production

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 16:06:15 +08:00

118 lines
5.7 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.
"""
支付模块配置
从环境变量读取微信支付和支付宝的商户密钥等配置
"""
import os
import logging
from dataclasses import dataclass, field
from typing import Optional
logger = logging.getLogger(__name__)
@dataclass
class WeChatPayConfig:
"""微信支付配置"""
app_id: str = "" # 应用 APPID
mch_id: str = "" # 商户号
api_v3_key: str = "" # APIv3 密钥
private_key_path: str = "" # 商户 API 私钥文件路径(与 private_key 二选一)
private_key: str = "" # 商户 API 私钥内容PEM 字符串,可放环境变量,与 private_key_path 二选一)
cert_serial_no: str = "" # 商户证书序列号
notify_url: str = "" # 支付结果回调地址
# 平台公钥模式(二选一):当环境无法访问 api.mch.weixin.qq.com 时使用,无需拉取平台证书
platform_public_key: str = "" # 微信支付平台公钥 PEM 内容(与 platform_public_key_path 二选一)
platform_public_key_path: str = "" # 平台公钥文件路径
platform_public_key_id: str = "" # 微信支付平台公钥 ID与 platform_public_key 同时配置)
@property
def is_configured(self) -> bool:
has_key = bool(self.private_key.strip()) or bool(self.private_key_path.strip())
return all([
self.app_id, self.mch_id, self.api_v3_key,
has_key, self.cert_serial_no, self.notify_url
])
@property
def use_platform_public_key(self) -> bool:
"""是否使用平台公钥模式(无需访问微信拉取证书)"""
has_pub = bool(self.platform_public_key.strip()) or bool(self.platform_public_key_path.strip())
return has_pub and bool(self.platform_public_key_id.strip())
@dataclass
class AlipayConfig:
"""支付宝配置"""
app_id: str = "" # 应用 APPID
private_key: str = "" # 应用私钥(字符串内容)
alipay_public_key: str = "" # 支付宝公钥(字符串内容)
notify_url: str = "" # 支付结果回调地址
sign_type: str = "RSA2" # 签名类型,默认 RSA2
@property
def is_configured(self) -> bool:
return all([
self.app_id, self.private_key,
self.alipay_public_key, self.notify_url
])
@dataclass
class PaymentConfig:
"""支付模块总配置"""
wechat: WeChatPayConfig = field(default_factory=WeChatPayConfig)
alipay: AlipayConfig = field(default_factory=AlipayConfig)
# 支付宝是否视为「开发中」不可用(默认 True上线时设 ALIPAY_UNDER_DEVELOPMENT=false
alipay_under_development: bool = True
@classmethod
def from_env(cls) -> "PaymentConfig":
"""从环境变量加载配置"""
# 微信私钥:优先使用 WECHAT_PAY_PRIVATE_KEY_PATH文件避免 .env 中长 PEM 的转义问题;否则用 WECHAT_PAY_PRIVATE_KEY
wechat_private_key_path = os.getenv("WECHAT_PAY_PRIVATE_KEY_PATH", "").strip()
wechat_private_key = os.getenv("WECHAT_PAY_PRIVATE_KEY", "").strip()
# 去除可能被解析进来的引号与 BOM
if wechat_private_key:
wechat_private_key = wechat_private_key.strip('"').strip("'").lstrip("\ufeff")
wechat_private_key = wechat_private_key.replace("\\n", "\n")
wechat_platform_pub = os.getenv("WECHAT_PAY_PLATFORM_PUBLIC_KEY", "").strip()
if wechat_platform_pub and "\\n" in wechat_platform_pub:
wechat_platform_pub = wechat_platform_pub.replace("\\n", "\n")
config = cls(
wechat=WeChatPayConfig(
app_id=os.getenv("WECHAT_PAY_APP_ID", ""),
mch_id=os.getenv("WECHAT_PAY_MCH_ID", ""),
api_v3_key=os.getenv("WECHAT_PAY_API_V3_KEY", ""),
private_key_path=wechat_private_key_path,
private_key=wechat_private_key if not wechat_private_key_path else "", # 有路径时不再用 env 内容
cert_serial_no=os.getenv("WECHAT_PAY_CERT_SERIAL_NO", ""),
notify_url=os.getenv("WECHAT_PAY_NOTIFY_URL", ""),
platform_public_key=wechat_platform_pub,
platform_public_key_path=os.getenv("WECHAT_PAY_PLATFORM_PUBLIC_KEY_PATH", "").strip(),
platform_public_key_id=os.getenv("WECHAT_PAY_PLATFORM_PUBLIC_KEY_ID", "").strip(),
),
alipay=AlipayConfig(
app_id=os.getenv("ALIPAY_APP_ID", ""),
private_key=os.getenv("ALIPAY_PRIVATE_KEY", ""),
alipay_public_key=os.getenv("ALIPAY_PUBLIC_KEY", ""),
notify_url=os.getenv("ALIPAY_NOTIFY_URL", ""),
sign_type=os.getenv("ALIPAY_SIGN_TYPE", "RSA2"),
),
alipay_under_development=os.getenv("ALIPAY_UNDER_DEVELOPMENT", "true").lower() in ("true", "1", "yes"),
)
# 日志输出配置状态
if config.wechat.is_configured:
mode = "平台公钥模式" if config.wechat.use_platform_public_key else "平台证书模式"
key_src = "WECHAT_PAY_PRIVATE_KEY(环境变量)" if config.wechat.private_key.strip() else "WECHAT_PAY_PRIVATE_KEY_PATH(文件)"
logger.info(f"微信支付配置已加载: APP_ID={config.wechat.app_id}, MCH_ID={config.wechat.mch_id}, 模式={mode}, 商户私钥={key_src}")
else:
logger.warning("微信支付配置不完整,微信支付将不可用")
if config.alipay.is_configured:
logger.info(f"支付宝配置已加载: APP_ID={config.alipay.app_id}")
else:
logger.warning("支付宝配置不完整,支付宝支付将不可用")
return config