fix: 罚款微信支付 openid 与 AppID 使用同一俱乐部

交罚款时不再用罚单申请人俱乐部取 openid、打手俱乐部取商户配置;统一按当前小程序俱乐部下单,避免部分人 appid/openid 不一致导致下单失败。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-17 18:45:57 +08:00
parent b6044cd44a
commit e7b59feacd

View File

@@ -142,6 +142,10 @@ class FaKuanPayView(APIView):
) )
# 生成支付参数(支持支付宝/微信) # 生成支付参数(支持支付宝/微信)
# 支付必须用「当前小程序/打手写入俱乐部」的 AppID+openid
# 与罚单 ClubID申请人俱乐部混用会导致部分人 openid/appid 不一致而下单失败。
from jituan.services.club_write import resolve_club_id_for_write
pay_club = resolve_club_id_for_write(request, request.user)
pay_method = (request.data.get('pay_method') or request.data.get('payMethod') or 'wechat').lower() pay_method = (request.data.get('pay_method') or request.data.get('payMethod') or 'wechat').lower()
if pay_method == 'alipay': if pay_method == 'alipay':
try: try:
@@ -150,42 +154,46 @@ class FaKuanPayView(APIView):
dingdanid=dingdanid, dingdanid=dingdanid,
jine=jine, jine=jine,
subject=settings.FAKUAN_PAY_DESCRIPTION, subject=settings.FAKUAN_PAY_DESCRIPTION,
club_id=penalty_cz_club_id(fadan), club_id=pay_club,
) )
except Exception as e: except Exception as e:
chongzhi_jilu.delete() chongzhi_jilu.delete()
logger.error(f"生成支付宝支付参数失败: {str(e)}") logger.error(f"生成支付宝支付参数失败: {str(e)}")
return Response({ return Response({
'code': 500, 'code': 500,
'message': f'支付参数生成失败: {str(e)}' 'message': f'支付参数生成失败: {str(e)}',
'msg': f'支付参数生成失败: {str(e)}',
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
else: else:
pay_club = penalty_cz_club_id(fadan) pay_openid, pay_club = get_payment_openid(request, club_id=pay_club)
pay_openid, _ = get_payment_openid(request, club_id=pay_club)
if not pay_openid: if not pay_openid:
chongzhi_jilu.delete() chongzhi_jilu.delete()
return Response({ return Response({
'code': 400, 'code': 400,
'message': '当前小程序未绑定微信openid请退出后重新登录再支付', 'message': '当前小程序未绑定微信openid请退出后重新登录再支付',
'msg': '当前小程序未绑定微信openid请退出后重新登录再支付',
}, status=status.HTTP_400_BAD_REQUEST) }, status=status.HTTP_400_BAD_REQUEST)
try: try:
pay_params = self.generate_wechat_pay_params( pay_params = self.generate_wechat_pay_params(
dingdanid=dingdanid, dingdanid=dingdanid,
jine=jine, jine=jine,
openid=pay_openid, openid=pay_openid,
pay_type='fakuan' pay_type='fakuan',
club_id=pay_club,
) )
except Exception as e: except Exception as e:
chongzhi_jilu.delete() chongzhi_jilu.delete()
logger.error(f"生成微信支付参数失败: {str(e)}") logger.error(f"生成微信支付参数失败: {str(e)}")
return Response({ return Response({
'code': 500, 'code': 500,
'message': f'支付参数生成失败: {str(e)}' 'message': f'支付参数生成失败: {str(e)}',
'msg': f'支付参数生成失败: {str(e)}',
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response({ return Response({
'code': 200, 'code': 200,
'message': '支付参数生成成功', 'message': '支付参数生成成功',
'msg': '支付参数生成成功',
'payParams': pay_params, 'payParams': pay_params,
'dingdanid': dingdanid 'dingdanid': dingdanid
}, status=status.HTTP_200_OK) }, status=status.HTTP_200_OK)
@@ -194,16 +202,30 @@ class FaKuanPayView(APIView):
logger.error(f"罚款支付下单接口异常: {str(e)}", exc_info=True) logger.error(f"罚款支付下单接口异常: {str(e)}", exc_info=True)
return Response({ return Response({
'code': 500, 'code': 500,
'message': '服务器内部错误,请稍后重试' 'message': '服务器内部错误,请稍后重试',
'msg': '服务器内部错误,请稍后重试',
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def generate_wechat_pay_params(self, dingdanid, jine, openid, pay_type): def generate_wechat_pay_params(self, dingdanid, jine, openid, pay_type, club_id=None):
from jituan.services.club_write import resolve_club_id_for_write from jituan.services.club_write import resolve_club_id_for_write
from jituan.services.wechat_pay import get_wechat_v2_config from jituan.services.wechat_pay import (
pay_cfg = get_wechat_v2_config(resolve_club_id_for_write(self.request, self.request.user)) get_wechat_v2_config,
log_wechat_v2_pay_diag,
parse_wechat_v2_unifiedorder_response,
)
pay_club = club_id or resolve_club_id_for_write(self.request, self.request.user)
pay_cfg = get_wechat_v2_config(pay_club)
uid = getattr(self.request.user, 'UserUID', '')
log_wechat_v2_pay_diag(
pay_cfg, scene='fakuan', openid=openid, order_hint=f'{dingdanid} uid={uid}',
)
APPID = pay_cfg['appid'] APPID = pay_cfg['appid']
MCHID = pay_cfg['mch_id'] MCHID = pay_cfg['mch_id']
KEY = pay_cfg['key'] KEY = pay_cfg['key']
if not APPID or not MCHID or not KEY:
raise Exception(
f'俱乐部{pay_club}微信支付配置不完整(appid/mch_id/密钥),请联系管理员'
)
if pay_type == 'fakuan': if pay_type == 'fakuan':
PAY_DESCRIPTION = settings.FAKUAN_PAY_DESCRIPTION PAY_DESCRIPTION = settings.FAKUAN_PAY_DESCRIPTION
@@ -259,63 +281,36 @@ class FaKuanPayView(APIView):
logger.info(f"罚款微信支付响应: {response.text}") logger.info(f"罚款微信支付响应: {response.text}")
root = ET.fromstring(response.content) parsed = parse_wechat_v2_unifiedorder_response(
response.content, log_prefix=f'FAKUAN_UNIFIED club={pay_club}',
)
prepay_id = parsed['prepay_id']
timestamp = str(int(time.time()))
nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32))
package = f'prepay_id={prepay_id}'
sign_type = 'MD5'
return_code_node = root.find('return_code') pay_sign_data = {
if return_code_node is None or return_code_node.text is None: 'appId': APPID,
logger.error(f"微信返回缺少return_code原始: {response.text}") 'timeStamp': timestamp,
raise Exception('微信支付接口异常缺少return_code') 'nonceStr': nonce_str,
return_code = return_code_node.text 'package': package,
'signType': sign_type
}
result_code_node = root.find('result_code') sorted_pay_sign_data = sorted(pay_sign_data.items(), key=lambda x: x[0])
result_code = result_code_node.text if result_code_node is not None else None pay_sign_string = '&'.join([f"{key}={value}" for key, value in sorted_pay_sign_data])
pay_sign_string += f'&key={KEY}'
pay_sign = hashlib.md5(pay_sign_string.encode('utf-8')).hexdigest().upper()
if return_code == 'SUCCESS' and result_code == 'SUCCESS': return {
prepay_id_node = root.find('prepay_id') 'appId': APPID,
if prepay_id_node is None or prepay_id_node.text is None: 'timeStamp': timestamp,
logger.error(f"统一下单成功但无prepay_id原始: {response.text}") 'nonceStr': nonce_str,
raise Exception('微信支付接口异常缺少prepay_id') 'package': package,
prepay_id = prepay_id_node.text 'signType': sign_type,
timestamp = str(int(time.time())) 'paySign': pay_sign
nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32)) }
package = f'prepay_id={prepay_id}'
sign_type = 'MD5'
pay_sign_data = {
'appId': APPID,
'timeStamp': timestamp,
'nonceStr': nonce_str,
'package': package,
'signType': sign_type
}
sorted_pay_sign_data = sorted(pay_sign_data.items(), key=lambda x: x[0])
pay_sign_string = '&'.join([f"{key}={value}" for key, value in sorted_pay_sign_data])
pay_sign_string += f'&key={KEY}'
pay_sign = hashlib.md5(pay_sign_string.encode('utf-8')).hexdigest().upper()
return {
'appId': APPID,
'timeStamp': timestamp,
'nonceStr': nonce_str,
'package': package,
'signType': sign_type,
'paySign': pay_sign
}
else:
err_code = root.find('err_code')
err_code_des = root.find('err_code_des')
return_msg = root.find('return_msg')
error_details = f"return_code: {return_code}"
if result_code:
error_details += f", result_code: {result_code}"
if return_msg is not None and return_msg.text:
error_details += f", return_msg: {return_msg.text}"
if err_code is not None and err_code.text:
error_details += f", err_code: {err_code.text}"
if err_code_des is not None and err_code_des.text:
error_details += f", err_code_des: {err_code_des.text}"
raise Exception(f"微信支付下单失败: {error_details}")
def get_client_ip(self): def get_client_ip(self):
request = self.request request = self.request