点单支付支持后端开关切换商户/虚拟支付,默认商户支付;前端按 pay_mode 分支。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-22 18:45:37 +08:00
parent 04fcfb909b
commit 2401794faf
2 changed files with 92 additions and 47 deletions

View File

@@ -269,6 +269,10 @@ WEIXIN_SECRET = '269dbb2a81fa843ef92f0cee8857c841'
# 点单支付方式(前端按返回的 pay_mode 自动分支,改这里即可,无需再改小程序)
# merchant = 商户微信支付virtual = 虚拟支付(代币)
DIANDAN_PAY_MODE = 'merchant'
# 微信虚拟支付配置
VP_OFFER_ID = '1450521456' # 虚拟支付应用ID
VP_APPKEY = '4cQkc13i7rkFNbubUJ63ijP2MMCrpB45' # 正式环境密钥

View File

@@ -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()