点单支付支持后端开关切换商户/虚拟支付,默认商户支付;前端按 pay_mode 分支。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -269,6 +269,10 @@ WEIXIN_SECRET = '269dbb2a81fa843ef92f0cee8857c841'
|
||||
|
||||
|
||||
|
||||
# 点单支付方式(前端按返回的 pay_mode 自动分支,改这里即可,无需再改小程序)
|
||||
# merchant = 商户微信支付;virtual = 虚拟支付(代币)
|
||||
DIANDAN_PAY_MODE = 'merchant'
|
||||
|
||||
# 微信虚拟支付配置
|
||||
VP_OFFER_ID = '1450521456' # 虚拟支付应用ID
|
||||
VP_APPKEY = '4cQkc13i7rkFNbubUJ63ijP2MMCrpB45' # 正式环境密钥
|
||||
|
||||
135
dingdan/views.py
135
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()
|
||||
|
||||
Reference in New Issue
Block a user