禁止 Czjilu 回调地址回落到订单 notify;回调失败回 fail 重试;取消支付改关单不删;客服补单走同一履约防多加。 Co-authored-by: Cursor <cursoragent@cursor.com>
637 lines
26 KiB
Python
637 lines
26 KiB
Python
"""押金支付流程视图。
|
||
|
||
包含押金购买的参数生成、支付回调、失败处理、轮询。
|
||
"""
|
||
# ==================== 标准库 ====================
|
||
import os
|
||
import json
|
||
import time
|
||
import random
|
||
import string
|
||
import uuid
|
||
import hashlib
|
||
import requests
|
||
import logging
|
||
import decimal
|
||
from datetime import timedelta
|
||
from decimal import Decimal, InvalidOperation, ROUND_CEILING
|
||
import defusedxml.ElementTree as ET
|
||
|
||
# ==================== Django 内置 ====================
|
||
from django.conf import settings
|
||
from django.db import transaction, connection
|
||
from django.db.models import Q
|
||
from gvsdsdk.fluent import db, func, FQ
|
||
from django.shortcuts import render, get_object_or_404
|
||
from django.http import HttpResponse
|
||
from django.utils import timezone
|
||
|
||
# ==================== DRF 框架 ====================
|
||
from rest_framework.views import APIView, View
|
||
from rest_framework.response import Response
|
||
from rest_framework import permissions, status
|
||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
|
||
from rest_framework.throttling import AnonRateThrottle
|
||
|
||
# ==================== 项目工具函数 ====================
|
||
from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss
|
||
from utils.money import format_yuan, quantize_yuan, yuan_to_fen
|
||
from backend.utils import (
|
||
update_guanshi_daily_by_action,
|
||
update_guanshi_xufei_daily,
|
||
update_zuzhang_daily_by_action
|
||
)
|
||
from users.fadan_fenhong_utils import process_fadan_fenhong
|
||
|
||
# ==================== 各App模型 ====================
|
||
from ..models import (
|
||
ShangpinLeixing, ShangpinZhuanqu, Shangpin, Huiyuan,
|
||
Czjilu, Gsfenhong, Huiyuangoumai, DuociFenhong
|
||
)
|
||
from users.models import (
|
||
AdminProfile, UserDashou, UserGuanshi, UserZuzhang, UserShangjia
|
||
)
|
||
from users.business_models import User
|
||
|
||
from orders.models import CommissionRate
|
||
from config.models import Gonggao, Lunbo
|
||
|
||
from orders.models import Penalty # 用于罚款类型
|
||
from jituan.services.club_config import (
|
||
get_commission_rate,
|
||
get_commission_rate_object,
|
||
get_huiyuan_price,
|
||
get_huiyuan_fenchong,
|
||
)
|
||
from jituan.services.club_context import resolve_effective_club_id
|
||
from jituan.services.club_write import resolve_club_id_for_write
|
||
from jituan.services.club_user import get_payment_openid
|
||
from jituan.services.club_penalty import resolve_gsfenhong_club_id
|
||
|
||
import traceback
|
||
|
||
# ==================== 日志实例 ====================
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class YajinGoumai(APIView):
|
||
"""
|
||
押金充值支付参数接口
|
||
路径:/shangpin/yajingoumai
|
||
请求方式:POST
|
||
权限:JWT认证
|
||
参数:jine(金额,1-10000元)
|
||
返回:支付参数(payParams)和订单ID(dingdanid)
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 验证用户身份和状态
|
||
try:
|
||
dashou = request.user.DashouProfile
|
||
except UserDashou.DoesNotExist:
|
||
return Response({
|
||
'code': 400,
|
||
'message': '用户不是打手,无法充值押金'
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if dashou.zhanghaozhuangtai != 1:
|
||
return Response({
|
||
'code': 400,
|
||
'message': '账号状态异常,无法进行充值'
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 2. 获取并验证金额参数
|
||
jine = request.data.get('jine')
|
||
if not jine:
|
||
return Response({
|
||
'code': 400,
|
||
'message': '请填写充值金额'
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
jine = int(jine)
|
||
except (ValueError, TypeError):
|
||
return Response({
|
||
'code': 400,
|
||
'message': '金额格式错误,请填写1-10000的整数'
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 验证金额范围
|
||
if jine < 1 or jine > 10000:
|
||
return Response({
|
||
'code': 400,
|
||
'message': '充值金额必须在1-10000元之间'
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 3. 生成订单ID(使用你提供的算法,前缀改为CzYJ)
|
||
timestamp_ms = int(time.time() * 1000)
|
||
timestamp_ns = int(time.time_ns() // 1000) % 1000000
|
||
random_num = random.randint(0, 99)
|
||
|
||
dingdanid = f"CzYJ{timestamp_ms:011d}{timestamp_ns:06d}{random_num:02d}"
|
||
|
||
# 4. 创建充值记录订单
|
||
chongzhi_jilu = Czjilu.query.create(
|
||
dingdan_id=dingdanid,
|
||
zhuangtai=9, # 未支付状态
|
||
yonghuid=request.user.UserUID,
|
||
jine=jine,
|
||
leixing=2, # 充值类型:2=押金
|
||
shuoming=settings.YAJIN_PAY_DESCRIPTION,
|
||
club_id=resolve_club_id_for_write(request, request.user),
|
||
)
|
||
|
||
logger.info(f"创建押金充值订单: 用户{request.user.UserUID}, 订单{dingdanid}, 金额{jine}元")
|
||
|
||
# 5. 生成支付参数(支持支付宝/微信)
|
||
pay_method = (request.data.get('pay_method') or request.data.get('payMethod') or 'wechat').lower()
|
||
if pay_method == 'alipay':
|
||
try:
|
||
from jituan.services.alipay_pay import generate_alipay_czjilu_pay_params
|
||
pay_params = generate_alipay_czjilu_pay_params(
|
||
dingdanid=dingdanid,
|
||
jine=jine,
|
||
subject=settings.YAJIN_PAY_DESCRIPTION,
|
||
club_id=resolve_club_id_for_write(request, request.user),
|
||
)
|
||
except Exception as e:
|
||
chongzhi_jilu.delete()
|
||
logger.error(f"生成支付宝支付参数失败: {str(e)}")
|
||
return Response({
|
||
'code': 500,
|
||
'message': f'支付参数生成失败: {str(e)}'
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
else:
|
||
try:
|
||
pay_club = resolve_club_id_for_write(request, request.user)
|
||
pay_openid, _ = get_payment_openid(request, club_id=pay_club)
|
||
if not pay_openid:
|
||
chongzhi_jilu.delete()
|
||
return Response({
|
||
'code': 400,
|
||
'message': '当前小程序未绑定微信openid,请退出后重新登录再支付',
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
pay_params = self.generate_wechat_pay_params(
|
||
dingdanid=dingdanid,
|
||
jine=jine,
|
||
openid=pay_openid,
|
||
pay_type='yajin'
|
||
)
|
||
except Exception as e:
|
||
# 支付参数生成失败,删除订单记录
|
||
chongzhi_jilu.delete()
|
||
logger.error(f"生成微信支付参数失败: {str(e)}")
|
||
return Response({
|
||
'code': 500,
|
||
'message': f'支付参数生成失败: {str(e)}'
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
# 6. 返回支付参数和订单ID
|
||
return Response({
|
||
'code': 200,
|
||
'message': '支付参数生成成功',
|
||
'payParams': pay_params, # 支付参数(前端接收为payParams)
|
||
'dingdanid': dingdanid # 订单ID(前端接收为dingdanid)
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
logger.error(f"押金充值接口异常: {str(e)}", exc_info=True)
|
||
return Response({
|
||
'code': 500,
|
||
'message': '服务器内部错误,请稍后重试'
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
def generate_wechat_pay_params(self, dingdanid, jine, openid, pay_type):
|
||
"""生成微信小程序支付参数"""
|
||
# 获取配置
|
||
from jituan.services.club_write import resolve_club_id_for_write
|
||
from jituan.services.wechat_pay import get_wechat_v2_config
|
||
pay_cfg = get_wechat_v2_config(resolve_club_id_for_write(self.request, self.request.user))
|
||
APPID = pay_cfg['appid']
|
||
MCHID = pay_cfg['mch_id']
|
||
KEY = pay_cfg['key']
|
||
|
||
# 根据支付类型获取对应的配置
|
||
if pay_type == 'yajin':
|
||
PAY_DESCRIPTION = settings.YAJIN_PAY_DESCRIPTION
|
||
NOTIFY_URL = settings.YAJIN_PAY_NOTIFY_URL
|
||
elif pay_type == 'jifen':
|
||
PAY_DESCRIPTION = settings.JIFEN_PAY_DESCRIPTION
|
||
NOTIFY_URL = settings.JIFEN_PAY_NOTIFY_URL
|
||
elif pay_type == 'huiyuan':
|
||
PAY_DESCRIPTION = settings.HUIYUAN_PAY_DESCRIPTION
|
||
NOTIFY_URL = settings.HUIYUAN_PAY_NOTIFY_URL
|
||
else:
|
||
raise Exception('未知的支付类型')
|
||
|
||
# 生成随机字符串
|
||
nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32))
|
||
|
||
# 金额转换(元转分)
|
||
total_fee = yuan_to_fen(jine)
|
||
|
||
# 交易类型(小程序支付使用JSAPI)
|
||
trade_type = 'JSAPI'
|
||
|
||
# 获取客户端IP
|
||
user_ip = self.get_client_ip()
|
||
|
||
# 1. 第一次签名(统一下单接口)
|
||
sign_data = {
|
||
'appid': APPID,
|
||
'mch_id': MCHID,
|
||
'nonce_str': nonce_str,
|
||
'body': PAY_DESCRIPTION,
|
||
'out_trade_no': dingdanid,
|
||
'total_fee': total_fee,
|
||
'spbill_create_ip': user_ip,
|
||
'notify_url': NOTIFY_URL,
|
||
'trade_type': trade_type,
|
||
'openid': openid # 小程序支付必须传openid
|
||
}
|
||
|
||
# 按照参数名ASCII码从小到大排序
|
||
sorted_sign_data = sorted(sign_data.items(), key=lambda x: x[0])
|
||
|
||
# 拼接字符串
|
||
sign_string = '&'.join([f"{key}={value}" for key, value in sorted_sign_data if value])
|
||
sign_string += f'&key={KEY}'
|
||
|
||
# MD5签名
|
||
sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest().upper()
|
||
|
||
# 构造XML数据
|
||
xml_data = f"""<xml>
|
||
<appid>{APPID}</appid>
|
||
<body>{PAY_DESCRIPTION}</body>
|
||
<mch_id>{MCHID}</mch_id>
|
||
<nonce_str>{nonce_str}</nonce_str>
|
||
<notify_url>{NOTIFY_URL}</notify_url>
|
||
<openid>{openid}</openid>
|
||
<out_trade_no>{dingdanid}</out_trade_no>
|
||
<spbill_create_ip>{user_ip}</spbill_create_ip>
|
||
<total_fee>{total_fee}</total_fee>
|
||
<trade_type>{trade_type}</trade_type>
|
||
<sign>{sign}</sign>
|
||
</xml>"""
|
||
|
||
# 调用微信统一下单接口(官方地址)
|
||
response = requests.post(
|
||
'https://api.mch.weixin.qq.com/pay/unifiedorder',
|
||
data=xml_data.encode('utf-8'),
|
||
headers={'Content-Type': 'application/xml'},
|
||
timeout=10 # 设置超时
|
||
)
|
||
|
||
# 解析返回的XML
|
||
root = ET.fromstring(response.content)
|
||
|
||
return_code = root.find('return_code').text
|
||
result_code = root.find('result_code').text
|
||
|
||
if return_code == 'SUCCESS' and result_code == 'SUCCESS':
|
||
# 获取prepay_id
|
||
prepay_id = root.find('prepay_id').text
|
||
|
||
# 2. 第二次签名(小程序支付参数)
|
||
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'
|
||
|
||
# 生成支付签名(注意:这里key的大小写和字段名与第一次不同)
|
||
pay_sign_data = {
|
||
'appId': APPID, # 注意:这里是appId,不是appid
|
||
'timeStamp': timestamp, # 注意:这里是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, # 对应前端:appId
|
||
'timeStamp': timestamp, # 对应前端:timeStamp
|
||
'nonceStr': nonce_str, # 对应前端:nonceStr
|
||
'package': package, # 对应前端:package
|
||
'signType': sign_type, # 对应前端:signType
|
||
'paySign': pay_sign # 对应前端:paySign
|
||
}
|
||
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 err_code is not None:
|
||
error_details += f", err_code: {err_code.text}"
|
||
if err_code_des is not None:
|
||
error_details += f", err_code_des: {err_code_des.text}"
|
||
if return_msg is not None:
|
||
error_details += f", return_msg: {return_msg.text}"
|
||
|
||
raise Exception(f"微信支付下单失败: {error_details}")
|
||
|
||
def get_client_ip(self):
|
||
"""获取客户端IP(从DRF的request对象中获取)"""
|
||
request = self.request
|
||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
||
if x_forwarded_for:
|
||
ip = x_forwarded_for.split(',')[0]
|
||
else:
|
||
ip = request.META.get('REMOTE_ADDR')
|
||
return ip if ip else '127.0.0.1'
|
||
|
||
|
||
|
||
|
||
|
||
class YajinHuitiao(View):
|
||
"""
|
||
押金支付回调接口
|
||
路径:/shangpin/yajinhuitiao
|
||
注意:这个接口不需要JWT认证,由微信服务器调用
|
||
"""
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 解析微信回调XML数据
|
||
request_body = request.body
|
||
logger.info(f"收到微信支付回调,原始数据: {request_body.decode('utf-8')}")
|
||
|
||
root = ET.fromstring(request_body)
|
||
|
||
# 获取关键字段
|
||
return_code = root.find('return_code').text
|
||
result_code = root.find('result_code').text
|
||
out_trade_no = root.find('out_trade_no').text # 订单ID
|
||
transaction_id = root.find('transaction_id').text # 微信交易号
|
||
total_fee = int(root.find('total_fee').text) # 支付金额(分)
|
||
|
||
logger.info(f"回调数据解析: 订单{out_trade_no}, 状态{return_code}/{result_code}, 金额{total_fee}分")
|
||
|
||
from jituan.services.wechat_pay import club_id_from_czjilu, verify_wechat_v2_signature
|
||
notify_club = club_id_from_czjilu(out_trade_no)
|
||
|
||
# 2. 验证基本返回状态
|
||
if return_code != 'SUCCESS' or result_code != 'SUCCESS':
|
||
logger.warning(f"微信支付回调状态异常: {return_code}/{result_code}")
|
||
return self.wechat_response('FAIL', '支付失败')
|
||
|
||
# 3. 验证签名(确保是微信发来的)
|
||
if not verify_wechat_v2_signature(request_body, notify_club):
|
||
logger.error(f"签名验证失败: {out_trade_no}")
|
||
return self.wechat_response('FAIL', '签名验证失败')
|
||
|
||
# 4. 查询订单
|
||
try:
|
||
order = Czjilu.query.get(dingdan_id=out_trade_no)
|
||
logger.info(f"找到订单: {order.dingdan_id}, 状态: {order.zhuangtai}, 金额: {order.jine}")
|
||
except Czjilu.DoesNotExist:
|
||
logger.error(f"订单不存在: {out_trade_no}")
|
||
return self.wechat_response('FAIL', '订单不存在')
|
||
|
||
# 5. 检查订单状态是否为未支付(9)
|
||
if order.zhuangtai != 9:
|
||
logger.warning(f"订单状态不是未支付: {order.dingdan_id}, 当前状态: {order.zhuangtai}")
|
||
try:
|
||
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
|
||
repair_wechat_income_if_missing(
|
||
Decimal(str(order.jine)),
|
||
getattr(order, 'club_id', None),
|
||
biz_ref=f'cz:{out_trade_no}',
|
||
)
|
||
except Exception as exc:
|
||
logger.error('押金回调补记收支失败 %s: %s', out_trade_no, exc, exc_info=True)
|
||
return self.wechat_response('SUCCESS', 'OK')
|
||
|
||
# 6. 验证金额(微信返回的是分,需要转换)
|
||
wechat_amount_yuan = total_fee / 100 # 转换为元
|
||
order_amount_yuan = float(order.jine) # 订单金额(元)
|
||
|
||
# 允许1分钱的误差(浮点数比较)
|
||
if abs(wechat_amount_yuan - order_amount_yuan) > 0.01:
|
||
logger.error(f"金额不一致: 订单金额{order_amount_yuan}元, 支付金额{wechat_amount_yuan}元")
|
||
return self.wechat_response('FAIL', '金额不一致')
|
||
|
||
from jituan.services.czjilu_fulfill import fulfill_yajin_recharge, CzjiluFulfillError
|
||
try:
|
||
fulfill_yajin_recharge(out_trade_no, paid_amount_yuan=wechat_amount_yuan)
|
||
except CzjiluFulfillError as exc:
|
||
logger.error('押金回调履约失败 %s: %s', out_trade_no, exc)
|
||
return self.wechat_response('FAIL', str(exc))
|
||
|
||
logger.info(f"押金支付回调处理完成: {out_trade_no}")
|
||
return self.wechat_response('SUCCESS', 'OK')
|
||
|
||
except ET.ParseError as e:
|
||
logger.error(f"XML解析失败: {str(e)}")
|
||
return self.wechat_response('FAIL', '数据格式错误')
|
||
except Exception as e:
|
||
logger.error(f"押金支付回调处理异常: {str(e)}", exc_info=True)
|
||
# 返回FAIL让微信重试
|
||
return self.wechat_response('FAIL', '处理异常')
|
||
|
||
def verify_signature(self, xml_data):
|
||
"""验证微信支付签名"""
|
||
try:
|
||
root = ET.fromstring(xml_data)
|
||
sign = root.find('sign').text
|
||
|
||
if not sign:
|
||
logger.error("签名字段不存在")
|
||
return False
|
||
|
||
# 获取所有参数并排除sign
|
||
params = {}
|
||
for child in root:
|
||
if child.tag != 'sign' and child.text is not None:
|
||
params[child.tag] = child.text
|
||
|
||
# 按照参数名ASCII码从小到大排序
|
||
sorted_params = sorted(params.items(), key=lambda x: x[0])
|
||
|
||
# 拼接字符串
|
||
sign_string = '&'.join([f"{key}={value}" for key, value in sorted_params if value])
|
||
sign_string += f'&key={settings.WEIXIN_SHANGHUMIYAO}'
|
||
|
||
# 计算签名
|
||
calculated_sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest().upper()
|
||
|
||
is_valid = sign == calculated_sign
|
||
if not is_valid:
|
||
logger.error(f"签名验证失败: 计算签名{calculated_sign}, 收到签名{sign}")
|
||
|
||
return is_valid
|
||
|
||
except Exception as e:
|
||
logger.error(f"验证签名异常: {str(e)}")
|
||
return False
|
||
|
||
def wechat_response(self, return_code, return_msg):
|
||
"""返回微信支付回调响应"""
|
||
xml_response = f"""<xml>
|
||
<return_code><![CDATA[{return_code}]]></return_code>
|
||
<return_msg><![CDATA[{return_msg}]]></return_msg>
|
||
</xml>"""
|
||
|
||
logger.info(f"返回微信响应: {return_code} - {return_msg}")
|
||
return HttpResponse(xml_response, content_type='application/xml')
|
||
|
||
|
||
|
||
|
||
# views.py
|
||
class YajinShibai(APIView):
|
||
"""
|
||
押金支付失败处理接口
|
||
路径:/shangpin/yajinshibai
|
||
请求方式:POST
|
||
权限:JWT认证
|
||
参数:dingdanid(订单ID)
|
||
返回:删除结果
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 获取订单ID
|
||
dingdanid = request.data.get('dingdanid')
|
||
if not dingdanid:
|
||
return Response({
|
||
'code': 400,
|
||
'message': '订单ID不能为空'
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 2. 查询订单(确保是当前用户的订单)
|
||
try:
|
||
order = Czjilu.query.get(
|
||
dingdan_id=dingdanid,
|
||
yonghuid=request.user.UserUID
|
||
)
|
||
except Czjilu.DoesNotExist:
|
||
return Response({
|
||
'code': 400,
|
||
'message': '订单不存在或不属于当前用户'
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 3. 检查订单状态是否为未支付(9):关单不删
|
||
if order.zhuangtai == 9:
|
||
from products.czjilu_types import close_unpaid_czjilu
|
||
close_unpaid_czjilu(order, reason='用户取消支付')
|
||
logger.info(f"用户{request.user.UserUID}关闭未支付押金订单(不删): {dingdanid}")
|
||
|
||
return Response({
|
||
'code': 200,
|
||
'message': '订单已取消'
|
||
}, status=status.HTTP_200_OK)
|
||
else:
|
||
return Response({
|
||
'code': 400,
|
||
'message': '订单状态不是未支付,无法取消'
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
except Exception as e:
|
||
logger.error(f"押金支付失败处理异常: {str(e)}", exc_info=True)
|
||
return Response({
|
||
'code': 500,
|
||
'message': '服务器内部错误'
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
# views.py
|
||
class YajinLunxun(APIView):
|
||
"""
|
||
押金支付成功轮询接口
|
||
路径:/shangpin/yajinlunxun
|
||
请求方式:POST
|
||
权限:JWT认证
|
||
参数:dingdanid(订单ID)
|
||
返回:订单状态和当前押金
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 获取订单ID
|
||
dingdanid = request.data.get('dingdanid')
|
||
if not dingdanid:
|
||
return Response({
|
||
'code': 400,
|
||
'message': '订单ID不能为空'
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 2. 查询订单(确保是当前用户的订单)
|
||
try:
|
||
order = Czjilu.query.get(
|
||
dingdan_id=dingdanid,
|
||
yonghuid=request.user.UserUID
|
||
)
|
||
except Czjilu.DoesNotExist:
|
||
return Response({
|
||
'code': 400,
|
||
'message': '订单不存在或不属于当前用户'
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 3. 未支付/已关单则查官方并幂等履约
|
||
from products.czjilu_types import is_czjilu_fulfillable_status
|
||
if is_czjilu_fulfillable_status(order.zhuangtai):
|
||
from jituan.services.czjilu_fulfill import confirm_czjilu_paid, CzjiluFulfillError
|
||
try:
|
||
confirm_czjilu_paid(dingdanid, request.user.UserUID, expected_leixing=2)
|
||
order = Czjilu.query.get(dingdan_id=dingdanid, yonghuid=request.user.UserUID)
|
||
except CzjiluFulfillError as exc:
|
||
logger.info(f"押金确认未就绪 {dingdanid}: {exc}")
|
||
return Response({
|
||
'code': 400,
|
||
'message': str(exc),
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if order.zhuangtai != 3:
|
||
logger.info(f"订单{dingdanid}尚未支付完成,当前状态: {order.zhuangtai}")
|
||
return Response({
|
||
'code': 400,
|
||
'message': '订单尚未支付完成'
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 4. 获取打手押金(通过反向查询,最快捷的方式)
|
||
try:
|
||
dashou = request.user.DashouProfile
|
||
yajin = float(dashou.yajin) # 转为浮点数方便前端处理
|
||
|
||
logger.info(f"轮询成功: 订单{dingdanid}已支付,用户{request.user.UserUID}押金为{yajin}元")
|
||
|
||
return Response({
|
||
'code': 200,
|
||
'message': '支付流程全部完成',
|
||
'yajin': yajin # 返回当前押金(前端需要更新全局变量)
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except UserDashou.DoesNotExist:
|
||
logger.error(f"打手信息不存在: 用户{request.user.UserUID}")
|
||
return Response({
|
||
'code': 400,
|
||
'message': '打手信息不存在'
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
except Exception as e:
|
||
logger.error(f"押金轮询异常: {str(e)}", exc_info=True)
|
||
return Response({
|
||
'code': 500,
|
||
'message': '服务器内部错误'
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
# views.py
|
||
|
||
|