diff --git a/a_long_dianjing/settings.py b/a_long_dianjing/settings.py index 4c598ad..4b91d11 100644 --- a/a_long_dianjing/settings.py +++ b/a_long_dianjing/settings.py @@ -269,6 +269,10 @@ WEIXIN_SECRET = '269dbb2a81fa843ef92f0cee8857c841' +# 点单支付方式(前端按返回的 pay_mode 自动分支,改这里即可,无需再改小程序) +# merchant = 商户微信支付;virtual = 虚拟支付(代币) +DIANDAN_PAY_MODE = 'merchant' + # 微信虚拟支付配置 VP_OFFER_ID = '1450521456' # 虚拟支付应用ID VP_APPKEY = '4cQkc13i7rkFNbubUJ63ijP2MMCrpB45' # 正式环境密钥 diff --git a/dingdan/views.py b/dingdan/views.py index 0498d96..4ee1eb4 100644 --- a/dingdan/views.py +++ b/dingdan/views.py @@ -941,11 +941,11 @@ logger = logging.getLogger(__name__) class VirtualCreateOrderView(APIView): """ - 虚拟代币支付创建订单 - 流程与原 CreateOrderView 完全一致 - session_key 管理(已修改为强制刷新): - 每次下单必须携带 code,用 code 换取最新 session_key 并存入 UserMain 表 - 不再信任数据库中缓存的旧 session_key + 点单创建订单(兼容商户支付 / 虚拟支付) + 由 settings.DIANDAN_PAY_MODE 决定: + merchant = 商户微信支付(默认) + virtual = 虚拟支付(代币) + 返回 data.pay_mode,前端按此字段调起对应支付,无需再改前端。 """ permission_classes = [permissions.IsAuthenticated] @@ -957,7 +957,7 @@ class VirtualCreateOrderView(APIView): beizhu = request.data.get('beizhu', '').strip() zhiding = request.data.get('zhiding', '').strip() jine = request.data.get('jine', 0) - code = request.data.get('code') # 每次下单必须携带,用于换取最新 session_key + code = request.data.get('code') # 虚拟支付必需;商户支付可选(用于刷新openid) # 2. 指定功能临时关闭(与原逻辑一致) if zhiding: @@ -1012,25 +1012,31 @@ class VirtualCreateOrderView(APIView): else: dashou_fencheng = Decimal(str(shangpin_jiage)) * Decimal(str(dashou_lilu.lilu)) - # ---------- 修改 session_key 获取逻辑 ---------- - # 每次下单必须携带 code,强制刷新 session_key - if not code: + # 点单支付方式:settings.DIANDAN_PAY_MODE 控制,前端按返回 pay_mode 分支 + pay_mode = getattr(settings, 'DIANDAN_PAY_MODE', 'merchant') + if pay_mode not in ('merchant', 'virtual'): + pay_mode = 'merchant' + + current_user = request.user + session_key = getattr(current_user, 'session_key', '') or '' + user_openid = getattr(current_user, 'openid', '') or '' + + # 有 code 则刷新 openid/session_key(虚拟支付必需;商户支付可选增强) + if code: + wx_resp = self._exchange_code_for_session_key(code) + if wx_resp is None: + return Response({'code': 11, 'msg': '微信认证失败', 'data': None}) + session_key = wx_resp['session_key'] + user_openid = wx_resp['openid'] + current_user.openid = user_openid + current_user.session_key = session_key + current_user.save(update_fields=['openid', 'session_key']) + logger.info(f"[DiandanCreate] 已刷新 session_key: openid={user_openid}, pay_mode={pay_mode}") + elif pay_mode == 'virtual': return Response({'code': 10, 'msg': '缺少登录凭证code', 'data': None}) - wx_resp = self._exchange_code_for_session_key(code) - if wx_resp is None: - return Response({'code': 11, 'msg': '微信认证失败', 'data': None}) - - session_key = wx_resp['session_key'] - user_openid = wx_resp['openid'] - - # 立即更新当前用户的 openid 和 session_key 到数据库,保证其他接口可用 - current_user = request.user - current_user.openid = user_openid - current_user.session_key = session_key - current_user.save(update_fields=['openid', 'session_key']) - logger.info(f"[VirtualCreate] 已刷新 session_key 并存入数据库: openid={user_openid}") - # --------------------------------------------- + if not user_openid: + return Response({'code': 10, 'msg': '用户openid不存在', 'data': None}) # 9. 数据库事务创建订单(与原逻辑完全相同) with transaction.atomic(): @@ -1065,29 +1071,44 @@ class VirtualCreateOrderView(APIView): laoban_id=laoban_yonghuid, ) - # 生成代币虚拟支付参数 - pay_params = self.generate_virtual_pay_params( - dingdanid=dingdanid, - jine=shangpin_jiage, - openid=user_openid, - session_key=session_key, - beizhu=beizhu - ) + merchant_view = CreateOrderView() + if pay_mode == 'virtual': + pay_params = self.generate_virtual_pay_params( + dingdanid=dingdanid, + jine=shangpin_jiage, + openid=user_openid, + session_key=session_key, + beizhu=beizhu + ) + if not pay_params: + raise Exception('虚拟支付参数生成失败') + else: + pay_params = merchant_view.generate_wechat_pay_params( + dingdanid=dingdanid, + jine=shangpin_jiage, + beizhu=getattr(settings, 'WEIXIN_PAY_DESCRIPTION', '订单支付'), + user_ip=merchant_view.get_client_ip(request), + openid=user_openid, + ) + if not pay_params: + raise Exception('商户支付参数生成失败') - if not pay_params: - raise Exception('虚拟支付参数生成失败') + # 供复查接口按本单支付方式分支(24小时) + from django.core.cache import cache + cache.set(f'diandan_pay_mode_{dingdanid}', pay_mode, timeout=86400) return Response({ 'code': 0, 'msg': '订单创建成功', 'data': { 'dingdanid': dingdanid, + 'pay_mode': pay_mode, 'payParams': pay_params } }) except Exception as e: - logger.error(f"虚拟支付下单失败: {e}", exc_info=True) + logger.error(f"点单下单失败: {e}", exc_info=True) return Response({'code': 99, 'msg': '系统错误', 'data': None}) def _exchange_code_for_session_key(self, code): @@ -1619,15 +1640,8 @@ class VirtualPayNotifyView(APIView): class VirtualPaymentVerifyView(APIView): """ - 虚拟支付复查接口(代币订单)- 简化版 - 策略: - 因微信后台查单API权限问题,暂时绕过主动查单。 - 只要前端调起复查接口(代表前端wx.requestVirtualPayment已成功回调), - 且订单状态为9(未支付),直接信任前端支付结果,更新订单为已支付。 - 安全提示: - 此方式依赖JWT Token的不可伪造性。若Token泄露,可能被伪造支付。 - 后期建议开通查单权限后恢复主动查单逻辑。 - 业务逻辑与原 PaymentVerifyView 完全一致,仅跳过微信查单步骤。 + 点单支付复查(兼容商户支付 / 虚拟支付) + 优先用前端回传的 pay_mode,其次缓存,再次 settings.DIANDAN_PAY_MODE """ permission_classes = [permissions.IsAuthenticated] parser_classes = [JSONParser] @@ -1659,16 +1673,44 @@ class VirtualPaymentVerifyView(APIView): 'data': {'dingdanid': dingdanid, 'zhuangtai': dingdan.zhuangtai} }) - # 订单为未支付,直接信任前端支付结果 + from django.core.cache import cache + pay_mode = request.data.get('pay_mode') or cache.get(f'diandan_pay_mode_{dingdanid}') + if pay_mode not in ('merchant', 'virtual'): + pay_mode = getattr(settings, 'DIANDAN_PAY_MODE', 'merchant') + if pay_mode not in ('merchant', 'virtual'): + pay_mode = 'merchant' + + # 订单为未支付 if dingdan.zhuangtai == 9: - from django.core.cache import cache lock_key = f"virtual_verify_lock_{dingdanid}" if not cache.add(lock_key, "locked", timeout=10): logger.info(f"[复查] 订单 {dingdanid} 正在处理中,稍后重试") return Response({'code': 1, 'msg': '订单正在处理中,请稍后'}) try: - # 尝试调用微信查单(日志记录用,结果不影响业务流程) + if pay_mode == 'merchant': + # 商户支付:主动查微信订单 + merchant_verify = PaymentVerifyView() + try: + trade_state, transaction_id = merchant_verify._query_wechat_payment(dingdanid) + except Exception as e: + logger.error(f"[复查] 商户支付查单失败: {e}", exc_info=True) + return Response({'code': 1, 'msg': '查询失败,请稍后重试'}) + + if trade_state == 'SUCCESS': + self._process_payment_success(dingdan, transaction_id or '', current_user.yonghuid) + dingdan.refresh_from_db() + return Response({ + 'code': 0, + 'msg': '支付成功', + 'data': {'dingdanid': dingdanid, 'zhuangtai': dingdan.zhuangtai} + }) + elif trade_state in ['NOTPAY', 'USERPAYING']: + return Response({'code': 1, 'msg': '支付中', 'data': {'zhuangtai': 9}}) + else: + return Response({'code': 2, 'msg': '支付失败'}) + + # 虚拟支付:尽量查单,失败则信任前端成功回调 try: trade_state, transaction_id = self._query_virtual_payment(dingdanid) logger.info(f"[复查] 微信查单结果: trade_state={trade_state}, transaction_id={transaction_id}") @@ -1676,7 +1718,6 @@ class VirtualPaymentVerifyView(APIView): logger.warning(f"[复查] 微信查单失败(忽略): {e}") transaction_id = "" - # 无论查单结果如何,直接更新订单为已支付 logger.info(f"[复查] 信任前端支付结果,执行本地更新") self._process_payment_success(dingdan, transaction_id, current_user.yonghuid) dingdan.refresh_from_db()