"""各俱乐部微信支付 V2(MD5)配置与验签。""" import hashlib import logging from django.conf import settings from jituan.constants import CLUB_ID_DEFAULT from jituan.models import Club logger = logging.getLogger(__name__) def get_wechat_v2_config(club_id=None): """ 小程序 JSAPI 支付 V2 配置。 多 club 时读 club 表;缺省或 xq 回落 settings(现网不变)。 """ cid = (club_id or '').strip() or CLUB_ID_DEFAULT fallback = { 'appid': getattr(settings, 'WEIXIN_APPID', ''), 'mch_id': getattr(settings, 'WEIXIN_MCHID', ''), 'key': getattr(settings, 'WEIXIN_SHANGHUMIYAO', ''), 'club_id': CLUB_ID_DEFAULT, } if cid == CLUB_ID_DEFAULT: return fallback try: club = Club.query.get(club_id=cid) except Club.DoesNotExist: logger.warning('club %s 不存在,回落全局支付配置', cid) return fallback cfg_json = club.config_json or {} mch_key = ( cfg_json.get('mch_key') or cfg_json.get('shanghumiyao') or cfg_json.get('WEIXIN_SHANGHUMIYAO') or fallback['key'] ) return { 'appid': club.pay_app_id or club.wx_appid or fallback['appid'], 'mch_id': club.mch_id or fallback['mch_id'], 'key': mch_key, 'club_id': cid, } def verify_wechat_v2_signature(xml_data, club_id=None): """验证微信 V2 支付回调 MD5 签名。""" try: import defusedxml.ElementTree as ET root = ET.fromstring(xml_data) sign_node = root.find('sign') if sign_node is None or not sign_node.text: return False sign = sign_node.text data = {child.tag: child.text for child in root if child.tag != 'sign'} string_a = '&'.join([f'{k}={data[k]}' for k in sorted(data.keys()) if data[k]]) pay_cfg = get_wechat_v2_config(club_id) calc = hashlib.md5(f'{string_a}&key={pay_cfg["key"]}'.encode('utf-8')).hexdigest().upper() if calc == sign: return True # 兼容:子 club 未配密钥时尝试全局密钥 global_key = getattr(settings, 'WEIXIN_SHANGHUMIYAO', '') if global_key and global_key != pay_cfg['key']: calc2 = hashlib.md5(f'{string_a}&key={global_key}'.encode('utf-8')).hexdigest().upper() return calc2 == sign return False except Exception as e: logger.error('微信验签异常: %s', e) return False def club_id_from_czjilu(dingdan_id): from products.models import Czjilu try: row = Czjilu.query.filter(dingdan_id=dingdan_id).first() if row and getattr(row, 'club_id', None): return row.club_id except Exception: pass return CLUB_ID_DEFAULT def club_id_from_order(order_id): from orders.models import Order try: row = Order.query.filter(OrderID=order_id).first() if row and getattr(row, 'ClubID', None): return row.ClubID except Exception: pass return CLUB_ID_DEFAULT def get_wechat_v3_config(club_id=None): """ 微信 V3(转账/查单)配置。 多 club 读 club 表;缺省或 xq 回落 settings.WECHAT_PAY_V3_CONFIG。 """ cid = (club_id or '').strip() or CLUB_ID_DEFAULT wx = dict(getattr(settings, 'WECHAT_PAY_V3_CONFIG', {}) or {}) fallback = { 'APPID': wx.get('APPID', ''), 'MCHID': wx.get('MCHID', ''), 'PRIVATE_KEY_PATH': wx.get('PRIVATE_KEY_PATH', ''), 'CERT_SERIAL_NO': wx.get('CERT_SERIAL_NO', ''), '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': CLUB_ID_DEFAULT, } if cid == CLUB_ID_DEFAULT: return fallback try: club = Club.query.get(club_id=cid) except Club.DoesNotExist: logger.warning('club %s 不存在,回落全局 V3 支付配置', cid) return fallback cfg_json = club.config_json or {} return { 'APPID': club.pay_app_id or club.wx_appid or fallback['APPID'], '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'] ), 'API_V3_KEY': ( club.api_v3_key or cfg_json.get('api_v3_key') or fallback['API_V3_KEY'] ), 'PLATFORM_CERT_DIR': ( club.platform_cert_dir or cfg_json.get('platform_cert_dir') or fallback['PLATFORM_CERT_DIR'] ), 'TRANSFER_SCENE_ID': ( cfg_json.get('transfer_scene_id') or cfg_json.get('TRANSFER_SCENE_ID') or fallback['TRANSFER_SCENE_ID'] ), 'club_id': cid, } def club_id_for_tixian_yonghuid(yonghuid): from jituan.services.club_user import get_user_club_id from users.business_models import User user = User.query.filter(UserUID=yonghuid).first() return get_user_club_id(user)