From ca67dfa863cdb195f13793007b3968c689ef15f1 Mon Sep 17 00:00:00 2001 From: XingQue Date: Fri, 10 Jul 2026 22:23:31 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=98=9F=E9=98=99V3=E6=8F=90=E7=8E=B0?= =?UTF-8?q?=E5=BC=BA=E5=88=B6=E7=94=A8=E5=85=A8=E5=B1=80=E8=AF=81=E4=B9=A6?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=EF=BC=8C=E4=BF=AE=E5=A4=8D=E5=BA=8F=E5=88=97?= =?UTF-8?q?=E5=8F=B7=E4=B8=8E=E7=A7=81=E9=92=A5=E4=B8=8D=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit club表私钥路径/序列号错填会导致微信报证书序列号错误。 xq 提现改读 settings+写死序列号;build_authorization 双重兜底。 Co-authored-by: Cursor --- a_long_dianjing/settings.py | 3 +- jituan/management/commands/seed_club_xq.py | 4 +- jituan/services/wechat_pay.py | 51 +++++++++++++++++----- utils/wechat_v3.py | 5 ++- 4 files changed, 47 insertions(+), 16 deletions(-) diff --git a/a_long_dianjing/settings.py b/a_long_dianjing/settings.py index 8ae3595..a53318d 100644 --- a/a_long_dianjing/settings.py +++ b/a_long_dianjing/settings.py @@ -379,7 +379,8 @@ WECHAT_PAY_V3_CONFIG = { 'APPID': app_secrets.WECHAT_PAY_V3_APPID, 'API_V3_KEY': app_secrets.WECHAT_PAY_V3_API_V3_KEY, 'PRIVATE_KEY_PATH': app_secrets.WECHAT_PAY_V3_PRIVATE_KEY_PATH, - 'CERT_SERIAL_NO': app_secrets.WECHAT_PAY_V3_CERT_SERIAL_NO, + # 与服务器 apiclient_key.pem 对应;勿用 club 表错误值 + 'CERT_SERIAL_NO': '481784CF56E872D9D8122BAC64E6BC7390803582', 'PLATFORM_CERT_DIR': app_secrets.WECHAT_PAY_V3_PLATFORM_CERT_DIR, 'NOTIFY_URL': 'https://www.abas.asia/hqhd/yonghu/tixiancallback', 'TRANSFER_SCENE_ID': '1005', diff --git a/jituan/management/commands/seed_club_xq.py b/jituan/management/commands/seed_club_xq.py index d9544f0..8634038 100644 --- a/jituan/management/commands/seed_club_xq.py +++ b/jituan/management/commands/seed_club_xq.py @@ -6,7 +6,7 @@ from django.conf import settings from django.core.management.base import BaseCommand from django.db import transaction -from jituan.constants import CLUB_ID_DEFAULT +from jituan.services.wechat_pay import WX_V3_CERT_SERIAL_NO_OVERRIDE from jituan.models import ( Club, ClubHuiyuanPrice, @@ -45,7 +45,7 @@ class Command(BaseCommand): 'pay_app_id': wx_v3.get('APPID', '') or getattr(settings, 'WEIXIN_APPID', ''), 'mch_key': getattr(settings, 'WEIXIN_SHANGHUMIYAO', ''), 'api_v3_key': wx_v3.get('API_V3_KEY', ''), - 'cert_serial_no': wx_v3.get('CERT_SERIAL_NO', ''), + 'cert_serial_no': WX_V3_CERT_SERIAL_NO_OVERRIDE, 'private_key_path': wx_v3.get('PRIVATE_KEY_PATH', ''), 'platform_cert_dir': wx_v3.get('PLATFORM_CERT_DIR', ''), 'config_json': { diff --git a/jituan/services/wechat_pay.py b/jituan/services/wechat_pay.py index 20b1e13..8c97ba3 100644 --- a/jituan/services/wechat_pay.py +++ b/jituan/services/wechat_pay.py @@ -1,6 +1,7 @@ """各俱乐部微信支付 V2(MD5)配置与验签。""" import hashlib import logging +import os from django.conf import settings @@ -209,10 +210,26 @@ def _apply_v3_cert_serial_override(cfg): return cfg +def _log_v3_config(cfg): + key_path = (cfg.get('PRIVATE_KEY_PATH') or '').strip() + logger.warning( + '[WX_V3_CONFIG] club=%s appid=%s mch=%s serial=%s key_path=%s key_exists=%s', + cfg.get('club_id'), + cfg.get('APPID'), + cfg.get('MCHID'), + cfg.get('CERT_SERIAL_NO'), + key_path, + bool(key_path and os.path.isfile(key_path)), + ) + + def get_wechat_v3_config(club_id=None): """ 微信 V3(转账/查单)配置。 - 各俱乐部(含 xq)优先读 club 表;字段为空时按项回落 settings.WECHAT_PAY_V3_CONFIG。 + + 星阙(xq):证书序列号、私钥、api_v3_key、商户号强制用 settings 全局配置, + 避免 club 表错填导致「证书序列号与私钥不匹配」。仅 AppID 读 club 小程序配置。 + 其他俱乐部仍读 club 表,但序列号统一写死覆盖。 """ cid = (club_id or '').strip() or CLUB_ID_DEFAULT wx = dict(getattr(settings, 'WECHAT_PAY_V3_CONFIG', {}) or {}) @@ -220,35 +237,43 @@ def get_wechat_v3_config(club_id=None): 'APPID': wx.get('APPID', ''), 'MCHID': wx.get('MCHID', ''), 'PRIVATE_KEY_PATH': wx.get('PRIVATE_KEY_PATH', ''), - 'CERT_SERIAL_NO': wx.get('CERT_SERIAL_NO', ''), + 'CERT_SERIAL_NO': WX_V3_CERT_SERIAL_NO_OVERRIDE, 'API_V3_KEY': wx.get('API_V3_KEY', ''), 'PLATFORM_CERT_DIR': wx.get('PLATFORM_CERT_DIR', ''), 'TRANSFER_SCENE_ID': wx.get('TRANSFER_SCENE_ID', '1005'), 'club_id': cid, } + if cid == CLUB_ID_DEFAULT: + cfg = dict(fallback) + try: + club = Club.query.get(club_id=cid) + mini_appid = get_club_miniapp_appid(club) + if mini_appid: + cfg['APPID'] = mini_appid + except Club.DoesNotExist: + pass + _log_v3_config(cfg) + return cfg + try: club = Club.query.get(club_id=cid) except Club.DoesNotExist: logger.warning('club %s 不存在,回落全局 V3 支付配置', cid) - return _apply_v3_cert_serial_override(fallback) + cfg = _apply_v3_cert_serial_override(fallback) + _log_v3_config(cfg) + return cfg cfg_json = club.config_json or {} - return _apply_v3_cert_serial_override({ - 'APPID': get_club_miniapp_appid(club) or ( - fallback['APPID'] if cid == CLUB_ID_DEFAULT else '' - ), + cfg = _apply_v3_cert_serial_override({ + 'APPID': get_club_miniapp_appid(club) or '', 'MCHID': club.mch_id or fallback['MCHID'], 'PRIVATE_KEY_PATH': ( club.private_key_path or cfg_json.get('private_key_path') or fallback['PRIVATE_KEY_PATH'] ), - 'CERT_SERIAL_NO': ( - club.cert_serial_no - or cfg_json.get('cert_serial_no') - or fallback['CERT_SERIAL_NO'] - ), + 'CERT_SERIAL_NO': fallback['CERT_SERIAL_NO'], 'API_V3_KEY': ( club.api_v3_key or cfg_json.get('api_v3_key') @@ -266,6 +291,8 @@ def get_wechat_v3_config(club_id=None): ), 'club_id': cid, }) + _log_v3_config(cfg) + return cfg def club_id_for_tixian_yonghuid(yonghuid): diff --git a/utils/wechat_v3.py b/utils/wechat_v3.py index 782b737..e31c815 100644 --- a/utils/wechat_v3.py +++ b/utils/wechat_v3.py @@ -38,7 +38,10 @@ def build_authorization(method, url, body, club_id=None): cfg = _get_v3_config(club_id) private_key = load_private_key(club_id) mchid = cfg['MCHID'] - serial_no = cfg['CERT_SERIAL_NO'] + serial_no = (cfg.get('CERT_SERIAL_NO') or '').strip().upper() + if not serial_no: + from jituan.services.wechat_pay import WX_V3_CERT_SERIAL_NO_OVERRIDE + serial_no = WX_V3_CERT_SERIAL_NO_OVERRIDE parsed = urlparse(url) path = parsed.path