支付与收款商户号分离:支付改读 pay_* 独立配置与证书目录,收款侧不动。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-27 05:41:56 +08:00
parent 89fa08e5de
commit db91626e7f
12 changed files with 531 additions and 78 deletions

150
utils/pay_mch_config.py Normal file
View File

@@ -0,0 +1,150 @@
"""
支付专用商户配置(与提现收款 WechatPayMchConfig / wechat_mch_service 完全分离)。
读取优先级:
1) XcxSysPeizhi.pay_*
2) 过渡回退:旧 weixin_* / settings.WEIXIN_*
历史订单退款:若订单 pay_mch_id 与当前支付商户不一致,但等于旧 weixin_mchid则用旧 weixin_* 退款。
"""
import logging
import os
from django.conf import settings
from utils.xcx_sys_config import wx_cfg, _val
logger = logging.getLogger(__name__)
def pay_cert_base_dir():
base = getattr(settings, 'WECHAT_PAY_CERT_BASE_DIR', '') or ''
if base:
return base
return os.path.join(str(settings.BASE_DIR), 'zhifuzhengshu')
def _pay_val(pay_attr, legacy_db_attr, settings_attr, default=''):
"""pay_* 优先;空则回退旧 weixin_* / settings。"""
v = _val(pay_attr, None, None)
if v is not None and v != '':
return v
return _val(legacy_db_attr, settings_attr, default) or default
class PayWxConfig:
"""支付商户配置代理(属性名兼容旧 WEIXIN_*,便于替换调用点)。"""
@property
def WEIXIN_MCHID(self):
return _pay_val('pay_mch_id', 'weixin_mchid', 'WEIXIN_MCHID', '')
@property
def WEIXIN_SHANGHUMIYAO(self):
return _pay_val('pay_api_v2_key', 'weixin_shanghu_miyao', 'WEIXIN_SHANGHUMIYAO', '')
@property
def WEIXIN_NOTIFY_URL(self):
return _pay_val('pay_notify_url', 'weixin_notify_url', 'WEIXIN_NOTIFY_URL', '')
@property
def WEIXIN_CERT_PATH(self):
return _pay_val('pay_cert_path', 'weixin_cert_path', 'WEIXIN_CERT_PATH', '')
@property
def WEIXIN_KEY_PATH(self):
return _pay_val('pay_key_path', 'weixin_key_path', 'WEIXIN_KEY_PATH', '')
@property
def PAY_API_V3_KEY(self):
return _val('pay_api_v3_key', None, '') or ''
@property
def PAY_CERT_SERIAL_NO(self):
return _val('pay_cert_serial_no', None, '') or ''
# 别名,语义更清晰
@property
def PAY_MCH_ID(self):
return self.WEIXIN_MCHID
@property
def PAY_API_V2_KEY(self):
return self.WEIXIN_SHANGHUMIYAO
pay_cfg = PayWxConfig()
def get_current_pay_mch_id():
return (pay_cfg.PAY_MCH_ID or '').strip()
def pay_cfg_as_dict():
return {
'MCHID': pay_cfg.WEIXIN_MCHID,
'KEY': pay_cfg.WEIXIN_SHANGHUMIYAO,
'NOTIFY_URL': pay_cfg.WEIXIN_NOTIFY_URL,
'CERT_PATH': pay_cfg.WEIXIN_CERT_PATH,
'KEY_PATH': pay_cfg.WEIXIN_KEY_PATH,
'API_V3_KEY': pay_cfg.PAY_API_V3_KEY,
'CERT_SERIAL_NO': pay_cfg.PAY_CERT_SERIAL_NO,
}
def get_pay_wx_cfg(mch_id=None):
"""
按商户号取支付配置(用于退款反查)。
- mch_id 空 / 等于当前支付商户 → 当前 pay_cfg
- mch_id 等于旧 weixin_mchid 且与当前不同 → 用旧 weixin_*(换号后历史单退款)
- 否则若 zhifuzhengshu/{mch_id}/ 下有证书,拼路径但仍需密钥;密钥仅当前/旧库有,故仍回退当前并打日志
"""
current = pay_cfg_as_dict()
want = (mch_id or '').strip()
current_mch = (current.get('MCHID') or '').strip()
if not want or want == current_mch:
return current
legacy_mch = (wx_cfg.WEIXIN_MCHID or '').strip()
if want == legacy_mch and want != current_mch:
return {
'MCHID': wx_cfg.WEIXIN_MCHID,
'KEY': wx_cfg.WEIXIN_SHANGHUMIYAO,
'NOTIFY_URL': wx_cfg.WEIXIN_NOTIFY_URL,
'CERT_PATH': wx_cfg.WEIXIN_CERT_PATH,
'KEY_PATH': wx_cfg.WEIXIN_KEY_PATH,
'API_V3_KEY': '',
'CERT_SERIAL_NO': '',
}
cert_path = os.path.join(pay_cert_base_dir(), want, 'apiclient_cert.pem')
key_path = os.path.join(pay_cert_base_dir(), want, 'apiclient_key.pem')
if os.path.isfile(cert_path) and os.path.isfile(key_path):
logger.warning(
'支付商户 %s 与当前配置 %s 不一致,仅找到证书文件,密钥仍用当前配置,退款可能失败',
want, current_mch,
)
out = dict(current)
out['MCHID'] = want
out['CERT_PATH'] = cert_path
out['KEY_PATH'] = key_path
return out
logger.warning(
'订单支付商户 %s 无可用历史配置,回退当前支付商户 %s',
want, current_mch,
)
return current
def apply_pay_cfg_to_proxy(cfg_dict):
"""把 get_pay_wx_cfg 结果转成带 WEIXIN_* 属性的简单对象,兼容旧代码写法。"""
class _Cfg:
pass
o = _Cfg()
o.WEIXIN_MCHID = cfg_dict.get('MCHID') or ''
o.WEIXIN_SHANGHUMIYAO = cfg_dict.get('KEY') or ''
o.WEIXIN_NOTIFY_URL = cfg_dict.get('NOTIFY_URL') or ''
o.WEIXIN_CERT_PATH = cfg_dict.get('CERT_PATH') or ''
o.WEIXIN_KEY_PATH = cfg_dict.get('KEY_PATH') or ''
return o