9342 lines
377 KiB
Python
9342 lines
377 KiB
Python
import os
|
||
from django.db.models import F
|
||
from django.shortcuts import render
|
||
from rest_framework.permissions import AllowAny # 新增这行
|
||
|
||
from dingdan.utils import sync_order_to_partners
|
||
|
||
from rest_framework.throttling import AnonRateThrottle
|
||
# dingdan/views.py
|
||
import time
|
||
import random
|
||
import hashlib
|
||
import xml.etree.ElementTree as ET
|
||
import requests
|
||
import uuid
|
||
from decimal import Decimal
|
||
from django.db import transaction
|
||
from django.conf import settings
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework import permissions, status
|
||
from tencentcloud.common.profile.client_profile import ClientProfile
|
||
from tencentcloud.common.profile.http_profile import HttpProfile
|
||
|
||
from shangpin.models import Shangpin, ShangpinLeixing, Huiyuangoumai
|
||
from dingdan.models import Dingdan, DingdanPingtai, Lilubiao, Pingfen, DingdanShangjia, Chufajilu, Liaotian
|
||
from yonghu.models import UserMain, UserDashou, UserShangjia
|
||
import os
|
||
import traceback
|
||
|
||
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework import permissions, status
|
||
from tencentcloud.common.profile.client_profile import ClientProfile
|
||
from tencentcloud.common.profile.http_profile import HttpProfile
|
||
|
||
from shangpin.models import Shangpin, ShangpinLeixing, Huiyuangoumai
|
||
from dingdan.models import Dingdan, DingdanPingtai, Lilubiao, Pingfen, DingdanShangjia, Chufajilu, Liaotian, Chufatupian
|
||
|
||
from yonghu.models import UserMain, UserDashou, UserShangjia
|
||
|
||
|
||
|
||
# dingdan/views.py
|
||
import xml.etree.ElementTree as ET
|
||
from django.db import transaction
|
||
from django.http import HttpResponse
|
||
from rest_framework.views import APIView
|
||
from rest_framework import permissions
|
||
|
||
from shangpin.models import Shangpin
|
||
|
||
from yonghu.models import UserMain, UserBoss
|
||
|
||
# views/pay_views.py - WechatPayNotifyView完整代码
|
||
import xml.etree.ElementTree as ET
|
||
from django.db import transaction
|
||
from django.http import HttpResponse
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework import status
|
||
from rest_framework.throttling import AnonRateThrottle
|
||
from rest_framework.permissions import AllowAny
|
||
|
||
from peizhi.models import Szjilu
|
||
import logging
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class CreateOrderView(APIView):
|
||
"""
|
||
创建订单并调用微信支付接口
|
||
"""
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 获取前端传递的字段
|
||
shangpin_id = request.data.get('shangpin_id')
|
||
nicheng = request.data.get('nicheng', '').strip()
|
||
beizhu = request.data.get('beizhu', '').strip()
|
||
zhiding = request.data.get('zhiding', '').strip()
|
||
jine = request.data.get('jine', 0)
|
||
|
||
# 4.0 临时关闭指定功能(如需开启,请删除或注释掉下面两行)
|
||
#if zhiding:
|
||
#return Response({'code': 8, 'msg': '指定功能尚未开启,请不要填写指定打手ID', 'data': None})
|
||
|
||
# 2. 验证必填字段
|
||
if not shangpin_id:
|
||
return Response({'code': 1, 'msg': '商品ID不能为空', 'data': None})
|
||
|
||
if not nicheng:
|
||
return Response({'code': 2, 'msg': '游戏昵称不能为空', 'data': None})
|
||
|
||
if len(nicheng) > 50:
|
||
return Response({'code': 3, 'msg': '游戏昵称过长', 'data': None})
|
||
|
||
# 3. 查询商品信息
|
||
shangpin = Shangpin.objects.filter(id=shangpin_id).first()
|
||
if not shangpin:
|
||
return Response({'code': 4, 'msg': '商品不存在', 'data': None})
|
||
|
||
# 检查库存
|
||
if hasattr(shangpin, 'kucun') and shangpin.kucun <= 0:
|
||
return Response({'code': 5, 'msg': '商品库存不足', 'data': None})
|
||
|
||
# 获取商品价格
|
||
shangpin_jiage = getattr(shangpin, 'jiage', 0)
|
||
if not shangpin_jiage:
|
||
return Response({'code': 6, 'msg': '商品价格异常', 'data': None})
|
||
#获取商品图片
|
||
shangpin_tupian = getattr(shangpin, 'tupian_url', None)
|
||
|
||
# 4. 验证指定打手(如果存在)
|
||
zhiding_id_value = None
|
||
if zhiding:
|
||
# 查询主表中是否存在该打手
|
||
zhiding_user = UserMain.objects.filter(
|
||
yonghuid=zhiding,
|
||
#user_type='dashou'
|
||
).first()
|
||
|
||
if not zhiding_user:
|
||
return Response({'code': 7, 'msg': '指定打手不存在', 'data': None})
|
||
|
||
# 查询打手扩展表
|
||
dashou_profile = UserDashou.objects.filter(user=zhiding_user).first()
|
||
|
||
if not dashou_profile:
|
||
return Response({'code': 8, 'msg': '指定用户不存在或已封禁', 'data': None})
|
||
|
||
# 检查在线状态
|
||
if getattr(dashou_profile, 'zhanghaozhuangtai', 0) == 0:
|
||
return Response({'code': 8, 'msg': '指定用户不存在或已封禁', 'data': None})
|
||
|
||
zhiding_id_value = zhiding
|
||
|
||
# 5. 生成订单ID
|
||
timestamp = int(time.time())
|
||
timestamp_ns = int(time.time_ns() // 1000) % 1000000
|
||
random_num = random.randint(0, 99)
|
||
dingdanid = f"PT{timestamp:011d}{timestamp_ns:06d}{random_num:02d}"
|
||
|
||
# 6. 查询利率并计算分成
|
||
# 查询打手利率(字符'1')
|
||
dashou_lilu = Lilubiao.objects.filter(fadanpingtai='1').first()
|
||
|
||
|
||
|
||
# 🔥 新增:判断是否使用商品配置的额外分成
|
||
if shangpin.kaioi_ewai_dashou_fencheng and shangpin.ewai_dashou_fencheng is not None:
|
||
# 使用商品指定的额外分成金额
|
||
dashou_fencheng = shangpin.ewai_dashou_fencheng
|
||
else:
|
||
# 按原有逻辑计算打手分成
|
||
dashou_fencheng = Decimal(str(shangpin_jiage)) * Decimal(str(dashou_lilu.lilu))
|
||
|
||
|
||
|
||
# 查询管事利率(字符'2')
|
||
#guanshi_lilu = Lilubiao.objects.filter(fadanpingtai='2').first()
|
||
|
||
if not dashou_lilu: #or not guanshi_lilu:
|
||
return Response({'code': 9, 'msg': '系统利率配置不完整', 'data': None})
|
||
|
||
# 计算打手分成
|
||
#dashou_fencheng = Decimal(str(shangpin_jiage)) * Decimal(str(dashou_lilu.lilu))
|
||
|
||
|
||
|
||
# 开始数据库事务
|
||
with transaction.atomic():
|
||
# 获取当前用户信息
|
||
current_user = request.user
|
||
laoban_yonghuid = getattr(current_user, 'yonghuid', '')
|
||
user_openid = getattr(current_user, 'openid', '')
|
||
|
||
if not user_openid:
|
||
return Response({'code': 10, 'msg': '用户openid不存在', 'data': None})
|
||
|
||
# 创建订单主表数据
|
||
order_data = {
|
||
'dingdan_id': dingdanid,
|
||
'zhuangtai': 9, # 未付款状态
|
||
'jine': shangpin_jiage,
|
||
'fadan_pingtai':1,
|
||
'dashou_fencheng': dashou_fencheng,
|
||
'zhiding_id': zhiding_id_value,
|
||
'shangpin_id': shangpin_id,
|
||
'yaoqiuleixing': getattr(shangpin, 'yaoqiuleixing', None),
|
||
'huiyuan_id': getattr(shangpin, 'huiyuan_id', ''),
|
||
'yongjin': getattr(shangpin, 'yongjin', 0),
|
||
'jieshao': getattr(shangpin, 'jieshao', ''),
|
||
'beizhu': beizhu,
|
||
'nicheng': nicheng,
|
||
'tupian':shangpin_tupian,
|
||
'user1_id':f"Boss{request.user.yonghuid}",
|
||
'leixing_id':shangpin.leixing_id
|
||
}
|
||
|
||
# 创建订单记录
|
||
dingdan = Dingdan.objects.create(**order_data)
|
||
|
||
# 创建订单平台扩展表
|
||
pingtaikuozhan_data = {
|
||
'dingdan': dingdan,
|
||
'laoban_id': laoban_yonghuid,
|
||
}
|
||
DingdanPingtai.objects.create(**pingtaikuozhan_data)
|
||
|
||
# 7. 生成微信支付参数(向微信官方发送请求)
|
||
pay_params = self.generate_wechat_pay_params(
|
||
dingdanid=dingdanid,
|
||
jine=shangpin_jiage,
|
||
beizhu=getattr(settings, 'WEIXIN_PAY_DESCRIPTION'),
|
||
user_ip=self.get_client_ip(request),
|
||
openid=user_openid
|
||
)
|
||
|
||
if not pay_params:
|
||
# 手动抛出异常触发事务回滚
|
||
raise Exception('支付参数生成失败')
|
||
|
||
# 8. 返回成功响应
|
||
response_data = {
|
||
'code': 0,
|
||
'msg': '订单创建成功',
|
||
'data': {
|
||
'dingdanid': dingdanid,
|
||
'payParams': pay_params
|
||
}
|
||
}
|
||
|
||
return Response(response_data)
|
||
|
||
except Exception as e:
|
||
# 这里会自动回滚事务
|
||
return Response({'code': 99, 'msg': f'系统错误: {str(e)}', 'data': None})
|
||
|
||
def get_client_ip(self, request):
|
||
"""获取客户端真实IP地址"""
|
||
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
|
||
|
||
def generate_wechat_pay_params(self, dingdanid, jine, beizhu, user_ip, openid):
|
||
"""
|
||
调用微信支付统一下单接口,生成支付参数
|
||
完全按照您提供的正确示例代码编写
|
||
"""
|
||
try:
|
||
# 微信支付配置
|
||
APPID = getattr(settings, 'WEIXIN_APPID', '')
|
||
MCHID = getattr(settings, 'WEIXIN_MCHID', '')
|
||
KEY = getattr(settings, 'WEIXIN_SHANGHUMIYAO', '')
|
||
NOTIFY_URL = getattr(settings, 'WEIXIN_NOTIFY_URL', '')
|
||
|
||
if not all([APPID, MCHID, KEY, NOTIFY_URL]):
|
||
raise Exception('微信支付配置不完整')
|
||
|
||
# 生成随机字符串
|
||
import string
|
||
nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32))
|
||
|
||
# 金额(单位:分)
|
||
total_fee = int(float(jine) * 100)
|
||
|
||
# 交易类型
|
||
trade_type = 'JSAPI'
|
||
|
||
# 生成签名
|
||
sign_data = {
|
||
'appid': APPID,
|
||
'mch_id': MCHID,
|
||
'nonce_str': nonce_str,
|
||
'body': beizhu,
|
||
'out_trade_no': dingdanid,
|
||
'total_fee': total_fee,
|
||
'spbill_create_ip': user_ip,
|
||
'notify_url': NOTIFY_URL,
|
||
'trade_type': trade_type,
|
||
'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>{beizhu}</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 = root.find('prepay_id').text
|
||
|
||
# 生成前端支付参数
|
||
timestamp = str(int(time.time()))
|
||
nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32))
|
||
package = f'prepay_id={prepay_id}'
|
||
|
||
# 生成支付签名
|
||
pay_sign_data = {
|
||
'appId': APPID,
|
||
'timeStamp': timestamp,
|
||
'nonceStr': nonce_str,
|
||
'package': package,
|
||
'signType': 'MD5'
|
||
}
|
||
|
||
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()
|
||
|
||
# 返回支付参数(格式与前端完全匹配)
|
||
result = {
|
||
'timeStamp': timestamp,
|
||
'nonceStr': nonce_str,
|
||
'package': package,
|
||
'signType': 'MD5',
|
||
'paySign': pay_sign
|
||
}
|
||
|
||
return result
|
||
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}")
|
||
|
||
except Exception as e:
|
||
raise e
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
class PaymentFailView(APIView):
|
||
"""
|
||
前端支付失败接口 - 处理用户主动取消或支付失败
|
||
只允许删除状态为9(未支付)的订单
|
||
"""
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
dingdanid = request.data.get('dingdanid')
|
||
if not dingdanid:
|
||
return Response({'code': 1, 'msg': '订单ID不能为空', 'data': None})
|
||
|
||
with transaction.atomic():
|
||
# 锁定订单防止并发
|
||
dingdan = Dingdan.objects.select_for_update().filter(dingdan_id=dingdanid).first()
|
||
if not dingdan:
|
||
return Response({'code': 2, 'msg': '订单不存在', 'data': None})
|
||
|
||
# 只允许删除未支付订单(状态9)
|
||
if dingdan.zhuangtai != 9:
|
||
return Response({'code': 3, 'msg': '只能删除未支付的订单', 'data': None})
|
||
|
||
# 删除扩展表和主表
|
||
DingdanPingtai.objects.filter(dingdan=dingdan).delete()
|
||
dingdan.delete()
|
||
|
||
return Response({'code': 0, 'msg': '订单已删除', 'data': None})
|
||
|
||
except Exception as e:
|
||
return Response({'code': 99, 'msg': f'删除订单失败: {str(e)}', 'data': None})
|
||
|
||
|
||
|
||
import string
|
||
import requests
|
||
import xmltodict
|
||
import logging
|
||
from decimal import Decimal
|
||
from django.db import transaction, models
|
||
from django.core.cache import cache
|
||
from django.conf import settings
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework import permissions, status
|
||
from rest_framework.parsers import JSONParser
|
||
|
||
from yonghu.models import UserMain, UserBoss
|
||
from shangpin.models import Shangpin
|
||
from dingdan.models import Dingdan, DingdanPingtai
|
||
from peizhi.models import Szjilu
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
|
||
|
||
|
||
from dingdan.utils import update_daily_income
|
||
import logging
|
||
import traceback
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
class WechatPayNotifyView(APIView):
|
||
"""微信支付回调接口 - 接收微信官方支付结果通知(含详细调试日志)"""
|
||
throttle_classes = [AnonRateThrottle]
|
||
permission_classes = [AllowAny]
|
||
|
||
def post(self, request):
|
||
# 记录原始请求数据(调试用)
|
||
xml_data = request.body.decode('utf-8')
|
||
|
||
# 最外层异常捕获,确保任何错误都能被记录并返回适当响应
|
||
try:
|
||
# 1. 解析 XML
|
||
try:
|
||
root = ET.fromstring(xml_data)
|
||
except ET.ParseError as e:
|
||
logger.error(f"XML解析失败: {e}")
|
||
return self._gen_response_xml('FAIL', 'XML解析失败')
|
||
|
||
# 2. 获取关键参数
|
||
return_code = root.find('return_code').text if root.find('return_code') is not None else ''
|
||
result_code = root.find('result_code').text if root.find('result_code') is not None else ''
|
||
out_trade_no = root.find('out_trade_no').text if root.find('out_trade_no') is not None else ''
|
||
transaction_id = root.find('transaction_id').text if root.find('transaction_id') is not None else ''
|
||
|
||
# 3. 签名验证
|
||
if not self._verify_signature(xml_data):
|
||
logger.warning("签名验证失败")
|
||
return self._gen_response_xml('FAIL', '签名验证失败')
|
||
|
||
# 4. 根据支付结果处理
|
||
if return_code == 'SUCCESS' and result_code == 'SUCCESS':
|
||
self._handle_success(out_trade_no, transaction_id, root)
|
||
else:
|
||
logger.info(f"支付失败或异常,return_code={return_code}, result_code={result_code}")
|
||
self._handle_failure(out_trade_no)
|
||
|
||
# 返回微信要求的成功响应(必须返回SUCCESS,即使内部处理失败也要返回,否则微信会重复通知)
|
||
return self._gen_response_xml('SUCCESS', 'OK')
|
||
|
||
except Exception as e:
|
||
# 捕获任何未处理的异常,记录完整堆栈
|
||
logger.error(f"回调处理发生未捕获异常: {str(e)}")
|
||
logger.error(traceback.format_exc())
|
||
return self._gen_response_xml('FAIL', f'服务器内部错误: {str(e)}')
|
||
|
||
def _verify_signature(self, xml_data):
|
||
"""微信支付签名验证(MD5)"""
|
||
try:
|
||
root = ET.fromstring(xml_data)
|
||
sign = root.find('sign').text
|
||
# 构建待签名字符串(排除sign字段)
|
||
data = {child.tag: child.text for child in root if child.tag != 'sign'}
|
||
# 按key排序
|
||
sorted_keys = sorted(data.keys())
|
||
stringA = '&'.join([f"{k}={data[k]}" for k in sorted_keys])
|
||
stringSignTemp = f"{stringA}&key={settings.WEIXIN_SHANGHUMIYAO}"
|
||
calc_sign = hashlib.md5(stringSignTemp.encode('utf-8')).hexdigest().upper()
|
||
logger.debug(f"计算签名: {calc_sign}, 微信签名: {sign}")
|
||
return calc_sign == sign
|
||
except Exception as e:
|
||
logger.error(f"签名验证过程异常: {e}")
|
||
logger.error(traceback.format_exc())
|
||
return False
|
||
|
||
def _gen_response_xml(self, return_code, return_msg):
|
||
"""生成返回给微信的XML响应"""
|
||
xml = f"""<xml>
|
||
<return_code><![CDATA[{return_code}]]></return_code>
|
||
<return_msg><![CDATA[{return_msg}]]></return_msg>
|
||
</xml>"""
|
||
logger.info(f"返回给微信的响应: {xml}")
|
||
return HttpResponse(xml, content_type='application/xml')
|
||
|
||
@transaction.atomic
|
||
def _handle_success(self, out_trade_no, transaction_id, root):
|
||
"""处理支付成功"""
|
||
logger.info(f"进入 _handle_success, out_trade_no={out_trade_no}, transaction_id={transaction_id}")
|
||
|
||
# 锁定订单防止并发
|
||
try:
|
||
dingdan = Dingdan.objects.select_for_update().filter(dingdan_id=out_trade_no).first()
|
||
except Exception as e:
|
||
logger.error(f"查询订单异常: {e}")
|
||
raise
|
||
|
||
if not dingdan:
|
||
logger.error(f"订单不存在: {out_trade_no}")
|
||
return
|
||
|
||
logger.info(f"订单当前状态: {dingdan.zhuangtai}")
|
||
|
||
# 幂等处理:只有状态为9(未支付)才处理,已处理则跳过
|
||
if dingdan.zhuangtai != 9:
|
||
logger.info(f"订单 {out_trade_no} 已处理过,状态={dingdan.zhuangtai},跳过")
|
||
return
|
||
|
||
# 保存微信交易号
|
||
if transaction_id:
|
||
dingdan.wechat_transaction_id = transaction_id
|
||
logger.info(f"保存微信交易号: {transaction_id}")
|
||
else:
|
||
logger.warning("微信未返回transaction_id")
|
||
|
||
# 更新订单状态
|
||
if dingdan.zhiding_id and dingdan.zhiding_id.strip():
|
||
dingdan.zhuangtai = 7
|
||
logger.info("指定单,状态设为7")
|
||
else:
|
||
dingdan.zhuangtai = 1
|
||
logger.info("普通单,状态设为1")
|
||
|
||
dingdan.save()
|
||
|
||
logger.info(f"订单状态已更新为 {dingdan.zhuangtai}")
|
||
|
||
# 删除同一用户同一商品的其他未支付订单(改为逻辑删除,状态25)
|
||
try:
|
||
abandoned_count = Dingdan.objects.filter(
|
||
user1_id=dingdan.user1_id,
|
||
shangpin_id=dingdan.shangpin_id,
|
||
zhuangtai=9
|
||
).exclude(dingdan_id=out_trade_no).update(zhuangtai=25)
|
||
logger.info(f"将 {abandoned_count} 个未支付订单标记为已废弃(25)")
|
||
except Exception as e:
|
||
logger.error(f"标记其他未支付订单异常: {e}")
|
||
|
||
# 更新老板扩展表
|
||
try:
|
||
laoban_id = None
|
||
if hasattr(dingdan, 'pingtai_kuozhan'):
|
||
laoban_id = dingdan.pingtai_kuozhan.laoban_id
|
||
if laoban_id:
|
||
user_main = UserMain.objects.filter(yonghuid=laoban_id).first()
|
||
if user_main and hasattr(user_main, 'boss_profile'):
|
||
boss = user_main.boss_profile
|
||
boss.zonge = (boss.zonge or 0) + (dingdan.jine or 0)
|
||
boss.alldingdan = (boss.alldingdan or 0) + 1
|
||
boss.save()
|
||
logger.info(f"更新老板 {laoban_id} 数据成功")
|
||
except Exception as e:
|
||
logger.error(f"更新老板数据异常: {e}")
|
||
|
||
# 更新商品库存
|
||
try:
|
||
if dingdan.shangpin_id:
|
||
Shangpin.objects.filter(id=dingdan.shangpin_id).update(
|
||
kucun=F('kucun') - 1
|
||
)
|
||
logger.info(f"商品 {dingdan.shangpin_id} 库存减1")
|
||
except Exception as e:
|
||
logger.error(f"更新商品库存异常: {e}")
|
||
|
||
# 更新收支记录(总累计)
|
||
self._update_szjilu(dingdan.jine)
|
||
|
||
# 更新每日收入统计
|
||
update_daily_income(dingdan.jine)
|
||
|
||
# 跨平台同步
|
||
sync_order_to_partners(dingdan.dingdan_id)
|
||
|
||
# 异步广播通知
|
||
self._broadcast_async(dingdan)
|
||
|
||
logger.info(f"订单 {out_trade_no} 支付成功处理完成")
|
||
|
||
@transaction.atomic
|
||
def _handle_failure(self, out_trade_no):
|
||
"""处理支付失败"""
|
||
logger.info(f"进入 _handle_failure, out_trade_no={out_trade_no}")
|
||
|
||
try:
|
||
dingdan = Dingdan.objects.select_for_update().filter(dingdan_id=out_trade_no).first()
|
||
if not dingdan:
|
||
logger.warning(f"订单不存在: {out_trade_no}")
|
||
return
|
||
|
||
logger.info(f"订单当前状态: {dingdan.zhuangtai}")
|
||
|
||
# 仅当订单状态为9(未支付)时才标记失败,不直接删除
|
||
if dingdan.zhuangtai == 9:
|
||
dingdan.zhuangtai = 14 # 自定义支付失败状态
|
||
dingdan.save()
|
||
logger.info(f"订单 {out_trade_no} 支付失败,标记为14")
|
||
else:
|
||
logger.info(f"订单 {out_trade_no} 状态非9,不处理")
|
||
except Exception as e:
|
||
logger.error(f"处理支付失败异常: {e}")
|
||
logger.error(traceback.format_exc())
|
||
|
||
def _update_szjilu(self, jine):
|
||
"""更新收支记录表(ID=1的记录)"""
|
||
try:
|
||
jine_decimal = Decimal(str(jine))
|
||
with transaction.atomic():
|
||
szjilu, created = Szjilu.objects.select_for_update().get_or_create(
|
||
id=1,
|
||
defaults={
|
||
'zongsy': Decimal('0.00'),
|
||
'zongls': Decimal('0.00'),
|
||
'zongzc': Decimal('0.00'),
|
||
'jrzc': Decimal('0.00'),
|
||
'jrls': Decimal('0.00')
|
||
}
|
||
)
|
||
if created:
|
||
logger.info("创建ID=1的收支记录")
|
||
szjilu.zongsy += jine_decimal
|
||
szjilu.zongls += jine_decimal
|
||
szjilu.jrls += jine_decimal
|
||
szjilu.save()
|
||
logger.info(f"收支记录更新成功,增加金额: {jine}")
|
||
except Exception as e:
|
||
logger.error(f"更新收支记录失败: {e}")
|
||
|
||
def _broadcast_async(self, dingdan):
|
||
"""异步广播通知"""
|
||
try:
|
||
def task():
|
||
try:
|
||
from . import WeixinBroadcastSender # 请根据实际路径修改
|
||
order_info = {
|
||
'dingdan_id': dingdan.dingdan_id,
|
||
'game_type': self._get_game_type_name(dingdan.leixing_id),
|
||
'amount': str(dingdan.jine),
|
||
'order_desc': (dingdan.jieshao or '')[:50],
|
||
}
|
||
logger.info(f"准备广播订单: {order_info}")
|
||
sender = WeixinBroadcastSender()
|
||
result = sender.broadcast_order(order_info)
|
||
if result.get('success'):
|
||
logger.info(f"订单广播完成: {result.get('msg')}")
|
||
else:
|
||
logger.warning(f"订单广播异常: {result.get('msg')}")
|
||
except Exception as e:
|
||
logger.error(f"广播任务异常: {e}")
|
||
logger.error(traceback.format_exc())
|
||
|
||
thread = threading.Thread(target=task)
|
||
thread.daemon = True
|
||
thread.start()
|
||
logger.info("广播线程已启动")
|
||
except Exception as e:
|
||
logger.error(f"启动广播线程异常: {e}")
|
||
|
||
def _get_game_type_name(self, leixing_id):
|
||
try:
|
||
from shangpin.models import ShangpinLeixing
|
||
gt = ShangpinLeixing.objects.filter(id=leixing_id).first()
|
||
return gt.jieshao if gt else '游戏订单'
|
||
except Exception as e:
|
||
logger.error(f"获取游戏类型名称异常: {e}")
|
||
return '游戏订单'
|
||
|
||
|
||
|
||
|
||
from django.core.cache import cache
|
||
class PaymentVerifyView(APIView):
|
||
"""
|
||
订单支付成功复查接口 - 前端支付成功后调用,确认订单状态
|
||
当订单状态为9时,主动查询微信支付状态,若已支付则更新本地状态
|
||
需要token认证
|
||
"""
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# ---------- 1. 获取参数 ----------
|
||
dingdanid = request.data.get('dingdanid')
|
||
if not dingdanid:
|
||
return Response({'code': 1, 'msg': '订单ID不能为空'})
|
||
|
||
# ---------- 2. 查询订单 ----------
|
||
dingdan = Dingdan.objects.filter(dingdan_id=dingdanid).first()
|
||
if not dingdan:
|
||
return Response({'code': 2, 'msg': '订单不存在'})
|
||
|
||
# ---------- 3. 验证订单归属(安全) ----------
|
||
current_user = request.user
|
||
user_identifier = f"Boss{current_user.yonghuid}"
|
||
if dingdan.user1_id != user_identifier:
|
||
logger.warning(f"用户 {current_user.yonghuid} 尝试复查他人订单 {dingdanid}")
|
||
return Response({'code': 403, 'msg': '无权访问'})
|
||
|
||
# ---------- 4. 如果订单状态已是1/7,直接返回成功 ----------
|
||
if dingdan.zhuangtai in [1, 7]:
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '订单状态正常',
|
||
'data': {'dingdanid': dingdanid, 'zhuangtai': dingdan.zhuangtai}
|
||
})
|
||
|
||
# ---------- 5. 如果状态为9,则主动查询微信支付状态 ----------
|
||
if dingdan.zhuangtai == 9:
|
||
# 使用分布式锁防止并发处理同一订单
|
||
lock_key = f"payment_verify_lock_{dingdanid}"
|
||
if not cache.add(lock_key, "locked", timeout=10):
|
||
# 已有其他进程在处理,让前端稍后重试
|
||
return Response({'code': 1, 'msg': '订单正在处理中,请稍后'})
|
||
|
||
try:
|
||
# 调用微信订单查询接口
|
||
trade_state, transaction_id = self._query_wechat_payment(dingdanid)
|
||
|
||
if trade_state == 'SUCCESS':
|
||
# 支付成功,执行本地更新
|
||
self._process_payment_success(dingdan, transaction_id, 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:
|
||
# 其他状态:CLOSED, REVOKED, PAYERROR 等,视为支付失败
|
||
return Response({'code': 2, 'msg': '支付失败'})
|
||
|
||
except Exception as e:
|
||
logger.error(f"查询微信支付异常: {e}", exc_info=True)
|
||
return Response({'code': 1, 'msg': '查询失败,请稍后重试'})
|
||
finally:
|
||
cache.delete(lock_key)
|
||
else:
|
||
# ---------- 6. 其他异常状态 ----------
|
||
status_msg = {
|
||
2: '订单已取消',
|
||
3: '订单已完成',
|
||
4: '退款审核中',
|
||
5: '已退款',
|
||
6: '退款失败',
|
||
8: '结算中',
|
||
0: '订单已作废',
|
||
}.get(dingdan.zhuangtai, '订单状态异常')
|
||
return Response({'code': 3, 'msg': status_msg, 'data': {'zhuangtai': dingdan.zhuangtai}})
|
||
|
||
except Exception as e:
|
||
logger.error(f"订单复查接口异常: {e}", exc_info=True)
|
||
return Response({'code': 99, 'msg': f'系统错误: {str(e)}'})
|
||
|
||
def _process_payment_success(self, dingdan, transaction_id, operator_id):
|
||
"""
|
||
支付成功后的原子操作(与微信回调逻辑完全一致)
|
||
在事务内完成所有状态更新和数据一致性操作
|
||
"""
|
||
with transaction.atomic():
|
||
# 重新锁定订单,防止并发更新
|
||
dingdan = Dingdan.objects.select_for_update().get(pk=dingdan.pk)
|
||
if dingdan.zhuangtai != 9:
|
||
# 订单状态已被其他进程更新,直接返回(幂等)
|
||
return
|
||
|
||
# 更新订单状态
|
||
dingdan.zhuangtai = 7 if dingdan.zhiding_id and dingdan.zhiding_id.strip() else 1
|
||
dingdan.wechat_transaction_id = transaction_id
|
||
dingdan.clkf = operator_id # 可选:记录操作人ID
|
||
dingdan.save()
|
||
|
||
# 删除同一用户同一商品的其他未支付订单(逻辑删除,状态25)
|
||
Dingdan.objects.filter(
|
||
user1_id=dingdan.user1_id,
|
||
shangpin_id=dingdan.shangpin_id,
|
||
zhuangtai=9
|
||
).exclude(dingdan_id=dingdan.dingdan_id).update(zhuangtai=25)
|
||
|
||
# 更新老板扩展表
|
||
laoban_id = dingdan.pingtai_kuozhan.laoban_id if hasattr(dingdan, 'pingtai_kuozhan') else None
|
||
if laoban_id:
|
||
user_main = UserMain.objects.filter(yonghuid=laoban_id).first()
|
||
if user_main and hasattr(user_main, 'boss_profile'):
|
||
boss = user_main.boss_profile
|
||
boss.zonge = (boss.zonge or 0) + (dingdan.jine or 0)
|
||
boss.alldingdan = (boss.alldingdan or 0) + 1
|
||
boss.save()
|
||
|
||
# 更新商品库存
|
||
if dingdan.shangpin_id:
|
||
Shangpin.objects.filter(id=dingdan.shangpin_id).update(
|
||
kucun=F('kucun') - 1
|
||
)
|
||
|
||
# 更新收支记录
|
||
self._update_szjilu(dingdan.jine)
|
||
|
||
# 更新每日收入统计
|
||
update_daily_income(dingdan.jine)
|
||
|
||
# 跨平台同步(复查成功也需要通知)
|
||
sync_order_to_partners(dingdan.dingdan_id)
|
||
|
||
def _query_wechat_payment(self, out_trade_no):
|
||
"""
|
||
调用微信支付订单查询接口,返回 (trade_state, transaction_id)
|
||
若查询失败,抛出异常
|
||
"""
|
||
appid = settings.WEIXIN_APPID
|
||
mch_id = settings.WEIXIN_MCHID
|
||
key = settings.WEIXIN_SHANGHUMIYAO
|
||
nonce_str = self._generate_nonce_str()
|
||
|
||
params = {
|
||
'appid': appid,
|
||
'mch_id': mch_id,
|
||
'out_trade_no': out_trade_no,
|
||
'nonce_str': nonce_str,
|
||
}
|
||
|
||
# 生成签名
|
||
sorted_keys = sorted(params.keys())
|
||
stringA = '&'.join([f"{k}={params[k]}" for k in sorted_keys])
|
||
stringSignTemp = f"{stringA}&key={key}"
|
||
sign = hashlib.md5(stringSignTemp.encode('utf-8')).hexdigest().upper()
|
||
params['sign'] = sign
|
||
|
||
xml_data = self._dict_to_xml(params)
|
||
url = 'https://api.mch.weixin.qq.com/pay/orderquery'
|
||
response = requests.post(url, data=xml_data, timeout=10)
|
||
result = xmltodict.parse(response.content)['xml']
|
||
|
||
if result.get('return_code') == 'SUCCESS' and result.get('result_code') == 'SUCCESS':
|
||
return result.get('trade_state'), result.get('transaction_id')
|
||
else:
|
||
err_msg = result.get('return_msg', '未知错误')
|
||
raise Exception(f"微信订单查询失败: {err_msg}")
|
||
|
||
def _update_szjilu(self, jine):
|
||
"""更新收支记录表(ID=1的记录)"""
|
||
try:
|
||
jine_decimal = Decimal(str(jine))
|
||
with transaction.atomic():
|
||
szjilu, created = Szjilu.objects.select_for_update().get_or_create(
|
||
id=1,
|
||
defaults={
|
||
'zongsy': Decimal('0.00'),
|
||
'zongls': Decimal('0.00'),
|
||
'zongzc': Decimal('0.00'),
|
||
'jrzc': Decimal('0.00'),
|
||
'jrls': Decimal('0.00')
|
||
}
|
||
)
|
||
if created:
|
||
logger.info("创建ID=1的收支记录")
|
||
szjilu.zongsy += jine_decimal
|
||
szjilu.zongls += jine_decimal
|
||
szjilu.jrls += jine_decimal
|
||
szjilu.save()
|
||
logger.info(f"收支记录更新成功,增加金额: {jine}")
|
||
except Exception as e:
|
||
logger.error(f"更新收支记录失败: {e}")
|
||
# 不抛出异常,因为收支记录不是核心流程,但记录错误供排查
|
||
|
||
def _generate_nonce_str(self):
|
||
"""生成随机字符串,长度32"""
|
||
return ''.join(random.choices(string.ascii_letters + string.digits, k=32))
|
||
|
||
def _dict_to_xml(self, data):
|
||
"""将字典转换为XML字符串"""
|
||
xml = ['<xml>']
|
||
for k, v in data.items():
|
||
xml.append(f'<{k}>{v}</{k}>')
|
||
xml.append('</xml>')
|
||
return ''.join(xml)
|
||
|
||
|
||
|
||
|
||
"""
|
||
虚拟支付视图(代币模式)
|
||
session_key 缓存策略:
|
||
- 优先从 UserMain 表读取
|
||
- 不存在或即将过期时,用前端传来的 code 换取
|
||
- 换取成功后存入 UserMain.session_key,其他接口直接从用户实例取
|
||
业务逻辑与原有商户支付完全一致,仅支付参数生成/查单方式替换为虚拟支付 API
|
||
"""
|
||
import json
|
||
import hmac
|
||
import hashlib
|
||
import logging
|
||
import time
|
||
import random
|
||
from decimal import Decimal
|
||
|
||
import requests
|
||
from django.conf import settings
|
||
from django.db import transaction
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework import permissions
|
||
from rest_framework.parsers import JSONParser
|
||
from django.http import HttpResponse
|
||
import xmltodict
|
||
import threading
|
||
|
||
from .models import Dingdan, DingdanPingtai, Lilubiao
|
||
from shangpin.models import Shangpin
|
||
from yonghu.models import UserMain, UserDashou, UserBoss
|
||
from peizhi.models import Szjilu
|
||
from .utils import update_daily_income, sync_order_to_partners
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
class VirtualCreateOrderView(APIView):
|
||
"""
|
||
虚拟代币支付创建订单
|
||
流程与原 CreateOrderView 完全一致
|
||
session_key 管理(已修改为强制刷新):
|
||
每次下单必须携带 code,用 code 换取最新 session_key 并存入 UserMain 表
|
||
不再信任数据库中缓存的旧 session_key
|
||
"""
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 获取前端参数
|
||
shangpin_id = request.data.get('shangpin_id')
|
||
nicheng = request.data.get('nicheng', '').strip()
|
||
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
|
||
|
||
# 2. 指定功能临时关闭(与原逻辑一致)
|
||
if zhiding:
|
||
return Response({'code': 8, 'msg': '指定功能尚未开启,请不要填写指定打手ID', 'data': None})
|
||
|
||
# 3. 必填字段验证
|
||
if not shangpin_id:
|
||
return Response({'code': 1, 'msg': '商品ID不能为空', 'data': None})
|
||
if not nicheng:
|
||
return Response({'code': 2, 'msg': '游戏昵称不能为空', 'data': None})
|
||
if len(nicheng) > 50:
|
||
return Response({'code': 3, 'msg': '游戏昵称过长', 'data': None})
|
||
|
||
# 4. 查询商品
|
||
shangpin = Shangpin.objects.filter(id=shangpin_id).first()
|
||
if not shangpin:
|
||
return Response({'code': 4, 'msg': '商品不存在', 'data': None})
|
||
if hasattr(shangpin, 'kucun') and shangpin.kucun <= 0:
|
||
return Response({'code': 5, 'msg': '商品库存不足', 'data': None})
|
||
|
||
shangpin_jiage = getattr(shangpin, 'jiage', 0)
|
||
if not shangpin_jiage:
|
||
return Response({'code': 6, 'msg': '商品价格异常', 'data': None})
|
||
shangpin_tupian = getattr(shangpin, 'tupian_url', None)
|
||
|
||
# 5. 验证指定打手(保留以备后续开启)
|
||
zhiding_id_value = None
|
||
if zhiding:
|
||
zhiding_user = UserMain.objects.filter(yonghuid=zhiding).first()
|
||
if not zhiding_user:
|
||
return Response({'code': 7, 'msg': '指定打手不存在', 'data': None})
|
||
dashou_profile = UserDashou.objects.filter(user=zhiding_user).first()
|
||
if not dashou_profile:
|
||
return Response({'code': 8, 'msg': '指定用户不存在或已封禁', 'data': None})
|
||
if getattr(dashou_profile, 'zhanghaozhuangtai', 0) == 0:
|
||
return Response({'code': 8, 'msg': '指定用户不存在或已封禁', 'data': None})
|
||
zhiding_id_value = zhiding
|
||
|
||
# 6. 生成订单ID
|
||
timestamp = int(time.time())
|
||
timestamp_ns = int(time.time_ns() // 1000) % 1000000
|
||
random_num = random.randint(0, 99)
|
||
dingdanid = f"PT{timestamp:011d}{timestamp_ns:06d}{random_num:02d}"
|
||
|
||
# 7. 计算打手分成(完全复用原逻辑)
|
||
dashou_lilu = Lilubiao.objects.filter(fadanpingtai='1').first()
|
||
if not dashou_lilu:
|
||
return Response({'code': 9, 'msg': '系统利率配置不完整', 'data': None})
|
||
|
||
if shangpin.kaioi_ewai_dashou_fencheng and shangpin.ewai_dashou_fencheng is not None:
|
||
dashou_fencheng = shangpin.ewai_dashou_fencheng
|
||
else:
|
||
dashou_fencheng = Decimal(str(shangpin_jiage)) * Decimal(str(dashou_lilu.lilu))
|
||
|
||
# ---------- 修改 session_key 获取逻辑 ----------
|
||
# 每次下单必须携带 code,强制刷新 session_key
|
||
if not code:
|
||
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}")
|
||
# ---------------------------------------------
|
||
|
||
# 9. 数据库事务创建订单(与原逻辑完全相同)
|
||
with transaction.atomic():
|
||
laoban_yonghuid = getattr(current_user, 'yonghuid', '')
|
||
|
||
# 订单主表
|
||
order_data = {
|
||
'dingdan_id': dingdanid,
|
||
'zhuangtai': 9,
|
||
'jine': shangpin_jiage,
|
||
'fadan_pingtai': 1,
|
||
'dashou_fencheng': dashou_fencheng,
|
||
'zhiding_id': zhiding_id_value,
|
||
'shangpin_id': shangpin_id,
|
||
'yaoqiuleixing': getattr(shangpin, 'yaoqiuleixing', None),
|
||
'huiyuan_id': getattr(shangpin, 'huiyuan_id', ''),
|
||
'yongjin': getattr(shangpin, 'yongjin', 0),
|
||
'jieshao': getattr(shangpin, 'jieshao', ''),
|
||
'beizhu': beizhu,
|
||
'nicheng': nicheng,
|
||
'tupian': shangpin_tupian,
|
||
'user1_id': f"Boss{current_user.yonghuid}",
|
||
'leixing_id': shangpin.leixing_id
|
||
}
|
||
dingdan = Dingdan.objects.create(**order_data)
|
||
|
||
# 订单平台扩展表
|
||
DingdanPingtai.objects.create(
|
||
dingdan=dingdan,
|
||
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
|
||
)
|
||
|
||
if not pay_params:
|
||
raise Exception('虚拟支付参数生成失败')
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '订单创建成功',
|
||
'data': {
|
||
'dingdanid': dingdanid,
|
||
'payParams': pay_params
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"虚拟支付下单失败: {e}", exc_info=True)
|
||
return Response({'code': 99, 'msg': '系统错误', 'data': None})
|
||
|
||
def _exchange_code_for_session_key(self, code):
|
||
"""
|
||
通过 jscode2session 换取 openid 和 session_key
|
||
"""
|
||
url = 'https://api.weixin.qq.com/sns/jscode2session'
|
||
params = {
|
||
'appid': settings.WEIXIN_APPID,
|
||
'secret': settings.WEIXIN_SECRET,
|
||
'js_code': code,
|
||
'grant_type': 'authorization_code'
|
||
}
|
||
try:
|
||
resp = requests.get(url, params=params, timeout=5)
|
||
data = resp.json()
|
||
if 'openid' in data and 'session_key' in data:
|
||
return data
|
||
else:
|
||
logger.error(f"jscode2session 失败: {data}")
|
||
except Exception as e:
|
||
logger.error(f"换取 session_key 异常: {e}")
|
||
return None
|
||
|
||
def generate_virtual_pay_params(self, dingdanid, jine, openid, session_key, beizhu=''):
|
||
"""
|
||
生成代币虚拟支付参数(sell_coin 模式)
|
||
"""
|
||
env = settings.VP_ENV
|
||
appkey = settings.VP_APPKEY_SANDBOX if env == 1 else settings.VP_APPKEY
|
||
offer_id = settings.VP_OFFER_ID
|
||
product_id = 'coin_1' # 代币道具ID
|
||
|
||
# 购买代币数量 = 总价(分)
|
||
buy_quantity = int(float(jine) * 100)
|
||
|
||
# signData 构建(驼峰字段,代币模式不含 goodsPrice)
|
||
sign_data = {
|
||
"offerId": offer_id,
|
||
"env": env,
|
||
"openid": openid,
|
||
"outTradeNo": dingdanid,
|
||
"productId": product_id,
|
||
"buyQuantity": buy_quantity,
|
||
"attach": beizhu or '游戏陪玩订单',
|
||
"currencyType": "CNY",
|
||
"zoneId": "1"
|
||
}
|
||
sign_data_str = json.dumps(sign_data, separators=(',', ':'), ensure_ascii=False)
|
||
|
||
# 支付签名(paySig)+ 用户态签名(signature)
|
||
uri = "requestVirtualPayment"
|
||
msg = uri + "&" + sign_data_str
|
||
pay_sig = hmac.new(appkey.encode('utf-8'), msg.encode('utf-8'), hashlib.sha256).hexdigest()
|
||
signature = hmac.new(session_key.encode('utf-8'), sign_data_str.encode('utf-8'), hashlib.sha256).hexdigest()
|
||
|
||
return {
|
||
"signData": sign_data_str,
|
||
"paySig": pay_sig,
|
||
"signature": signature,
|
||
"mode": "short_series_coin"
|
||
}
|
||
|
||
|
||
class VirtualPayNotifyView(APIView):
|
||
"""
|
||
虚拟支付回调接口
|
||
业务处理逻辑与原 WechatPayNotifyView 完全一致,仅 XML 解析和签名验证方式不同
|
||
"""
|
||
permission_classes = [permissions.AllowAny]
|
||
|
||
def post(self, request):
|
||
try:
|
||
body = request.body
|
||
# 虚拟支付回调签名验证
|
||
signature = request.headers.get('Wechatpay-Signature')
|
||
if not self._verify_virtual_signature(body, signature):
|
||
logger.warning("虚拟支付回调签名验证失败")
|
||
return HttpResponse('FAIL', status=401)
|
||
|
||
data = xmltodict.parse(body.decode('utf-8')).get('xml', {})
|
||
event = data.get('Event')
|
||
if event != 'xpay_goods_deliver_notify':
|
||
return HttpResponse('success')
|
||
|
||
out_trade_no = data.get('OutTradeNo')
|
||
transaction_id = ''
|
||
wechat_pay_info = data.get('WeChatPayInfo', {})
|
||
if isinstance(wechat_pay_info, dict):
|
||
transaction_id = wechat_pay_info.get('TransactionId', '')
|
||
|
||
# 执行支付成功处理(完全复用原 _handle_success 逻辑)
|
||
self._handle_success(out_trade_no, transaction_id)
|
||
return HttpResponse('success')
|
||
|
||
except Exception as e:
|
||
logger.error(f"虚拟支付回调异常: {e}", exc_info=True)
|
||
return HttpResponse('success')
|
||
|
||
def _verify_virtual_signature(self, body, signature):
|
||
"""验证虚拟支付回调签名"""
|
||
if not signature:
|
||
return False
|
||
appkey = settings.VP_APPKEY_SANDBOX if settings.VP_ENV == 1 else settings.VP_APPKEY
|
||
calc_sign = hmac.new(appkey.encode('utf-8'), body, hashlib.sha256).hexdigest()
|
||
return hmac.compare_digest(calc_sign, signature)
|
||
|
||
@transaction.atomic
|
||
def _handle_success(self, out_trade_no, transaction_id):
|
||
"""
|
||
支付成功处理(与 WechatPayNotifyView._handle_success 完全相同)
|
||
"""
|
||
logger.info(f"虚拟支付回调成功, out_trade_no={out_trade_no}, transaction_id={transaction_id}")
|
||
|
||
try:
|
||
dingdan = Dingdan.objects.select_for_update().filter(dingdan_id=out_trade_no).first()
|
||
except Exception as e:
|
||
logger.error(f"查询订单异常: {e}")
|
||
raise
|
||
|
||
if not dingdan:
|
||
logger.error(f"订单不存在: {out_trade_no}")
|
||
return
|
||
|
||
# 幂等:仅处理未支付订单
|
||
if dingdan.zhuangtai != 9:
|
||
logger.info(f"订单 {out_trade_no} 已处理过,状态={dingdan.zhuangtai},跳过")
|
||
return
|
||
|
||
# 保存交易号
|
||
dingdan.wechat_transaction_id = transaction_id or 'virtual_' + out_trade_no
|
||
|
||
# 更新状态:指定单 7,普通单 1
|
||
if dingdan.zhiding_id and dingdan.zhiding_id.strip():
|
||
dingdan.zhuangtai = 7
|
||
else:
|
||
dingdan.zhuangtai = 1
|
||
dingdan.save()
|
||
|
||
# 废弃同商品其他未支付订单(状态 25)
|
||
Dingdan.objects.filter(
|
||
user1_id=dingdan.user1_id,
|
||
shangpin_id=dingdan.shangpin_id,
|
||
zhuangtai=9
|
||
).exclude(dingdan_id=out_trade_no).update(zhuangtai=25)
|
||
|
||
# 更新老板消费数据
|
||
try:
|
||
laoban_id = dingdan.pingtai_kuozhan.laoban_id if hasattr(dingdan, 'pingtai_kuozhan') else None
|
||
if laoban_id:
|
||
user_main = UserMain.objects.filter(yonghuid=laoban_id).first()
|
||
if user_main and hasattr(user_main, 'boss_profile'):
|
||
boss = user_main.boss_profile
|
||
boss.zonge = (boss.zonge or 0) + (dingdan.jine or 0)
|
||
boss.alldingdan = (boss.alldingdan or 0) + 1
|
||
boss.save()
|
||
except Exception as e:
|
||
logger.error(f"更新老板数据异常: {e}")
|
||
|
||
# 更新商品库存
|
||
if dingdan.shangpin_id:
|
||
Shangpin.objects.filter(id=dingdan.shangpin_id).update(kucun=F('kucun') - 1)
|
||
|
||
# 收支记录、日收入、跨平台同步
|
||
self._update_szjilu(dingdan.jine)
|
||
update_daily_income(dingdan.jine)
|
||
sync_order_to_partners(dingdan.dingdan_id)
|
||
|
||
# 异步广播通知
|
||
self._broadcast_async(dingdan)
|
||
|
||
logger.info(f"订单 {out_trade_no} 支付成功处理完成")
|
||
|
||
def _update_szjilu(self, jine):
|
||
"""更新收支记录表(与旧逻辑一致)"""
|
||
try:
|
||
jine_decimal = Decimal(str(jine))
|
||
with transaction.atomic():
|
||
szjilu, created = Szjilu.objects.select_for_update().get_or_create(
|
||
id=1,
|
||
defaults={
|
||
'zongsy': Decimal('0.00'), 'zongls': Decimal('0.00'),
|
||
'zongzc': Decimal('0.00'), 'jrzc': Decimal('0.00'),
|
||
'jrls': Decimal('0.00')
|
||
}
|
||
)
|
||
szjilu.zongsy += jine_decimal
|
||
szjilu.zongls += jine_decimal
|
||
szjilu.jrls += jine_decimal
|
||
szjilu.save()
|
||
except Exception as e:
|
||
logger.error(f"更新收支记录失败: {e}")
|
||
|
||
def _broadcast_async(self, dingdan):
|
||
"""异步广播通知(同旧)"""
|
||
try:
|
||
def task():
|
||
try:
|
||
from . import WeixinBroadcastSender
|
||
order_info = {
|
||
'dingdan_id': dingdan.dingdan_id,
|
||
'game_type': self._get_game_type_name(dingdan.leixing_id),
|
||
'amount': str(dingdan.jine),
|
||
'order_desc': (dingdan.jieshao or '')[:50],
|
||
}
|
||
sender = WeixinBroadcastSender()
|
||
sender.broadcast_order(order_info)
|
||
except Exception as e:
|
||
logger.error(f"广播任务异常: {e}")
|
||
|
||
thread = threading.Thread(target=task)
|
||
thread.daemon = True
|
||
thread.start()
|
||
except Exception as e:
|
||
logger.error(f"启动广播线程异常: {e}")
|
||
|
||
def _get_game_type_name(self, leixing_id):
|
||
try:
|
||
from shangpin.models import ShangpinLeixing
|
||
gt = ShangpinLeixing.objects.filter(id=leixing_id).first()
|
||
return gt.jieshao if gt else '游戏订单'
|
||
except Exception:
|
||
return '游戏订单'
|
||
|
||
'''class VirtualPaymentVerifyView(APIView):
|
||
"""
|
||
虚拟支付复查接口(代币订单)- 完整调试版
|
||
"""
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
try:
|
||
dingdanid = request.data.get('dingdanid')
|
||
print(f"\n{'='*60}")
|
||
print(f"[复查-步骤1] 收到复查请求")
|
||
print(f"[复查-步骤1] 订单号: {dingdanid}")
|
||
print(f"{'='*60}")
|
||
|
||
logger.info(f"[复查] 收到复查请求 dingdanid={dingdanid}")
|
||
|
||
if not dingdanid:
|
||
return Response({'code': 1, 'msg': '订单ID不能为空'})
|
||
|
||
dingdan = Dingdan.objects.filter(dingdan_id=dingdanid).first()
|
||
if not dingdan:
|
||
print(f"[复查-错误] 订单不存在: {dingdanid}")
|
||
return Response({'code': 2, 'msg': '订单不存在'})
|
||
|
||
current_user = request.user
|
||
user_identifier = f"Boss{current_user.yonghuid}"
|
||
if dingdan.user1_id != user_identifier:
|
||
print(f"[复查-错误] 无权访问: user={user_identifier}, order_owner={dingdan.user1_id}")
|
||
return Response({'code': 403, 'msg': '无权访问'})
|
||
|
||
if dingdan.zhuangtai in [1, 7]:
|
||
print(f"[复查-步骤2] 订单已成功,状态: {dingdan.zhuangtai}")
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '订单状态正常',
|
||
'data': {'dingdanid': dingdanid, 'zhuangtai': dingdan.zhuangtai}
|
||
})
|
||
|
||
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):
|
||
print(f"[复查-步骤3] 订单正在处理中,跳过")
|
||
return Response({'code': 1, 'msg': '订单正在处理中,请稍后'})
|
||
|
||
try:
|
||
print(f"[复查-步骤4] 开始查单...")
|
||
trade_state, transaction_id = self._query_virtual_payment(dingdanid)
|
||
print(f"[复查-步骤5] 查单结果: trade_state={trade_state}, transaction_id={transaction_id}")
|
||
|
||
if trade_state == 'SUCCESS':
|
||
self._process_payment_success(dingdan, transaction_id, current_user.yonghuid)
|
||
dingdan.refresh_from_db()
|
||
print(f"[复查-成功] 订单状态已更新为 {dingdan.zhuangtai}")
|
||
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': '支付失败'})
|
||
except Exception as e:
|
||
print(f"[复查-异常] {e}")
|
||
return Response({'code': 1, 'msg': f'查询失败: {str(e)}'})
|
||
finally:
|
||
cache.delete(lock_key)
|
||
|
||
status_msg = {
|
||
2: '订单已取消', 3: '订单已完成', 4: '退款审核中',
|
||
5: '已退款', 6: '退款失败', 8: '结算中', 0: '订单已作废'
|
||
}
|
||
return Response({'code': 3, 'msg': status_msg.get(dingdan.zhuangtai, '订单状态异常'),
|
||
'data': {'zhuangtai': dingdan.zhuangtai}})
|
||
|
||
except Exception as e:
|
||
print(f"[复查-全局异常] {e}")
|
||
return Response({'code': 99, 'msg': '系统错误'})
|
||
|
||
def _get_access_token(self):
|
||
"""
|
||
获取 access_token,打印每一步详细信息
|
||
"""
|
||
from django.core.cache import cache
|
||
|
||
print(f"\n{'='*60}")
|
||
print(f"[获取AccessToken-步骤1] 开始获取 access_token")
|
||
print(f"[获取AccessToken-步骤1] AppID: {settings.WEIXIN_APPID}")
|
||
print(f"{'='*60}")
|
||
|
||
cache_key = "virtual_payment_access_token"
|
||
access_token = cache.get(cache_key)
|
||
|
||
if access_token:
|
||
print(f"[获取AccessToken-成功] 从缓存获取到 access_token")
|
||
print(f"[获取AccessToken-成功] token 长度: {len(access_token)}")
|
||
print(f"[获取AccessToken-成功] token 前10位: {access_token[:10]}...")
|
||
print(f"[获取AccessToken-成功] token 后10位: ...{access_token[-10:]}")
|
||
return access_token
|
||
|
||
print(f"[获取AccessToken-步骤2] 缓存未命中,调用微信接口获取...")
|
||
|
||
try:
|
||
resp = requests.get(
|
||
"https://api.weixin.qq.com/cgi-bin/token",
|
||
params={
|
||
"grant_type": "client_credential",
|
||
"appid": settings.WEIXIN_APPID,
|
||
"secret": settings.WEIXIN_SECRET,
|
||
},
|
||
timeout=10,
|
||
)
|
||
data = resp.json()
|
||
|
||
print(f"[获取AccessToken-步骤3] 微信接口返回完整数据:")
|
||
print(f" {json.dumps(data, indent=2, ensure_ascii=False)}")
|
||
|
||
if "access_token" in data:
|
||
access_token = data["access_token"]
|
||
expires_in = data.get("expires_in", 7200)
|
||
cache.set(cache_key, access_token, expires_in - 300)
|
||
|
||
print(f"[获取AccessToken-成功] 新获取的 access_token:")
|
||
print(f"[获取AccessToken-成功] - 长度: {len(access_token)}")
|
||
print(f"[获取AccessToken-成功] - 前10位: {access_token[:10]}...")
|
||
print(f"[获取AccessToken-成功] - 后10位: ...{access_token[-10:]}")
|
||
print(f"[获取AccessToken-成功] - 有效期: {expires_in} 秒")
|
||
|
||
return access_token
|
||
else:
|
||
errcode = data.get("errcode", "未知")
|
||
errmsg = data.get("errmsg", "未知")
|
||
print(f"[获取AccessToken-失败] 微信返回错误:")
|
||
print(f"[获取AccessToken-失败] - errcode: {errcode}")
|
||
print(f"[获取AccessToken-失败] - errmsg: {errmsg}")
|
||
print(f"[获取AccessToken-失败] - 可能原因: AppID或AppSecret错误")
|
||
raise Exception(f"获取 access_token 失败: errcode={errcode}, errmsg={errmsg}")
|
||
|
||
except Exception as e:
|
||
print(f"[获取AccessToken-异常] 网络请求失败: {e}")
|
||
raise
|
||
|
||
def _query_virtual_payment(self, out_trade_no):
|
||
"""
|
||
查询代币订单状态 - 完整调试版
|
||
"""
|
||
print(f"\n{'='*60}")
|
||
print(f"[查单-步骤1] 开始查单流程")
|
||
print(f"[查单-步骤1] 订单号: {out_trade_no}")
|
||
print(f"{'='*60}")
|
||
|
||
# 1. 获取 access_token
|
||
access_token = self._get_access_token()
|
||
|
||
# 2. 准备请求参数
|
||
uri = "/wxa/game/queryorderinfo"
|
||
base_url = "https://api.weixin.qq.com"
|
||
appkey = settings.VP_APPKEY_SANDBOX if settings.VP_ENV == 1 else settings.VP_APPKEY
|
||
offer_id = settings.VP_OFFER_ID
|
||
ts = int(time.time())
|
||
|
||
print(f"\n[查单-步骤2] 请求参数配置:")
|
||
print(f"[查单-步骤2] - offer_id: {offer_id}")
|
||
print(f"[查单-步骤2] - env: {settings.VP_ENV} (0=现网, 1=沙箱)")
|
||
print(f"[查单-步骤2] - ts: {ts}")
|
||
print(f"[查单-步骤2] - appkey 长度: {len(appkey)}")
|
||
print(f"[查单-步骤2] - appkey 前5位: {appkey[:5]}...")
|
||
print(f"[查单-步骤2] - openid: {self.request.user.openid}")
|
||
|
||
body_data = {
|
||
"openid": self.request.user.openid,
|
||
"offer_id": offer_id,
|
||
"ts": ts,
|
||
"zone_id": "1",
|
||
"env": settings.VP_ENV,
|
||
"out_trade_no": out_trade_no,
|
||
"biz_id": 1
|
||
}
|
||
body = json.dumps(body_data, separators=(",", ":"))
|
||
|
||
print(f"\n[查单-步骤3] 请求体 (Body):")
|
||
print(f" {body}")
|
||
|
||
# 3. 生成签名
|
||
msg = uri + "&" + body
|
||
pay_sig = hmac.new(
|
||
appkey.encode("utf-8"),
|
||
msg.encode("utf-8"),
|
||
hashlib.sha256
|
||
).hexdigest()
|
||
|
||
session_key = getattr(self.request.user, 'session_key', '')
|
||
signature = ""
|
||
if session_key:
|
||
signature = hmac.new(
|
||
session_key.encode("utf-8"),
|
||
body.encode("utf-8"),
|
||
hashlib.sha256
|
||
).hexdigest()
|
||
|
||
print(f"\n[查单-步骤4] 签名信息:")
|
||
print(f"[查单-步骤4] - pay_sig: {pay_sig}")
|
||
print(f"[查单-步骤4] - signature: {signature}")
|
||
print(f"[查单-步骤4] - session_key 长度: {len(session_key) if session_key else 0}")
|
||
|
||
# 4. 构建完整 URL
|
||
url = (
|
||
f"{base_url}{uri}"
|
||
f"?access_token={access_token}"
|
||
f"&signature={signature}"
|
||
f"&sig_method=hmac_sha256"
|
||
f"&pay_sig={pay_sig}"
|
||
)
|
||
|
||
print(f"\n[查单-步骤5] 完整请求 URL:")
|
||
print(f" {url}")
|
||
|
||
# 5. 发起请求
|
||
print(f"\n[查单-步骤6] 正在发送请求...")
|
||
headers = {"Content-Type": "application/json"}
|
||
response = requests.post(url, data=body, headers=headers, timeout=10)
|
||
result = response.json()
|
||
|
||
print(f"\n[查单-步骤7] 微信返回完整数据:")
|
||
print(f" HTTP 状态码: {response.status_code}")
|
||
print(f" {json.dumps(result, indent=2, ensure_ascii=False)}")
|
||
|
||
# 6. 解析结果
|
||
errcode = result.get("errcode", -1)
|
||
errmsg = result.get("errmsg", "")
|
||
|
||
print(f"\n[查单-步骤8] 结果分析:")
|
||
print(f"[查单-步骤8] - errcode: {errcode}")
|
||
print(f"[查单-步骤8] - errmsg: {errmsg}")
|
||
|
||
if errcode == 0:
|
||
pay_state = result.get("pay_state", 1)
|
||
trade_state_map = {1: "USERPAYING", 2: "SUCCESS", 3: "CLOSED"}
|
||
trade_state = trade_state_map.get(pay_state, "UNKNOWN")
|
||
transaction_id = result.get("transaction_id", "")
|
||
print(f"[查单-成功] pay_state={pay_state}, trade_state={trade_state}")
|
||
return trade_state, transaction_id
|
||
|
||
# 错误分析
|
||
print(f"\n[查单-失败] 错误分析:")
|
||
error_map = {
|
||
40001: "access_token 无效或过期 (invalid credential)",
|
||
48001: "API 权限未开通 (api unauthorized)",
|
||
90016: "session_key 已过期",
|
||
90019: "订单号未找到"
|
||
}
|
||
reason = error_map.get(errcode, f"未知错误码: {errcode}")
|
||
print(f"[查单-失败] 错误原因: {reason}")
|
||
|
||
if errcode == 48001:
|
||
print(f"\n[查单-排查建议] API 权限未开通,请检查:")
|
||
print(f" 1. 小程序后台 -> 功能 -> 虚拟支付2.0 -> 支付应用 -> 确认签约状态")
|
||
print(f" 2. 小程序后台 -> 功能 -> 虚拟支付2.0 -> 支付应用 -> 权限管理 -> 开启查单权限")
|
||
print(f" 3. 确认 offer_id ({offer_id}) 与当前小程序 AppID 匹配")
|
||
print(f" 4. 确认使用的 appkey 与支付应用后台一致")
|
||
|
||
raise Exception(f"查单失败: [{errcode}] {errmsg} - {reason}")
|
||
|
||
@transaction.atomic
|
||
def _process_payment_success(self, dingdan, transaction_id, operator_id):
|
||
"""支付成功原子操作,与旧流程完全一致"""
|
||
dingdan = Dingdan.objects.select_for_update().get(pk=dingdan.pk)
|
||
if dingdan.zhuangtai != 9:
|
||
return
|
||
|
||
dingdan.zhuangtai = 7 if (dingdan.zhiding_id and dingdan.zhiding_id.strip()) else 1
|
||
dingdan.wechat_transaction_id = transaction_id
|
||
dingdan.clkf = operator_id
|
||
dingdan.save()
|
||
|
||
Dingdan.objects.filter(
|
||
user1_id=dingdan.user1_id,
|
||
shangpin_id=dingdan.shangpin_id,
|
||
zhuangtai=9
|
||
).exclude(dingdan_id=dingdan.dingdan_id).update(zhuangtai=25)
|
||
|
||
laoban_id = dingdan.pingtai_kuozhan.laoban_id if hasattr(dingdan, 'pingtai_kuozhan') else None
|
||
if laoban_id:
|
||
user_main = UserMain.objects.filter(yonghuid=laoban_id).first()
|
||
if user_main and hasattr(user_main, 'boss_profile'):
|
||
boss = user_main.boss_profile
|
||
boss.zonge = (boss.zonge or 0) + (dingdan.jine or 0)
|
||
boss.alldingdan = (boss.alldingdan or 0) + 1
|
||
boss.save()
|
||
|
||
if dingdan.shangpin_id:
|
||
Shangpin.objects.filter(id=dingdan.shangpin_id).update(kucun=F('kucun') - 1)
|
||
|
||
self._update_szjilu(dingdan.jine)
|
||
update_daily_income(dingdan.jine)
|
||
sync_order_to_partners(dingdan.dingdan_id)
|
||
|
||
def _update_szjilu(self, jine):
|
||
"""更新平台收支记录"""
|
||
try:
|
||
jine_decimal = Decimal(str(jine))
|
||
with transaction.atomic():
|
||
szjilu, created = Szjilu.objects.select_for_update().get_or_create(
|
||
id=1,
|
||
defaults={
|
||
'zongsy': Decimal('0.00'), 'zongls': Decimal('0.00'),
|
||
'zongzc': Decimal('0.00'), 'jrzc': Decimal('0.00'),
|
||
'jrls': Decimal('0.00')
|
||
}
|
||
)
|
||
szjilu.zongsy += jine_decimal
|
||
szjilu.zongls += jine_decimal
|
||
szjilu.jrls += jine_decimal
|
||
szjilu.save()
|
||
except Exception as e:
|
||
logger.error(f"更新收支记录失败: {e}")'''
|
||
|
||
|
||
|
||
class VirtualPaymentVerifyView(APIView):
|
||
"""
|
||
虚拟支付复查接口(代币订单)- 简化版
|
||
策略:
|
||
因微信后台查单API权限问题,暂时绕过主动查单。
|
||
只要前端调起复查接口(代表前端wx.requestVirtualPayment已成功回调),
|
||
且订单状态为9(未支付),直接信任前端支付结果,更新订单为已支付。
|
||
安全提示:
|
||
此方式依赖JWT Token的不可伪造性。若Token泄露,可能被伪造支付。
|
||
后期建议开通查单权限后恢复主动查单逻辑。
|
||
业务逻辑与原 PaymentVerifyView 完全一致,仅跳过微信查单步骤。
|
||
"""
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
try:
|
||
dingdanid = request.data.get('dingdanid')
|
||
logger.info(f"[复查] 收到复查请求 dingdanid={dingdanid}")
|
||
if not dingdanid:
|
||
return Response({'code': 1, 'msg': '订单ID不能为空'})
|
||
|
||
dingdan = Dingdan.objects.filter(dingdan_id=dingdanid).first()
|
||
if not dingdan:
|
||
logger.error(f"[复查] 订单不存在: {dingdanid}")
|
||
return Response({'code': 2, 'msg': '订单不存在'})
|
||
|
||
current_user = request.user
|
||
user_identifier = f"Boss{current_user.yonghuid}"
|
||
if dingdan.user1_id != user_identifier:
|
||
logger.warning(f"[复查] 无权访问: user={user_identifier}, order_owner={dingdan.user1_id}")
|
||
return Response({'code': 403, 'msg': '无权访问'})
|
||
|
||
# 订单已经成功(状态 1 普通单 / 7 指定单)
|
||
if dingdan.zhuangtai in [1, 7]:
|
||
logger.info(f"[复查] 订单状态已成功: {dingdan.zhuangtai}")
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '订单状态正常',
|
||
'data': {'dingdanid': dingdanid, 'zhuangtai': dingdan.zhuangtai}
|
||
})
|
||
|
||
# 订单为未支付,直接信任前端支付结果
|
||
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:
|
||
# 尝试调用微信查单(日志记录用,结果不影响业务流程)
|
||
try:
|
||
trade_state, transaction_id = self._query_virtual_payment(dingdanid)
|
||
logger.info(f"[复查] 微信查单结果: trade_state={trade_state}, transaction_id={transaction_id}")
|
||
except Exception as e:
|
||
logger.warning(f"[复查] 微信查单失败(忽略): {e}")
|
||
transaction_id = ""
|
||
|
||
# 无论查单结果如何,直接更新订单为已支付
|
||
logger.info(f"[复查] 信任前端支付结果,执行本地更新")
|
||
self._process_payment_success(dingdan, transaction_id, current_user.yonghuid)
|
||
dingdan.refresh_from_db()
|
||
logger.info(f"[复查] 订单状态已更新为 {dingdan.zhuangtai}")
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '支付成功',
|
||
'data': {'dingdanid': dingdanid, 'zhuangtai': dingdan.zhuangtai}
|
||
})
|
||
finally:
|
||
cache.delete(lock_key)
|
||
|
||
# 其他异常状态
|
||
status_msg = {
|
||
2: '订单已取消', 3: '订单已完成', 4: '退款审核中',
|
||
5: '已退款', 6: '退款失败', 8: '结算中', 0: '订单已作废'
|
||
}
|
||
logger.info(f"[复查] 订单状态非9/1/7: zhuangtai={dingdan.zhuangtai}")
|
||
return Response({'code': 3, 'msg': status_msg.get(dingdan.zhuangtai, '订单状态异常'),
|
||
'data': {'zhuangtai': dingdan.zhuangtai}})
|
||
|
||
except Exception as e:
|
||
logger.error(f"[复查] 接口异常: {e}", exc_info=True)
|
||
return Response({'code': 99, 'msg': '系统错误'})
|
||
|
||
@transaction.atomic
|
||
def _process_payment_success(self, dingdan, transaction_id, operator_id):
|
||
"""
|
||
支付成功原子操作(与旧流程完全一致)
|
||
"""
|
||
logger.info(f"[复查-处理] 开始处理支付成功: dingdanid={dingdan.dingdan_id}, transaction_id={transaction_id}")
|
||
dingdan = Dingdan.objects.select_for_update().get(pk=dingdan.pk)
|
||
if dingdan.zhuangtai != 9:
|
||
logger.warning(f"[复查-处理] 订单状态已非9,跳过: 当前状态={dingdan.zhuangtai}")
|
||
return
|
||
|
||
dingdan.zhuangtai = 7 if (dingdan.zhiding_id and dingdan.zhiding_id.strip()) else 1
|
||
dingdan.wechat_transaction_id = transaction_id
|
||
dingdan.clkf = operator_id
|
||
dingdan.save()
|
||
logger.info(f"[复查-处理] 订单状态更新为 {dingdan.zhuangtai}")
|
||
|
||
# 废弃同商品其他未支付订单
|
||
abandoned = Dingdan.objects.filter(
|
||
user1_id=dingdan.user1_id,
|
||
shangpin_id=dingdan.shangpin_id,
|
||
zhuangtai=9
|
||
).exclude(dingdan_id=dingdan.dingdan_id).update(zhuangtai=25)
|
||
logger.info(f"[复查-处理] 废弃 {abandoned} 个重复未支付订单")
|
||
|
||
# 更新老板消费数据
|
||
laoban_id = dingdan.pingtai_kuozhan.laoban_id if hasattr(dingdan, 'pingtai_kuozhan') else None
|
||
if laoban_id:
|
||
user_main = UserMain.objects.filter(yonghuid=laoban_id).first()
|
||
if user_main and hasattr(user_main, 'boss_profile'):
|
||
boss = user_main.boss_profile
|
||
boss.zonge = (boss.zonge or 0) + (dingdan.jine or 0)
|
||
boss.alldingdan = (boss.alldingdan or 0) + 1
|
||
boss.save()
|
||
logger.info(f"[复查-处理] 更新老板 {laoban_id} 消费数据: +{dingdan.jine}元, 累计订单+1")
|
||
|
||
# 减库存
|
||
if dingdan.shangpin_id:
|
||
Shangpin.objects.filter(id=dingdan.shangpin_id).update(kucun=F('kucun') - 1)
|
||
logger.info(f"[复查-处理] 商品 {dingdan.shangpin_id} 库存减1")
|
||
|
||
# 收支、日收入、跨平台同步
|
||
self._update_szjilu(dingdan.jine)
|
||
update_daily_income(dingdan.jine)
|
||
sync_order_to_partners(dingdan.dingdan_id)
|
||
logger.info(f"[复查-处理] 收支、日收入、跨平台同步完成")
|
||
|
||
def _query_virtual_payment(self, out_trade_no):
|
||
"""
|
||
尝试调用微信查单接口(仅日志记录)
|
||
若失败会抛出异常,由上层捕获
|
||
"""
|
||
# 1. 获取 access_token
|
||
from django.core.cache import cache
|
||
access_token = self._get_access_token()
|
||
|
||
# 2. 准备请求体
|
||
uri = "/wxa/game/queryorderinfo"
|
||
base_url = "https://api.weixin.qq.com"
|
||
appkey = settings.VP_APPKEY_SANDBOX if settings.VP_ENV == 1 else settings.VP_APPKEY
|
||
offer_id = settings.VP_OFFER_ID
|
||
ts = int(time.time())
|
||
|
||
body_data = {
|
||
"openid": self.request.user.openid,
|
||
"offer_id": offer_id,
|
||
"ts": ts,
|
||
"zone_id": "1",
|
||
"env": settings.VP_ENV,
|
||
"out_trade_no": out_trade_no,
|
||
"biz_id": 1 # 代币=1
|
||
}
|
||
body = json.dumps(body_data, separators=(",", ":"))
|
||
|
||
# 3. 双重签名
|
||
msg = uri + "&" + body
|
||
pay_sig = hmac.new(
|
||
appkey.encode("utf-8"),
|
||
msg.encode("utf-8"),
|
||
hashlib.sha256
|
||
).hexdigest()
|
||
|
||
session_key = getattr(self.request.user, 'session_key', '')
|
||
signature = hmac.new(
|
||
session_key.encode("utf-8"),
|
||
body.encode("utf-8"),
|
||
hashlib.sha256
|
||
).hexdigest() if session_key else ""
|
||
|
||
# 4. 发送请求
|
||
url = (
|
||
f"{base_url}{uri}"
|
||
f"?access_token={access_token}"
|
||
f"&signature={signature}"
|
||
f"&sig_method=hmac_sha256"
|
||
f"&pay_sig={pay_sig}"
|
||
)
|
||
headers = {"Content-Type": "application/json"}
|
||
response = requests.post(url, data=body, headers=headers, timeout=10)
|
||
result = response.json()
|
||
|
||
errcode = result.get("errcode", -1)
|
||
if errcode == 0:
|
||
pay_state = result.get("pay_state", 1)
|
||
trade_state_map = {1: "USERPAYING", 2: "SUCCESS", 3: "CLOSED"}
|
||
trade_state = trade_state_map.get(pay_state, "UNKNOWN")
|
||
transaction_id = result.get("transaction_id", "")
|
||
return trade_state, transaction_id
|
||
else:
|
||
raise Exception(f"查单失败: {result.get('errmsg', '未知错误')}")
|
||
|
||
def _get_access_token(self):
|
||
"""获取 access_token,带缓存"""
|
||
from django.core.cache import cache
|
||
cache_key = "virtual_payment_access_token"
|
||
access_token = cache.get(cache_key)
|
||
if not access_token:
|
||
resp = requests.get(
|
||
"https://api.weixin.qq.com/cgi-bin/token",
|
||
params={
|
||
"grant_type": "client_credential",
|
||
"appid": settings.WEIXIN_APPID,
|
||
"secret": settings.WEIXIN_SECRET,
|
||
},
|
||
timeout=10,
|
||
)
|
||
data = resp.json()
|
||
if "access_token" in data:
|
||
access_token = data["access_token"]
|
||
cache.set(cache_key, access_token, data.get("expires_in", 7200) - 300)
|
||
else:
|
||
raise Exception(f"获取 access_token 失败: {data}")
|
||
return access_token
|
||
|
||
def _update_szjilu(self, jine):
|
||
"""更新平台收支记录(同旧逻辑)"""
|
||
try:
|
||
jine_decimal = Decimal(str(jine))
|
||
with transaction.atomic():
|
||
szjilu, created = Szjilu.objects.select_for_update().get_or_create(
|
||
id=1,
|
||
defaults={
|
||
'zongsy': Decimal('0.00'), 'zongls': Decimal('0.00'),
|
||
'zongzc': Decimal('0.00'), 'jrzc': Decimal('0.00'),
|
||
'jrls': Decimal('0.00')
|
||
}
|
||
)
|
||
szjilu.zongsy += jine_decimal
|
||
szjilu.zongls += jine_decimal
|
||
szjilu.jrls += jine_decimal
|
||
szjilu.save()
|
||
except Exception as e:
|
||
logger.error(f"更新收支记录失败: {e}")
|
||
|
||
|
||
|
||
# 支付失败删除订单接口(与旧 PaymentFailView 一致)
|
||
class VirtualPaymentFailView(APIView):
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
dingdanid = request.data.get('dingdanid')
|
||
if not dingdanid:
|
||
return Response({'code': 1, 'msg': '订单ID不能为空'})
|
||
|
||
with transaction.atomic():
|
||
dingdan = Dingdan.objects.select_for_update().filter(dingdan_id=dingdanid).first()
|
||
if not dingdan:
|
||
return Response({'code': 2, 'msg': '订单不存在'})
|
||
if dingdan.zhuangtai != 9:
|
||
return Response({'code': 3, 'msg': '只能删除未支付的订单'})
|
||
|
||
DingdanPingtai.objects.filter(dingdan=dingdan).delete()
|
||
dingdan.delete()
|
||
|
||
return Response({'code': 0, 'msg': '订单已删除'})
|
||
except Exception as e:
|
||
return Response({'code': 99, 'msg': f'删除订单失败: {str(e)}'})
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
from yonghu.models import UserMain
|
||
|
||
# dingdan/views.py
|
||
|
||
from rest_framework.views import APIView
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from rest_framework.response import Response
|
||
from django.db.models import Q
|
||
|
||
from .models import Dingdan, DingdanPingtai
|
||
|
||
|
||
class DingdanHuoquView(APIView):
|
||
"""订单获取接口"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
# 获取当前登录用户
|
||
try:
|
||
current_user = request.user
|
||
yonghuid = current_user.yonghuid
|
||
except Exception as e:
|
||
return Response({
|
||
'code': 1,
|
||
'msg': '用户认证失败',
|
||
'data': None
|
||
})
|
||
|
||
# 获取前端参数
|
||
try:
|
||
data = request.data
|
||
zhuangtai_list = data.get('zhuangtai_list', [])
|
||
page = int(data.get('page', 1))
|
||
page_size = int(data.get('page_size', 10))
|
||
except Exception as e:
|
||
return Response({
|
||
'code': 2,
|
||
'msg': '参数格式错误',
|
||
'data': None
|
||
})
|
||
|
||
# 参数验证
|
||
if not isinstance(zhuangtai_list, list) or len(zhuangtai_list) == 0:
|
||
return Response({
|
||
'code': 3,
|
||
'msg': '状态列表不能为空',
|
||
'data': None
|
||
})
|
||
|
||
if page < 1:
|
||
page = 1
|
||
if page_size <= 0:
|
||
page_size = 10
|
||
|
||
try:
|
||
# 查询扩展表,获取用户的所有订单ID
|
||
pingtai_orders = DingdanPingtai.objects.filter(laoban_id=yonghuid)
|
||
pingtai_order_ids = list(pingtai_orders.values_list('dingdan__dingdan_id', flat=True))
|
||
|
||
if not pingtai_order_ids:
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '成功',
|
||
'data': {
|
||
'list': [],
|
||
'has_more': False,
|
||
'total': 0
|
||
}
|
||
})
|
||
|
||
# 查询主表
|
||
status_query = Q()
|
||
if zhuangtai_list != [1, 2, 3, 4, 5, 6, 7, 8]:
|
||
status_query = Q(zhuangtai__in=zhuangtai_list)
|
||
|
||
query = Q(dingdan_id__in=pingtai_order_ids) & status_query
|
||
orders = Dingdan.objects.filter(query).order_by('-create_time')
|
||
#total_orders = orders.count()
|
||
|
||
# 手动分页逻辑
|
||
start_index = (page - 1) * page_size
|
||
end_index = start_index + page_size
|
||
|
||
# 获取当前页的数据
|
||
current_page_orders = orders[start_index:end_index]
|
||
|
||
# 构建返回数据
|
||
order_list = []
|
||
for order in current_page_orders:
|
||
order_data = {
|
||
'dingdanId': order.dingdan_id,
|
||
'zhuangtai': order.zhuangtai,
|
||
'jine': float(order.jine) if order.jine else 0.0,
|
||
'tupian': order.tupian or '',
|
||
'jieshao': order.jieshao or '',
|
||
'beizhu': order.beizhu or '',
|
||
'nicheng': order.nicheng or '',
|
||
'chuangjianshijian': order.create_time.strftime('%Y-%m-%d %H:%M:%S')
|
||
}
|
||
order_list.append(order_data)
|
||
|
||
|
||
# 判断是否还有更多数据
|
||
current_page_count = len(current_page_orders)
|
||
has_more = current_page_count == page_size
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '成功',
|
||
'data': {
|
||
'list': order_list,
|
||
'has_more': has_more,
|
||
#'total': total_orders
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
return Response({
|
||
'code': 4,
|
||
'msg': '服务器内部错误',
|
||
'data': None
|
||
})
|
||
|
||
|
||
# dingdan/views.py
|
||
|
||
from rest_framework.views import APIView
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from rest_framework.response import Response
|
||
from django.db.models import Q
|
||
import json
|
||
|
||
from .models import Dingdan, DingdanPingtai, Dashoutupian
|
||
|
||
|
||
class DingdanXiangqingView(APIView):
|
||
"""订单详情接口"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
# 获取当前登录用户
|
||
try:
|
||
current_user = request.user
|
||
yonghuid = current_user.yonghuid
|
||
except Exception as e:
|
||
return Response({
|
||
'code': 1,
|
||
'msg': '用户认证失败',
|
||
'data': None
|
||
})
|
||
|
||
# 获取前端参数
|
||
try:
|
||
data = request.data
|
||
dingdan_id = data.get('dingdanId', '')
|
||
except Exception as e:
|
||
return Response({
|
||
'code': 2,
|
||
'msg': '参数格式错误',
|
||
'data': None
|
||
})
|
||
|
||
# 参数验证
|
||
if not dingdan_id:
|
||
return Response({
|
||
'code': 3,
|
||
'msg': '订单ID不能为空',
|
||
'data': None
|
||
})
|
||
|
||
try:
|
||
# 1. 验证订单是否属于当前用户
|
||
# 查询平台订单扩展表,检查laoban_id是否等于当前用户
|
||
pingtai_kuozhan = DingdanPingtai.objects.filter(
|
||
laoban_id=yonghuid,
|
||
dingdan__dingdan_id=dingdan_id
|
||
).first()
|
||
|
||
if not pingtai_kuozhan:
|
||
# 如果不是平台订单,检查商家订单
|
||
from .models import DingdanShangjia
|
||
shangjia_kuozhan = DingdanShangjia.objects.filter(
|
||
shangjia_id=yonghuid,
|
||
dingdan__dingdan_id=dingdan_id
|
||
).first()
|
||
|
||
if not shangjia_kuozhan:
|
||
return Response({
|
||
'code': 4,
|
||
'msg': '订单不存在或无权限查看',
|
||
'data': None
|
||
})
|
||
|
||
# 2. 查询订单主表
|
||
dingdan = Dingdan.objects.filter(dingdan_id=dingdan_id).first()
|
||
|
||
if not dingdan:
|
||
return Response({
|
||
'code': 5,
|
||
'msg': '订单不存在',
|
||
'data': None
|
||
})
|
||
|
||
# 3. 获取订单信息
|
||
zhuangtai = dingdan.zhuangtai or 0
|
||
zhiding_id = dingdan.zhiding_id or ''
|
||
jiedan_dashou_id = dingdan.jiedan_dashou_id or ''
|
||
dashou_liuyan = dingdan.dashou_liuyan or ''
|
||
|
||
# 4. 查询打手交付图片
|
||
dashoutupian_records = Dashoutupian.objects.filter(
|
||
dingdan_id=dingdan_id
|
||
).order_by('create_time')[:9] # 最多取9条
|
||
|
||
dashoujiaofu_list = []
|
||
for record in dashoutupian_records:
|
||
if record.tupian:
|
||
dashoujiaofu_list.append(record.tupian)
|
||
|
||
# 5. 构建返回数据(字段名与前端对应)
|
||
response_data = {
|
||
'zhuangtai': zhuangtai, # 订单状态
|
||
'zhiding': zhiding_id, # 指定打手ID
|
||
'dashou': jiedan_dashou_id, # 服务打手ID
|
||
'dashouliuyan': dashou_liuyan, # 打手留言
|
||
'dashoujiaofu': dashoujiaofu_list # 打手交付图片列表
|
||
}
|
||
|
||
# 6. 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '成功',
|
||
'data': response_data
|
||
})
|
||
|
||
except Exception as e:
|
||
print(f"订单详情查询失败: {str(e)}")
|
||
return Response({
|
||
'code': 6,
|
||
'msg': '服务器内部错误',
|
||
'data': None
|
||
})
|
||
|
||
|
||
|
||
|
||
|
||
|
||
class JiedanView(APIView):
|
||
"""结单接口"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
# 获取当前登录用户
|
||
try:
|
||
current_user = request.user
|
||
yonghuid = current_user.yonghuid
|
||
except Exception as e:
|
||
return Response({
|
||
'code': 1,
|
||
'msg': '用户认证失败',
|
||
'data': None
|
||
})
|
||
|
||
# 获取前端参数
|
||
try:
|
||
data = request.data
|
||
dingdan_id = data.get('dingdanId', '')
|
||
pingfen = int(data.get('pingfen', 0)) # 评分,默认0
|
||
liuyan = data.get('liuyan', '') # 留言,默认空
|
||
except Exception as e:
|
||
return Response({
|
||
'code': 2,
|
||
'msg': '参数格式错误',
|
||
'data': None
|
||
})
|
||
|
||
# 参数验证
|
||
if not dingdan_id:
|
||
return Response({
|
||
'code': 3,
|
||
'msg': '订单ID不能为空',
|
||
'data': None
|
||
})
|
||
|
||
# 验证评分范围
|
||
if pingfen < 0 or pingfen > 5:
|
||
return Response({
|
||
'code': 4,
|
||
'msg': '评分必须在0-5分之间',
|
||
'data': None
|
||
})
|
||
|
||
# 使用事务确保数据一致性
|
||
try:
|
||
with transaction.atomic():
|
||
# 1. 验证订单是否存在且属于当前用户
|
||
pingtai_kuozhan = DingdanPingtai.objects.filter(
|
||
laoban_id=yonghuid,
|
||
dingdan__dingdan_id=dingdan_id
|
||
).select_related('dingdan').first()
|
||
|
||
if not pingtai_kuozhan:
|
||
return Response({
|
||
'code': 5,
|
||
'msg': '订单不存在或无权限操作',
|
||
'data': None
|
||
})
|
||
|
||
dingdan = pingtai_kuozhan.dingdan
|
||
|
||
# 2. 验证订单状态(必须是结算中 - 状态8)
|
||
if dingdan.zhuangtai != 8:
|
||
return Response({
|
||
'code': 6,
|
||
'msg': '订单不在结算状态,无法结单',
|
||
'data': None
|
||
})
|
||
|
||
# 3. 获取订单相关数据
|
||
dashou_fencheng = dingdan.dashou_fencheng or Decimal('0.00') # 打手分成
|
||
jiedan_dashou_id = dingdan.jiedan_dashou_id or '' # 接单打手ID
|
||
shangpin_id = dingdan.shangpin_id # 商品ID
|
||
|
||
# 4. 更新平台扩展表的老板评价(如果有留言)
|
||
if liuyan:
|
||
pingtai_kuozhan.laoban_pingjia = liuyan
|
||
pingtai_kuozhan.save()
|
||
|
||
# 5. 如果有评分,更新评分表
|
||
if pingfen > 0 and jiedan_dashou_id:
|
||
# 使用filter查询,更健壮
|
||
pingfen_records = Pingfen.objects.filter(dashou=jiedan_dashou_id)
|
||
|
||
if pingfen_records.exists():
|
||
# 已有记录,更新
|
||
pingfen_record = pingfen_records.first()
|
||
pingfen_record.zongfen += Decimal(str(pingfen))
|
||
pingfen_record.cishu += 1
|
||
pingfen_record.save()
|
||
else:
|
||
# 新记录,创建
|
||
Pingfen.objects.create(
|
||
dashou=jiedan_dashou_id,
|
||
zongfen=Decimal(str(pingfen)),
|
||
cishu=1
|
||
)
|
||
|
||
# 6. 更新商品真实销量(如果有商品ID)
|
||
if shangpin_id:
|
||
shangpin_records = Shangpin.objects.filter(id=shangpin_id)
|
||
|
||
if shangpin_records.exists():
|
||
shangpin = shangpin_records.first()
|
||
# 检查字段是否存在
|
||
if hasattr(shangpin, 'zhenshi_xiaoliang'):
|
||
shangpin.zhenshi_xiaoliang = (shangpin.zhenshi_xiaoliang or 0) + 1
|
||
shangpin.save()
|
||
|
||
# ========== 新增:跨平台我方派单且有对方信息,直接通知对方并跳过本地结算 ==========
|
||
if (dingdan.is_cross == 1
|
||
and dingdan.partner_order_id
|
||
and dingdan.partner_club_id):
|
||
# 更新订单状态为已完成
|
||
dingdan.zhuangtai = 3
|
||
dingdan.clkf = current_user.phone if hasattr(current_user, 'phone') else yonghuid
|
||
dingdan.save()
|
||
|
||
# 事务提交后异步通知对方平台
|
||
transaction.on_commit(
|
||
lambda: self._notify_partner_jiedan(
|
||
my_dingdan_id=dingdan.dingdan_id,
|
||
partner_club_id=dingdan.partner_club_id,
|
||
partner_order_id=dingdan.partner_order_id,
|
||
new_status=3,
|
||
current_user=current_user
|
||
)
|
||
)
|
||
|
||
# 直接返回,跳过后续所有本地打手结算、评价、统计等
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '结单成功(已通知对方平台)',
|
||
'data': None
|
||
})
|
||
# ====================================================================
|
||
|
||
# 7. 更新打手扩展表(如果有打手ID和分成)
|
||
if jiedan_dashou_id and dashou_fencheng > 0:
|
||
# 查询打手用户
|
||
dashou_user = UserMain.objects.filter(yonghuid=jiedan_dashou_id).first()
|
||
|
||
if dashou_user:
|
||
# 查询打手扩展表
|
||
dashou_kuozhan = UserDashou.objects.filter(user=dashou_user).first()
|
||
|
||
if dashou_kuozhan:
|
||
# 更新四个金额字段
|
||
dashou_kuozhan.zonge = (dashou_kuozhan.zonge or Decimal('0.00')) + dashou_fencheng
|
||
dashou_kuozhan.yue = (dashou_kuozhan.yue or Decimal('0.00')) + dashou_fencheng
|
||
dashou_kuozhan.jinrishouyi = (dashou_kuozhan.jinrishouyi or Decimal(
|
||
'0.00')) + dashou_fencheng
|
||
dashou_kuozhan.jinyueshouyi = (dashou_kuozhan.jinyueshouyi or Decimal(
|
||
'0.00')) + dashou_fencheng
|
||
|
||
# 成交总量加1
|
||
dashou_kuozhan.chengjiaozongliang = (dashou_kuozhan.chengjiaozongliang or 0) + 1
|
||
|
||
# 状态改为数字1
|
||
dashou_kuozhan.zhuangtai = 1
|
||
|
||
dashou_kuozhan.save()
|
||
|
||
# 8. 更新订单状态为已完成(状态3)
|
||
dingdan.zhuangtai = 3
|
||
dingdan.save()
|
||
|
||
# 9. 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '结单成功',
|
||
'data': None
|
||
})
|
||
|
||
except Exception as e:
|
||
|
||
# 事务会自动回滚
|
||
return Response({
|
||
'code': 7,
|
||
'msg': '结单失败,服务器内部错误',
|
||
'data': None
|
||
})
|
||
|
||
|
||
def _notify_partner_jiedan(self, my_dingdan_id, partner_club_id, partner_order_id, new_status, current_user):
|
||
"""
|
||
通知对方俱乐部我方订单已完成
|
||
参照 CrossPlatformForceCompleteView._notify_partner_order_status 实现
|
||
"""
|
||
try:
|
||
# 1. 获取我方俱乐部ID
|
||
from .models import CrossPlatformOrderData
|
||
my_club_config = ClubConfig.objects.filter(is_self=1).first()
|
||
if not my_club_config:
|
||
logger.error("未配置我方俱乐部信息,无法通知对方")
|
||
return
|
||
self_club_id = my_club_config.club_id
|
||
|
||
# 2. 查询对方服务器域名
|
||
club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id)
|
||
partner_domain = club_rel.partner_domain
|
||
if not partner_domain:
|
||
logger.error(f"对方俱乐部 {partner_club_id} 未配置域名")
|
||
return
|
||
|
||
# 3. 构造通知请求
|
||
url = partner_domain.rstrip('/') + '/houtai/dfddtk'
|
||
payload = {
|
||
'order_id': partner_order_id, # 在对方看来这是他们的订单ID
|
||
'club_id': self_club_id,
|
||
'status': new_status
|
||
}
|
||
resp = requests.post(url, json=payload, timeout=3)
|
||
if resp.status_code == 200:
|
||
resp_json = resp.json()
|
||
if resp_json.get('code') == 0:
|
||
# 4. 更新跨平台订单扩展表(记录对方状态)
|
||
CrossPlatformOrderData.objects.update_or_create(
|
||
dingdan_id=my_dingdan_id, # 我方订单ID
|
||
defaults={
|
||
'partner_order_id': partner_order_id,
|
||
'partner_club_id': partner_club_id,
|
||
'partner_order_status': new_status,
|
||
'partner_dashou_id': '',
|
||
'partner_amount': Decimal('0')
|
||
}
|
||
)
|
||
logger.info(f"老板结单通知对方成功: {partner_order_id} -> {new_status}")
|
||
else:
|
||
logger.warning(f"对方接口返回错误: {resp_json.get('msg')}")
|
||
else:
|
||
logger.warning(f"对方接口HTTP错误: {resp.status_code}")
|
||
except requests.exceptions.Timeout:
|
||
logger.error(f"通知对方平台超时: {partner_club_id}")
|
||
except Exception as e:
|
||
logger.error(f"通知对方平台异常: {str(e)}")
|
||
|
||
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from django.db.models import Prefetch
|
||
from dengji.models import Chenghao
|
||
|
||
class ShangpinLeixingView(APIView):
|
||
"""
|
||
获取商家可用的商品类型列表(含关联板块的称号)
|
||
POST /dingdan/sjspleixing
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
shangpin_queryset = ShangpinLeixing.objects.filter(
|
||
|
||
shenhezhuangtai=3
|
||
).select_related('bankuai').order_by('paixu')
|
||
|
||
bankuai_ids = shangpin_queryset.values_list('bankuai_id', flat=True).distinct()
|
||
|
||
chenghao_list = Chenghao.objects.filter(
|
||
bankuai_id__in=bankuai_ids
|
||
).values('id', 'mingcheng', 'texiao_miaoshu', 'bankuai_id')
|
||
|
||
bankuai_chenghao_map = {}
|
||
for ch in chenghao_list:
|
||
bid = ch['bankuai_id']
|
||
if bid not in bankuai_chenghao_map:
|
||
bankuai_chenghao_map[bid] = []
|
||
bankuai_chenghao_map[bid].append({
|
||
'id': ch['id'],
|
||
'mingcheng': ch['mingcheng'],
|
||
'texiao_json': ch['texiao_miaoshu']
|
||
})
|
||
|
||
data_list = []
|
||
for sp in shangpin_queryset:
|
||
item = {
|
||
'id': sp.id,
|
||
'yaoqiuleixing': sp.yaoqiuleixing,
|
||
'huiyuan_id': sp.huiyuan_id,
|
||
'jieshao': sp.jieshao,
|
||
'yongjin': sp.yongjin,
|
||
'is_cross_enabled': sp.is_cross_enabled,
|
||
'is_special_type': sp.is_special_type, # 🔴 新增字段
|
||
'chenghaoList': bankuai_chenghao_map.get(sp.bankuai_id, [])
|
||
}
|
||
|
||
# 🔴 特殊类型:返回商品模板列表(押金>0)
|
||
if sp.is_special_type:
|
||
goods_qs = Shangpin.objects.filter(
|
||
leixing_id=sp.id,
|
||
yaoqiuleixing=2,
|
||
yongjin__gt=0
|
||
).values('id', 'biaoqian', 'jieshao','yongjin')
|
||
item['goodsList'] = list(goods_qs)
|
||
else:
|
||
item['goodsList'] = []
|
||
|
||
data_list.append(item)
|
||
|
||
return Response({
|
||
'code': 200,
|
||
'msg': '获取成功',
|
||
'data': data_list
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取商品类型失败: {str(e)}", exc_info=True)
|
||
return Response({'code': 500, 'msg': '服务器内部错误', 'data': {}}, status=500)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
# views/order_views.py - ShangjiaPaifaView完整代码
|
||
import os
|
||
import logging
|
||
from shangpin.models import ShangpinLeixing
|
||
import threading
|
||
from utils.weixin_broadcast import WeixinBroadcastSender
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
|
||
|
||
|
||
# views/order_views.py - ShangjiaPaifaView完整代码
|
||
import os
|
||
import logging
|
||
from shangpin.models import ShangpinLeixing
|
||
import threading
|
||
from utils.weixin_broadcast import WeixinBroadcastSender
|
||
|
||
|
||
from dengji.models import DingdanBiaoqian,Chenghao
|
||
|
||
|
||
import os
|
||
import threading
|
||
import logging
|
||
from decimal import Decimal
|
||
from django.db import transaction
|
||
from django.db.models import F
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from rest_framework import status
|
||
|
||
from yonghu.models import UserMain, UserShangjia, UserDashou
|
||
|
||
from houtai.utils import update_shangjia_daily
|
||
|
||
|
||
# 导入 Celery 广播任务
|
||
from dingdan.tongzhi_tasks import dingdan_guangbo
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class ShangjiaPaifaView(APIView):
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
logger.info(f"商家派单请求: user_id={request.user.yonghuid}, data={request.data}")
|
||
|
||
try:
|
||
data = request.data
|
||
|
||
# ----- 1. 获取原始参数(保持原类型)-----
|
||
shangpin_type_id = data.get('shangpinTypeId')
|
||
huiyuan_id = data.get('huiyuanId')
|
||
yaoqiuleixing = data.get('yaoqiuleixing')
|
||
dingdan_jieshao_raw = data.get('dingdanJieshao', '')
|
||
dingdan_beizhu_raw = data.get('dingdanBeizhu', '')
|
||
laoban_name_raw = data.get('laobanName', '')
|
||
zhiding_uid_raw = data.get('zhidingUid', '')
|
||
jiage_str = data.get('jiage')
|
||
|
||
label_id = data.get('labelId') # 整数或 None
|
||
commission_enabled = data.get('commissionEnabled', False)
|
||
commission_value_raw = data.get('commissionValue')
|
||
|
||
# ----- 2. 字符串字段安全处理 -----
|
||
dingdan_jieshao = str(dingdan_jieshao_raw).strip()
|
||
dingdan_beizhu = str(dingdan_beizhu_raw).strip()
|
||
laoban_name = str(laoban_name_raw).strip()
|
||
zhiding_uid = str(zhiding_uid_raw).strip()
|
||
commission_value_str = str(commission_value_raw).strip() if commission_value_raw is not None else ''
|
||
|
||
# ----- 3. 必填校验 -----
|
||
if not all([shangpin_type_id, dingdan_jieshao, laoban_name, jiage_str]):
|
||
logger.warning(f"参数缺失: {request.data}")
|
||
return Response({'code': 400, 'msg': '缺少必填参数'})
|
||
|
||
# ----- 4. 价格验证 -----
|
||
try:
|
||
jiage = float(jiage_str)
|
||
if jiage < 10 or jiage > 10000:
|
||
return Response({'code': 400, 'msg': '价格需在10-10000元之间'})
|
||
if '.' in str(jiage_str) and len(str(jiage_str).split('.')[1]) > 2:
|
||
return Response({'code': 400, 'msg': '最多支持两位小数'})
|
||
except ValueError:
|
||
return Response({'code': 400, 'msg': '价格格式错误'})
|
||
|
||
# ----- 5. 商品类型 -----
|
||
try:
|
||
shangpin_type = ShangpinLeixing.objects.get(id=shangpin_type_id)
|
||
except ShangpinLeixing.DoesNotExist:
|
||
logger.error(f"商品类型不存在: {shangpin_type_id}")
|
||
return Response({'code': 400, 'msg': '商品类型不存在'})
|
||
|
||
# ----- 6. 押金 / 会员模式决定 -----
|
||
final_yongjin = None
|
||
if commission_enabled and commission_value_str:
|
||
try:
|
||
cv = float(commission_value_str)
|
||
if cv > 0:
|
||
final_yaoqiu_type = 2
|
||
final_yongjin = Decimal(str(cv))
|
||
final_huiyuan_id = ''
|
||
logger.info(f"押金模式: 佣金={cv}")
|
||
else:
|
||
final_yaoqiu_type = shangpin_type.yaoqiuleixing or 1
|
||
final_huiyuan_id = str(huiyuan_id) if huiyuan_id else ''
|
||
logger.warning("佣金为0,降级为会员模式")
|
||
except ValueError:
|
||
final_yaoqiu_type = shangpin_type.yaoqiuleixing or 1
|
||
final_huiyuan_id = str(huiyuan_id) if huiyuan_id else ''
|
||
logger.warning("佣金格式错误,降级为会员模式")
|
||
else:
|
||
final_yaoqiu_type = shangpin_type.yaoqiuleixing or 1
|
||
final_huiyuan_id = str(huiyuan_id) if huiyuan_id else ''
|
||
logger.info(f"会员模式: 类型={final_yaoqiu_type}")
|
||
|
||
# ----- 7. 称号(可选:不传/空值/无效/不存在均视为无标签)-----
|
||
chenghao_obj = None
|
||
label_id_str = str(label_id).strip() if label_id is not None else ''
|
||
if label_id_str and label_id_str not in ('0', 'null', 'undefined'):
|
||
try:
|
||
lid = int(label_id_str)
|
||
if lid > 0:
|
||
chenghao_obj = Chenghao.objects.filter(id=lid).first()
|
||
if chenghao_obj:
|
||
logger.info(f"订单将关联称号: {chenghao_obj.mingcheng}")
|
||
else:
|
||
logger.info(f"标签不存在,按无标签派单: {label_id}")
|
||
except (ValueError, TypeError):
|
||
logger.info(f"标签ID无效,按无标签派单: {label_id}")
|
||
|
||
# ----- 8. 商家信息 -----
|
||
try:
|
||
shangjia = request.user.shop_profile
|
||
except UserShangjia.DoesNotExist:
|
||
logger.warning(f"用户不是商家: {request.user.yonghuid}")
|
||
return Response({'code': 403, 'msg': '您不是商家'})
|
||
|
||
if shangjia.zhuangtai != 1:
|
||
logger.warning(f"商家状态异常: {shangjia.zhuangtai}")
|
||
return Response({'code': 403, 'msg': '商家状态异常'})
|
||
|
||
if jiage > shangjia.yue:
|
||
logger.warning(f"余额不足: {shangjia.yue} < {jiage}")
|
||
return Response({'code': 400, 'msg': f'余额不足,当前余额{shangjia.yue}元'})
|
||
|
||
# ----- 9. 指定打手 -----
|
||
zhiding_dashou = None
|
||
if zhiding_uid:
|
||
try:
|
||
zhiding_user = UserMain.objects.get(yonghuid=zhiding_uid)
|
||
zhiding_dashou = zhiding_user.dashou_profile
|
||
if zhiding_dashou.zhanghaozhuangtai != 1:
|
||
return Response({'code': 400, 'msg': '指定打手状态异常'})
|
||
except UserMain.DoesNotExist:
|
||
return Response({'code': 400, 'msg': '指定用户不存在'})
|
||
except UserDashou.DoesNotExist:
|
||
return Response({'code': 400, 'msg': '指定用户不是打手'})
|
||
|
||
# ----- 10. 利率(打手+管事分成,打手优先) -----
|
||
from dingdan.utils import calc_shangjia_order_fencheng
|
||
dashou_fencheng, guanshi_fencheng, guanshi_shangjia_fencheng = calc_shangjia_order_fencheng(jiage)
|
||
|
||
# ----- 11. 生成订单ID -----
|
||
def generate_dingdan_id():
|
||
import time, random
|
||
ts = int(time.time() * 1000)
|
||
pid = os.getpid() % 1000
|
||
rnd = random.randint(1000, 9999)
|
||
return f"SJ{ts}{pid:03d}{rnd}"
|
||
|
||
# ----- 12. 事务:创建订单、更新商家统计 -----
|
||
dingdan = None
|
||
try:
|
||
with transaction.atomic():
|
||
dingdan = Dingdan.objects.create(
|
||
dingdan_id=generate_dingdan_id(),
|
||
zhuangtai=7 if zhiding_dashou else 1,
|
||
fadan_pingtai=2,
|
||
jine=jiage,
|
||
dashou_fencheng=dashou_fencheng,
|
||
guanshi_fencheng=guanshi_fencheng,
|
||
guanshi_shangjia_fencheng=guanshi_shangjia_fencheng,
|
||
zhiding_id=zhiding_uid if zhiding_dashou else '',
|
||
shangpin_id=0,
|
||
leixing_id=int(shangpin_type_id),
|
||
yaoqiuleixing=final_yaoqiu_type,
|
||
yongjin=final_yongjin,
|
||
huiyuan_id=final_huiyuan_id,
|
||
jieshao=dingdan_jieshao[:2000],
|
||
beizhu=dingdan_beizhu[:1000],
|
||
nicheng=laoban_name[:50],
|
||
tupian=request.user.avatar,
|
||
user1_id=f"Sj{request.user.yonghuid}"
|
||
)
|
||
logger.info(f"订单主表创建: {dingdan.dingdan_id}")
|
||
|
||
DingdanShangjia.objects.create(
|
||
dingdan=dingdan,
|
||
shangjia_id=request.user.yonghuid,
|
||
sjnicheng=shangjia.nicheng,
|
||
)
|
||
|
||
if chenghao_obj:
|
||
DingdanBiaoqian.objects.create(dingdan=dingdan, chenghao=chenghao_obj)
|
||
logger.info(f"订单关联称号: {chenghao_obj.mingcheng}")
|
||
|
||
# 更新商家统计
|
||
UserShangjia.objects.filter(id=shangjia.id).update(
|
||
fabu=F('fabu') + 1,
|
||
yue=F('yue') - jiage,
|
||
jinridingdan=F('jinridingdan') + 1,
|
||
jinriliushui=F('jinriliushui') + jiage,
|
||
jinyuedingdan=F('jinyuedingdan') + 1,
|
||
jinyueliushui=F('jinyueliushui') + jiage,
|
||
)
|
||
|
||
update_shangjia_daily(
|
||
yonghuid=request.user.yonghuid,
|
||
amount=Decimal(str(jiage)),
|
||
action=1
|
||
)
|
||
# 事务提交,订单已持久化
|
||
except Exception as e:
|
||
logger.error(f"订单创建事务失败: {str(e)}", exc_info=True)
|
||
return Response({'code': 500, 'msg': '订单创建失败,请稍后重试'})
|
||
|
||
# ========== 以下操作均在事务外,避免异步任务查不到订单 ==========
|
||
# 跨平台同步(内部可能是异步任务,但函数本身是同步调用,现在订单已存在)
|
||
try:
|
||
sync_order_to_partners(dingdan.dingdan_id)
|
||
except Exception as e:
|
||
logger.error(f"跨平台同步失败: {e}", exc_info=True)
|
||
|
||
# Celery 广播任务(异步)
|
||
try:
|
||
order_info = {
|
||
'dingdan_id': dingdan.dingdan_id,
|
||
'game_type': self._get_game_type_name(dingdan.leixing_id),
|
||
'amount': str(dingdan.jine),
|
||
'order_desc': (dingdan.jieshao or '')[:50],
|
||
}
|
||
dingdan_guangbo.delay(order_info)
|
||
logger.info(f"已提交广播任务: {dingdan.dingdan_id}")
|
||
except Exception as e:
|
||
logger.error(f"广播任务提交失败: {e}", exc_info=True)
|
||
|
||
logger.info(f"商家派单成功: {dingdan.dingdan_id}, 金额={jiage}")
|
||
return Response({'code': 200, 'msg': '派单成功', 'data': {'jine': str(dingdan.jine)}})
|
||
|
||
except Exception as e:
|
||
logger.error(f"商家派单接口未捕获异常: {str(e)}", exc_info=True)
|
||
return Response({'code': 500, 'msg': '系统内部错误'})
|
||
|
||
def _get_game_type_name(self, leixing_id):
|
||
try:
|
||
gt = ShangpinLeixing.objects.filter(id=leixing_id).first()
|
||
return gt.jieshao if gt else '游戏订单'
|
||
except Exception:
|
||
return '游戏订单'
|
||
|
||
|
||
class ShangjiaDingdanHuoquView(APIView):
|
||
"""
|
||
商家订单获取接口
|
||
POST /dingdan/sjdingdanhq
|
||
需要JWT认证
|
||
支持分页、状态筛选、商品类型筛选、模糊搜索(订单ID/订单介绍/游戏昵称/打手ID)
|
||
返回待结算订单数量(pending_count)
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
# 1. 获取当前登录用户的yonghuid
|
||
try:
|
||
current_user = request.user
|
||
yonghuid = current_user.yonghuid
|
||
except AttributeError:
|
||
return Response({
|
||
'code': 1,
|
||
'msg': '用户认证失败',
|
||
'data': None
|
||
}, status=status.HTTP_401_UNAUTHORIZED)
|
||
|
||
# 2. 解析参数
|
||
try:
|
||
data = request.data
|
||
zhuangtai_list = data.get('zhuangtai_list', [])
|
||
page = max(1, int(data.get('page', 1)))
|
||
page_size = min(100, max(1, int(data.get('page_size', 10))))
|
||
|
||
# 🆕 商品类型筛选
|
||
leixing_id = data.get('leixing_id', None)
|
||
|
||
# 🆕 模糊搜索关键字(支持订单ID、订单介绍、游戏昵称、打手ID)
|
||
keyword = data.get('keyword', '').strip()
|
||
except (ValueError, TypeError):
|
||
return Response({
|
||
'code': 2,
|
||
'msg': '参数格式错误',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 3. 验证状态列表
|
||
if not isinstance(zhuangtai_list, list) or len(zhuangtai_list) == 0:
|
||
return Response({
|
||
'code': 3,
|
||
'msg': '状态列表不能为空',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
# 4. 构建基础查询(该商家的所有订单)
|
||
base_qs = DingdanShangjia.objects.filter(
|
||
shangjia_id=yonghuid
|
||
).select_related('dingdan')
|
||
|
||
# 5. 状态过滤
|
||
base_qs = base_qs.filter(dingdan__zhuangtai__in=zhuangtai_list)
|
||
|
||
# 🆕 6. 商品类型筛选
|
||
if leixing_id is not None:
|
||
try:
|
||
leixing_id_int = int(leixing_id)
|
||
base_qs = base_qs.filter(dingdan__leixing_id=leixing_id_int)
|
||
except (ValueError, TypeError):
|
||
# 如果leixing_id不是有效整数,忽略该筛选
|
||
pass
|
||
|
||
# 🆕 7. 模糊搜索(支持订单ID、订单介绍、游戏昵称、打手ID)
|
||
if keyword:
|
||
from django.db.models import Q
|
||
# 订单ID精确匹配(dingdan_id有唯一索引,先用精确匹配)
|
||
dingdan_id_match = Q(dingdan__dingdan_id__exact=keyword)
|
||
# 订单介绍模糊匹配
|
||
jieshao_match = Q(dingdan__jieshao__icontains=keyword)
|
||
# 游戏昵称模糊匹配
|
||
nicheng_match = Q(dingdan__nicheng__icontains=keyword)
|
||
# 打手ID精确匹配
|
||
dashou_match = Q(dingdan__jiedan_dashou_id__exact=keyword)
|
||
|
||
base_qs = base_qs.filter(
|
||
dingdan_id_match | jieshao_match | nicheng_match | dashou_match
|
||
)
|
||
|
||
# 8. 计算待结算订单数量(状态为8)
|
||
pending_count = DingdanShangjia.objects.filter(
|
||
shangjia_id=yonghuid,
|
||
dingdan__zhuangtai=8
|
||
).count()
|
||
|
||
# 9. 排序、分页
|
||
total_count = base_qs.count()
|
||
start_index = (page - 1) * page_size
|
||
end_index = start_index + page_size
|
||
current_page_orders = base_qs.order_by('-dingdan__create_time')[start_index:end_index]
|
||
|
||
# 10. 构建返回数据
|
||
order_list = []
|
||
for shangjia_order in current_page_orders:
|
||
order = shangjia_order.dingdan
|
||
order_data = {
|
||
'dingdan_id': order.dingdan_id,
|
||
'zhuangtai': order.zhuangtai,
|
||
'jine': float(order.jine) if order.jine else 0.0,
|
||
'jieshao': order.jieshao or '',
|
||
'beizhu': order.beizhu or '',
|
||
'nicheng': order.nicheng or '',
|
||
'leixing_id': order.leixing_id, # 🆕 返回商品类型ID
|
||
'tupian': order.tupian or '', # 🆕 返回商品图片
|
||
'jiedan_dashou_id': order.jiedan_dashou_id or '', # 🆕 返回打手ID
|
||
'create_time': order.create_time.strftime('%Y-%m-%d %H:%M:%S') if order.create_time else ''
|
||
}
|
||
order_list.append(order_data)
|
||
|
||
# 11. 判断是否还有更多
|
||
current_page_count = len(current_page_orders)
|
||
has_more = current_page_count == page_size and end_index < total_count
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '成功',
|
||
'data': {
|
||
'list': order_list,
|
||
'has_more': has_more,
|
||
'total': total_count,
|
||
'pending_count': pending_count
|
||
}
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
logger.error(f"商家订单获取失败: {str(e)}", exc_info=True)
|
||
return Response({
|
||
'code': 4,
|
||
'msg': '服务器内部错误',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
from dingdan.models import Dingdan, DingdanShangjia, Dashoutupian, Fadan
|
||
|
||
from .models import FadanFenhong # 注意:fadan_fenhong 可能在其他 app 中,根据实际路径调整
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class ShangjiaDingdanXiangqingView(APIView):
|
||
"""
|
||
商家订单详情接口
|
||
返回订单基础信息、打手信息、交付图片、罚款数据、分红利率、更换打手产生的新订单ID等
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
# 1. 获取当前登录用户的 yonghuid
|
||
try:
|
||
current_user = request.user
|
||
yonghuid = current_user.yonghuid
|
||
except AttributeError:
|
||
return Response({
|
||
'code': 1,
|
||
'msg': '用户认证失败',
|
||
'data': None
|
||
}, status=status.HTTP_401_UNAUTHORIZED)
|
||
|
||
# 2. 获取前端参数
|
||
try:
|
||
data = request.data
|
||
dingdan_id = data.get('dingdan_id', '').strip()
|
||
except Exception:
|
||
return Response({
|
||
'code': 2,
|
||
'msg': '参数格式错误',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if not dingdan_id:
|
||
return Response({
|
||
'code': 3,
|
||
'msg': '订单ID不能为空',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
# 3. 查询商家订单扩展表,验证权限并获取基础订单数据
|
||
shangjia_kuozhan = DingdanShangjia.objects.filter(
|
||
shangjia_id=yonghuid,
|
||
dingdan__dingdan_id=dingdan_id
|
||
).select_related('dingdan').first()
|
||
|
||
if not shangjia_kuozhan:
|
||
return Response({
|
||
'code': 4,
|
||
'msg': '订单不存在或您无权限查看',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
order = shangjia_kuozhan.dingdan
|
||
|
||
# 4. 交付图片列表
|
||
dashou_tupian_list = Dashoutupian.objects.filter(
|
||
dingdan_id=dingdan_id
|
||
).order_by('create_time').values_list('tupian', flat=True)
|
||
dashou_jiaofu = list(dashou_tupian_list)
|
||
|
||
# 5. 打手详细信息(头像、昵称、用户ID)
|
||
dashou_yonghuid = order.jiedan_dashou_id or ''
|
||
dashou_nicheng = ''
|
||
dashou_avatar = ''
|
||
if dashou_yonghuid:
|
||
dashou_user = UserMain.objects.filter(
|
||
yonghuid=dashou_yonghuid
|
||
).select_related('dashou_profile').first()
|
||
if dashou_user:
|
||
dashou_avatar = dashou_user.avatar or ''
|
||
if hasattr(dashou_user, 'dashou_profile') and dashou_user.dashou_profile:
|
||
dashou_nicheng = dashou_user.dashou_profile.nicheng or ''
|
||
|
||
# 6. 罚款数据(如果存在)
|
||
fadan_obj = Fadan.objects.filter(
|
||
guanliandingdan_id=dingdan_id,
|
||
beichufa_id=dashou_yonghuid
|
||
).first()
|
||
fadan_dict = None
|
||
if fadan_obj:
|
||
fadan_dict = {
|
||
'id': fadan_obj.id,
|
||
'chufaliyou': fadan_obj.chufaliyou or '',
|
||
'fakuanjine': float(fadan_obj.fakuanjine) if fadan_obj.fakuanjine else 0.0,
|
||
'yingxiang_qiangdan': fadan_obj.yingxiang_qiangdan,
|
||
'zhuangtai': fadan_obj.zhuangtai,
|
||
'shensuliyou': fadan_obj.shensuliyou or '',
|
||
'tongyi_chufaliyou': fadan_obj.tongyi_chufaliyou or '',
|
||
'bohuiliyou': fadan_obj.bohuiliyou or '',
|
||
'create_time': fadan_obj.create_time.strftime('%Y-%m-%d %H:%M:%S') if fadan_obj.create_time else '',
|
||
'update_time': fadan_obj.update_time.strftime('%Y-%m-%d %H:%M:%S') if fadan_obj.update_time else '',
|
||
}
|
||
|
||
# 7. 商家自己的分红利率(用于罚款分红计算)
|
||
fenhong_lilv = 0.0
|
||
try:
|
||
# 查找商家作为分红者的最新记录,取应得利率
|
||
last_fenhong = FadanFenhong.objects.filter(
|
||
fenhongzhe_id=yonghuid,
|
||
shenfen=1 # 1=商家
|
||
).order_by('-create_time').first()
|
||
if last_fenhong:
|
||
fenhong_lilv = float(last_fenhong.yingde_lilv or 0)
|
||
except Exception as e:
|
||
logger.warning(f'获取分红利率失败: {e}')
|
||
|
||
# 8. 更换打手后产生的新订单ID(如果当前订单被更换过)
|
||
xin_dingdan_id = ''
|
||
try:
|
||
# 查找以当前订单作为“上一个订单ID”的新订单扩展记录
|
||
new_shangjia_order = DingdanShangjia.objects.filter(
|
||
shangyige_dingdan_id=dingdan_id,
|
||
shangjia_id=yonghuid
|
||
).select_related('dingdan').first()
|
||
if new_shangjia_order:
|
||
xin_dingdan_id = new_shangjia_order.dingdan.dingdan_id
|
||
except Exception as e:
|
||
logger.warning(f'查找新订单ID失败: {e}')
|
||
|
||
# 9. 构建返回数据
|
||
response_data = {
|
||
# 基础订单字段
|
||
'dingdan_id': order.dingdan_id,
|
||
'zhuangtai': order.zhuangtai or 0,
|
||
'jine': float(order.jine) if order.jine else 0.0,
|
||
'jieshao': order.jieshao or '',
|
||
'beizhu': order.beizhu or '',
|
||
'nicheng': order.nicheng or '',
|
||
'create_time': order.create_time.strftime('%Y-%m-%d %H:%M:%S') if order.create_time else '',
|
||
|
||
# 打手信息
|
||
'dashou_yonghuid': dashou_yonghuid,
|
||
'dashou_nicheng': dashou_nicheng,
|
||
'dashou_avatar': dashou_avatar,
|
||
|
||
# 服务详情
|
||
'fuwudashou_id': dashou_yonghuid, # 保留旧字段兼容
|
||
'dashou_liuyan': order.dashou_liuyan or '',
|
||
'dashou_jiaofu': dashou_jiaofu,
|
||
|
||
# 商家扩展字段(旧)
|
||
'shangjia_liuyan': shangjia_kuozhan.shangjia_pingjia or '',
|
||
'tkliyou': order.tkly or '',
|
||
'cfliyou': shangjia_kuozhan.cfliyou or '', # 保留,但不影响新罚款
|
||
'sqzhuangtai': shangjia_kuozhan.sqzhuangtai or 0,
|
||
'bhliyou': shangjia_kuozhan.bhliyou or '',
|
||
'chufa_jieguo': '', # 已弃用
|
||
|
||
# 新增:罚款数据
|
||
'fadan': fadan_dict,
|
||
|
||
# 新增:分红利率
|
||
'fenhong_lilv': fenhong_lilv,
|
||
|
||
# 新增:更换打手后的新订单ID
|
||
'xin_dingdan_id': xin_dingdan_id,
|
||
}
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '成功',
|
||
'data': response_data
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
logger.error(f"商家订单详情查询失败: {str(e)}", exc_info=True)
|
||
return Response({
|
||
'code': 5,
|
||
'msg': '服务器内部错误',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
from dingdan.models import Dingdan, DingdanShangjia, PreSettlement
|
||
|
||
from dingdan.utils import update_daily_dispatch_stat
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
from dingdan.models import Dingdan, DingdanShangjia, PreSettlement
|
||
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
from houtai.utils import update_shangjia_daily
|
||
class ShangjiaJiesuanView(APIView):
|
||
"""商家结单接口(支持跨平台订单)"""
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
# 1. 获取当前登录用户
|
||
try:
|
||
current_user = request.user
|
||
yonghuid = current_user.yonghuid
|
||
|
||
try:
|
||
shangjia = current_user.shop_profile
|
||
except UserShangjia.DoesNotExist:
|
||
return Response({
|
||
'code': 1,
|
||
'msg': '您不是商家或商家信息未完善',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
except AttributeError:
|
||
return Response({
|
||
'code': 1,
|
||
'msg': '用户认证失败',
|
||
'data': None
|
||
}, status=status.HTTP_401_UNAUTHORIZED)
|
||
|
||
# 2. 获取前端参数
|
||
try:
|
||
data = request.data
|
||
dingdan_id = data.get('dingdan_id', '').strip()
|
||
pingfen = int(data.get('pingfen', 0))
|
||
liuyan = data.get('liuyan', '').strip()
|
||
except (ValueError, TypeError):
|
||
return Response({
|
||
'code': 2,
|
||
'msg': '参数格式错误',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 3. 参数验证
|
||
if not dingdan_id:
|
||
return Response({
|
||
'code': 3,
|
||
'msg': '订单ID不能为空',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if pingfen < 0 or pingfen > 5:
|
||
return Response({
|
||
'code': 4,
|
||
'msg': '评分必须在0-5分之间',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
settle_ctx = None
|
||
success_response = None
|
||
try:
|
||
with transaction.atomic():
|
||
# 4. 查询订单并加锁(修复并发问题)
|
||
order = Dingdan.objects.select_for_update().get(dingdan_id=dingdan_id)
|
||
|
||
# 验证订单是否属于当前商家
|
||
shangjia_kuozhan = DingdanShangjia.objects.filter(
|
||
dingdan=order,
|
||
shangjia_id=yonghuid
|
||
).first()
|
||
|
||
if not shangjia_kuozhan:
|
||
return Response({
|
||
'code': 5,
|
||
'msg': '订单不存在或您无权限操作',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 5. 验证订单状态
|
||
if order.zhuangtai != 8:
|
||
return Response({
|
||
'code': 6,
|
||
'msg': '订单不在结算状态,无法结单',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 获取订单字段
|
||
jine = order.jine or Decimal('0')
|
||
dashou_fencheng = order.dashou_fencheng or Decimal('0')
|
||
jiedan_dashou_id = order.jiedan_dashou_id
|
||
dispatch_type = order.dispatch_type # 1=我方派单,2=对方派单
|
||
is_cross = order.is_cross
|
||
partner_club_id = order.partner_club_id
|
||
partner_order_id = order.partner_order_id
|
||
|
||
if not jiedan_dashou_id:
|
||
return Response({
|
||
'code': 7,
|
||
'msg': '订单没有接单打手,无法结单',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# ========== 跨平台判断 ==========
|
||
is_cross_claimed_by_partner = (is_cross == 1 and (partner_club_id or partner_order_id))
|
||
|
||
# ========== 根据订单类型处理 ==========
|
||
# 情况1:对方派单(我方打手接单)-> 预结算,不直接给打手加钱
|
||
if dispatch_type == 2:
|
||
# 创建预结算记录
|
||
PreSettlement.objects.update_or_create(
|
||
dingdan_id=dingdan_id,
|
||
defaults={
|
||
'partner_order_id': partner_order_id,
|
||
'partner_club_id': partner_club_id or '000001',
|
||
'dashou_id': jiedan_dashou_id,
|
||
'amount': dashou_fencheng,
|
||
'order_amount': jine,
|
||
'status': 0,
|
||
'operator': request.user.phone,
|
||
}
|
||
)
|
||
# 更新订单状态
|
||
order.zhuangtai = 3
|
||
order.save()
|
||
|
||
# 更新统计表
|
||
update_daily_dispatch_stat(
|
||
direction=2,
|
||
partner_club_id=partner_club_id or '000001',
|
||
success_amount=jine,
|
||
success_count=1
|
||
)
|
||
|
||
# 情况2:我方派单且被对方打手接单 -> 只改状态,不给本地打手加钱
|
||
elif dispatch_type == 1 and is_cross_claimed_by_partner:
|
||
order.zhuangtai = 3
|
||
order.save()
|
||
|
||
update_daily_dispatch_stat(
|
||
direction=1,
|
||
partner_club_id=partner_club_id or '000001',
|
||
success_amount=dashou_fencheng,
|
||
success_count=1
|
||
)
|
||
|
||
# 情况3:本地订单 或 跨平台但自家打手接单 -> 正常结算给打手
|
||
else:
|
||
# 查询打手信息
|
||
try:
|
||
dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id)
|
||
dashou = dashou_user.dashou_profile
|
||
except (UserMain.DoesNotExist, UserDashou.DoesNotExist):
|
||
return Response({
|
||
'code': 8,
|
||
'msg': '接单打手不存在或信息不完整',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 原子更新打手数据(使用 F 表达式)
|
||
UserDashou.objects.filter(id=dashou.id).update(
|
||
chengjiaozongliang=F('chengjiaozongliang') + 1,
|
||
yue=F('yue') + dashou_fencheng,
|
||
zonge=F('zonge') + dashou_fencheng,
|
||
jinrishouyi=F('jinrishouyi') + dashou_fencheng,
|
||
jinyueshouyi=F('jinyueshouyi') + dashou_fencheng,
|
||
)
|
||
|
||
# 更新商家成交单量
|
||
UserShangjia.objects.filter(id=shangjia.id).update(
|
||
chengjiao=F('chengjiao') + 1,
|
||
)
|
||
|
||
# 更新订单状态
|
||
order.zhuangtai = 3
|
||
order.save()
|
||
|
||
# 处理评分(所有情况都处理)
|
||
if pingfen > 0:
|
||
pingfen_obj, created = Pingfen.objects.get_or_create(
|
||
dashou=jiedan_dashou_id,
|
||
defaults={'zongfen': pingfen, 'cishu': 1}
|
||
)
|
||
if not created:
|
||
Pingfen.objects.filter(id=pingfen_obj.id).update(
|
||
zongfen=F('zongfen') + pingfen,
|
||
cishu=F('cishu') + 1,
|
||
)
|
||
|
||
# 更新商家订单扩展表的评价
|
||
DingdanShangjia.objects.filter(id=shangjia_kuozhan.id).update(
|
||
shangjia_pingjia=liuyan,
|
||
)
|
||
|
||
# 🔥 新增:调用打手每日统计(成交)
|
||
try:
|
||
update_dashou_daily_by_action(
|
||
yonghuid=jiedan_dashou_id,
|
||
amount=dashou_fencheng,
|
||
action=2 # 2 表示成交(结算)
|
||
)
|
||
logger.info(f"打手 {jiedan_dashou_id} 每日统计更新成功")
|
||
except Exception as e:
|
||
logger.error(f"打手每日统计更新失败: {e}")
|
||
# 不影响主流程
|
||
|
||
# ✅ 新方法(行为驱动,更安全)
|
||
update_shangjia_daily(
|
||
yonghuid=request.user.yonghuid,
|
||
amount=Decimal(str(jine)),
|
||
action=2 # 2 = 结算
|
||
)
|
||
|
||
settle_ctx = (dingdan_id, jiedan_dashou_id)
|
||
success_response = Response({
|
||
'code': 0,
|
||
'msg': '结单成功',
|
||
'data': {
|
||
'dingdan_id': dingdan_id,
|
||
'new_zhuangtai': 3,
|
||
'jine': float(jine),
|
||
'dashou_fencheng': float(dashou_fencheng),
|
||
'jiedan_dashou_id': jiedan_dashou_id,
|
||
'pingfen': pingfen,
|
||
'liuyan': liuyan
|
||
}
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
if settle_ctx:
|
||
from dingdan.utils import settle_shangjia_order_guanshi_fenhong
|
||
settle_order = Dingdan.objects.get(dingdan_id=settle_ctx[0])
|
||
settle_shangjia_order_guanshi_fenhong(settle_order, settle_ctx[1])
|
||
|
||
if success_response is not None:
|
||
return success_response
|
||
|
||
except Dingdan.DoesNotExist:
|
||
return Response({
|
||
'code': 5,
|
||
'msg': '订单不存在',
|
||
'data': None
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
except Exception as e:
|
||
logger.error(f"商家结单失败: {str(e)}", exc_info=True)
|
||
return Response({
|
||
'code': 9,
|
||
'msg': '服务器内部错误,结单失败',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
from dingdan.models import Dingdan, DingdanShangjia, Tuikuanjilu
|
||
|
||
|
||
class ShangjiaChexiaoView(APIView):
|
||
"""商家撤销订单申请接口"""
|
||
permission_classes = [IsAuthenticated] # JWT认证
|
||
|
||
def post(self, request):
|
||
# 1. 获取当前登录用户并验证商家身份
|
||
try:
|
||
current_user = request.user
|
||
# 使用反向查询获取商家扩展信息(性能最佳)
|
||
shangjia = current_user.shop_profile
|
||
except UserShangjia.DoesNotExist:
|
||
return Response({
|
||
'code': 1,
|
||
'msg': '您不是商家或商家信息未完善',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 2. 检查商家账号状态
|
||
if shangjia.zhuangtai != 1:
|
||
return Response({
|
||
'code': 2,
|
||
'msg': '商家账号状态异常,无法操作',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 3. 获取前端参数
|
||
try:
|
||
data = request.data
|
||
dingdan_id = data.get('dingdan_id', '').strip()
|
||
chexiao_liyou = data.get('chexiao_liyou', '').strip()
|
||
except Exception:
|
||
return Response({
|
||
'code': 3,
|
||
'msg': '参数格式错误',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 4. 参数验证
|
||
if not dingdan_id or not chexiao_liyou:
|
||
return Response({
|
||
'code': 4,
|
||
'msg': '订单ID和撤销原因不能为空',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
# 5. 使用事务确保数据一致性
|
||
with transaction.atomic():
|
||
# 查询订单主表
|
||
dingdan = Dingdan.objects.filter(dingdan_id=dingdan_id).first()
|
||
if not dingdan:
|
||
return Response({
|
||
'code': 5,
|
||
'msg': '订单不存在',
|
||
'data': None
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 6. 验证订单状态
|
||
if dingdan.zhuangtai not in [1, 7]:
|
||
return Response({
|
||
'code': 6,
|
||
'msg': '当前订单状态不可撤销',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 7. 验证订单所属商家(使用反向查询)
|
||
shangjia_kuozhan = DingdanShangjia.objects.filter(
|
||
dingdan__dingdan_id=dingdan_id,
|
||
shangjia_id=current_user.yonghuid
|
||
).first()
|
||
|
||
if not shangjia_kuozhan:
|
||
return Response({
|
||
'code': 7,
|
||
'msg': '您无权撤销此订单',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 8. 创建退款记录(注意:申请状态直接设为1-成功)
|
||
Tuikuanjilu.objects.create(
|
||
dingdan_id=dingdan_id,
|
||
qingqiuid=current_user.yonghuid,
|
||
liyou=chexiao_liyou,
|
||
sqzhuangtai=1, # 直接成功
|
||
jine=dingdan.jine,
|
||
dashou_fencheng=dingdan.dashou_fencheng,
|
||
jieshao=dingdan.jieshao,
|
||
beizhu=dingdan.beizhu,
|
||
nicheng=dingdan.nicheng,
|
||
|
||
)
|
||
|
||
# 9. 更新商家扩展表数据(使用F表达式原子操作)
|
||
UserShangjia.objects.filter(id=shangjia.id).update(
|
||
yue=F('yue') + dingdan.jine,
|
||
tuikuan=F('tuikuan') + 1,
|
||
jinridingdan=F('jinridingdan') - 1,
|
||
jinyuedingdan=F('jinyuedingdan') - 1,
|
||
jinriliushui=F('jinriliushui') - dingdan.jine,
|
||
jinyueliushui=F('jinyueliushui') - dingdan.jine,
|
||
|
||
)
|
||
|
||
# 10. 更新订单状态为5(已退款)
|
||
dingdan.zhuangtai = 5
|
||
|
||
dingdan.save()
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '撤销成功',
|
||
'data': {
|
||
'dingdan_id': dingdan_id,
|
||
'new_zhuangtai': 5,
|
||
'tuikuan_liyou': chexiao_liyou,
|
||
'jine': float(dingdan.jine) if dingdan.jine else 0.0
|
||
}
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
# 记录异常日志
|
||
import logging
|
||
logger = logging.getLogger(__name__)
|
||
logger.error(f"商家撤销订单失败: {str(e)}", exc_info=True)
|
||
|
||
return Response({
|
||
'code': 8,
|
||
'msg': '服务器内部错误,撤销失败',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
class ShangjiaTuikuanShenqingView(APIView):
|
||
"""商家退款申请接口"""
|
||
permission_classes = [IsAuthenticated] # JWT认证
|
||
|
||
def post(self, request):
|
||
# 1. 获取当前登录用户并验证商家身份
|
||
try:
|
||
current_user = request.user
|
||
shangjia = current_user.shop_profile
|
||
except UserShangjia.DoesNotExist:
|
||
return Response({
|
||
'code': 1,
|
||
'msg': '您不是商家或商家信息未完善',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 2. 检查商家账号状态
|
||
if shangjia.zhuangtai != 1:
|
||
return Response({
|
||
'code': 2,
|
||
'msg': '商家账号状态异常,无法操作',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 3. 获取前端参数
|
||
try:
|
||
data = request.data
|
||
dingdan_id = data.get('dingdan_id', '').strip()
|
||
tuikuan_liyou = data.get('tuikuan_liyou', '').strip()
|
||
except Exception:
|
||
return Response({
|
||
'code': 3,
|
||
'msg': '参数格式错误',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 4. 参数验证
|
||
if not dingdan_id or not tuikuan_liyou:
|
||
return Response({
|
||
'code': 4,
|
||
'msg': '订单ID和退款原因不能为空',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
# 5. 使用事务确保数据一致性
|
||
with transaction.atomic():
|
||
# 查询订单主表
|
||
dingdan = Dingdan.objects.filter(dingdan_id=dingdan_id).first()
|
||
if not dingdan:
|
||
return Response({
|
||
'code': 5,
|
||
'msg': '订单不存在',
|
||
'data': None
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 6. 验证订单状态
|
||
if dingdan.zhuangtai not in [2, 8]:
|
||
return Response({
|
||
'code': 6,
|
||
'msg': '当前订单状态不可申请退款',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 7. 验证订单所属商家
|
||
shangjia_kuozhan = DingdanShangjia.objects.filter(
|
||
dingdan__dingdan_id=dingdan_id,
|
||
shangjia_id=current_user.yonghuid
|
||
).first()
|
||
|
||
if not shangjia_kuozhan:
|
||
return Response({
|
||
'code': 7,
|
||
'msg': '您无权为此订单申请退款',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 8. 创建退款记录(申请状态为0-待处理)
|
||
Tuikuanjilu.objects.create(
|
||
dingdan_id=dingdan_id,
|
||
dashouid=dingdan.jiedan_dashou_id,
|
||
qingqiuid=current_user.yonghuid,
|
||
liyou=tuikuan_liyou,
|
||
sqzhuangtai=0, # 待处理
|
||
jine=dingdan.jine,
|
||
dashou_fencheng=dingdan.dashou_fencheng,
|
||
jieshao=dingdan.jieshao,
|
||
beizhu=dingdan.beizhu,
|
||
nicheng=dingdan.nicheng,
|
||
)
|
||
|
||
# 9. 更新订单状态为4(退款中)
|
||
dingdan.zhuangtai = 4
|
||
dingdan.tkly = tuikuan_liyou
|
||
dingdan.save()
|
||
|
||
# ====== 原有功能:更新打手状态 ======
|
||
if dingdan.jiedan_dashou_id:
|
||
try:
|
||
dashou_main = UserMain.objects.filter(
|
||
yonghuid=dingdan.jiedan_dashou_id
|
||
).first()
|
||
if dashou_main and hasattr(dashou_main, 'dashou_profile'):
|
||
dashou_profile = dashou_main.dashou_profile
|
||
dashou_profile.zhuangtai = 1
|
||
dashou_profile.save()
|
||
logger.info(f"退款申请成功,打手状态已更新:订单{dingdan_id},打手{dingdan.jiedan_dashou_id}状态变更为1")
|
||
except Exception as e:
|
||
logger.error(f"更新打手状态失败:{str(e)},订单{dingdan_id},打手ID{dingdan.jiedan_dashou_id}")
|
||
|
||
# ========== 新增:如果是跨平台订单,异步通知对方平台 ==========
|
||
if dingdan.is_cross == 1:
|
||
import threading
|
||
import requests
|
||
from django.conf import settings
|
||
|
||
def send_refund_notify():
|
||
url = f"{settings.PARTNER_BASE_URL}{settings.PARTNER_REFUND_NOTIFY_URL}"
|
||
payload = {
|
||
'token': settings.CROSS_PLATFORM_TOKEN,
|
||
'order_id': dingdan.dingdan_id # 发送我方订单ID,对方用 partner_order_id 查找
|
||
}
|
||
try:
|
||
resp = requests.post(url, json=payload, timeout=5)
|
||
if resp.status_code == 200:
|
||
data = resp.json()
|
||
if data.get('code') == 0:
|
||
logger.info(f"跨平台退款通知发送成功: {dingdan.dingdan_id}")
|
||
else:
|
||
logger.error(f"跨平台退款通知业务失败: {data}")
|
||
else:
|
||
logger.error(f"跨平台退款通知HTTP失败: {resp.status_code}")
|
||
except Exception as e:
|
||
logger.error(f"跨平台退款通知异常: {str(e)}", exc_info=True)
|
||
|
||
thread = threading.Thread(target=send_refund_notify)
|
||
thread.daemon = True
|
||
thread.start()
|
||
logger.info(f"跨平台退款通知线程已启动: {dingdan.dingdan_id}")
|
||
# ========== 新增结束 ==========
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '退款申请提交成功',
|
||
'data': {
|
||
'dingdan_id': dingdan_id,
|
||
'new_zhuangtai': 4,
|
||
'tuikuan_liyou': tuikuan_liyou,
|
||
'jine': float(dingdan.jine) if dingdan.jine else 0.0,
|
||
'dashou_id': dingdan.jiedan_dashou_id
|
||
}
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
logger.error(f"商家退款申请失败: {str(e)}", exc_info=True)
|
||
return Response({
|
||
'code': 8,
|
||
'msg': '服务器内部错误,申请失败',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
class ShangjiaChufaShenqingView(APIView):
|
||
"""商家处罚申请接口(已添加图片上传功能)"""
|
||
permission_classes = [IsAuthenticated] # JWT认证
|
||
|
||
def post(self, request):
|
||
# 1. 获取当前登录用户并验证商家身份
|
||
try:
|
||
current_user = request.user
|
||
shangjia = current_user.shop_profile
|
||
except UserShangjia.DoesNotExist:
|
||
return Response({
|
||
'code': 1,
|
||
'msg': '您不是商家或商家信息未完善',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 2. 检查商家账号状态
|
||
if shangjia.zhuangtai != 1:
|
||
return Response({
|
||
'code': 2,
|
||
'msg': '商家账号状态异常,无法操作',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 3. 获取前端参数
|
||
try:
|
||
data = request.data
|
||
dingdan_id = data.get('dingdan_id', '').strip()
|
||
dashou_id = data.get('dashou_id', '').strip()
|
||
chufa_liyou = data.get('chufa_liyou', '').strip()
|
||
chufa_tupian_urls = data.get('chufa_tupian_urls', []) # 🔴 新增:图片URL数组
|
||
except Exception:
|
||
return Response({
|
||
'code': 3,
|
||
'msg': '参数格式错误',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 4. 参数验证
|
||
if not dingdan_id or not dashou_id or not chufa_liyou:
|
||
return Response({
|
||
'code': 4,
|
||
'msg': '订单ID、打手ID和处罚原因不能为空',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
# 5. 使用事务确保数据一致性
|
||
with transaction.atomic():
|
||
# 验证打手身份
|
||
try:
|
||
dashou_user = UserMain.objects.get(yonghuid=dashou_id)
|
||
dashou = dashou_user.dashou_profile
|
||
except UserMain.DoesNotExist:
|
||
return Response({
|
||
'code': 5,
|
||
'msg': '打手用户不存在',
|
||
'data': None
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
except UserDashou.DoesNotExist:
|
||
return Response({
|
||
'code': 5,
|
||
'msg': '该用户不是打手',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 查询订单主表
|
||
dingdan = Dingdan.objects.filter(dingdan_id=dingdan_id).first()
|
||
if not dingdan:
|
||
return Response({
|
||
'code': 6,
|
||
'msg': '订单不存在',
|
||
'data': None
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 验证订单状态
|
||
if dingdan.zhuangtai not in [2, 8, 4, 5, 6, 3]:
|
||
return Response({
|
||
'code': 7,
|
||
'msg': '当前订单状态不可申请处罚',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 验证订单所属商家
|
||
shangjia_kuozhan = DingdanShangjia.objects.filter(
|
||
dingdan__dingdan_id=dingdan_id,
|
||
shangjia_id=current_user.yonghuid
|
||
).first()
|
||
|
||
if not shangjia_kuozhan:
|
||
return Response({
|
||
'code': 8,
|
||
'msg': '您无权为此订单申请处罚',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 验证订单的打手是否与提供的打手ID一致
|
||
if dingdan.jiedan_dashou_id != dashou_id:
|
||
return Response({
|
||
'code': 9,
|
||
'msg': '该打手并非此订单的服务打手',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 创建处罚记录
|
||
chufajilu = Chufajilu.objects.create(
|
||
dashouid=dashou_id,
|
||
qingqiuid=current_user.yonghuid,
|
||
cfliyou=chufa_liyou,
|
||
sqzhuangtai=0, # 待处理
|
||
jifen=0,
|
||
dingdan_id=dingdan_id,
|
||
)
|
||
|
||
# 🔴【新增】保存处罚图片到数据库
|
||
if chufa_tupian_urls and isinstance(chufa_tupian_urls, list):
|
||
for tupian_url in chufa_tupian_urls:
|
||
if tupian_url and tupian_url.strip():
|
||
Chufatupian.objects.create(
|
||
dingdan_id=dingdan_id,
|
||
yonghuid=current_user.yonghuid,
|
||
tupian=tupian_url
|
||
)
|
||
|
||
# 更新商家订单扩展表的处罚相关字段
|
||
shangjia_kuozhan.cfliyou = chufa_liyou
|
||
shangjia_kuozhan.sqzhuangtai = 0
|
||
shangjia_kuozhan.save()
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '处罚申请提交成功',
|
||
'data': {
|
||
'dingdan_id': dingdan_id,
|
||
'dashou_id': dashou_id,
|
||
'chufa_liyou': chufa_liyou,
|
||
'sqzhuangtai': 0,
|
||
'jifen': 0,
|
||
'tupian_count': len(chufa_tupian_urls) if chufa_tupian_urls else 0 # 🔴 返回图片数量
|
||
}
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
import logging
|
||
logger = logging.getLogger(__name__)
|
||
logger.error(f"商家处罚申请失败: {str(e)}", exc_info=True)
|
||
|
||
return Response({
|
||
'code': 10,
|
||
'msg': '服务器内部错误,申请失败',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
from django.db.models import Q, OuterRef, Subquery, F, Prefetch
|
||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||
|
||
|
||
from dengji.models import DingdanBiaoqian
|
||
from dengji.models import YonghuChenghao, Chenghao
|
||
|
||
class DashouDingdanHuoquView(APIView):
|
||
"""
|
||
获取可抢订单列表(支持标签筛选)
|
||
POST /dingdan/ddhq
|
||
需要JWT Token认证
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 获取请求参数
|
||
data = request.data
|
||
leixing_id = data.get('leixing_id')
|
||
page = int(data.get('page', 1))
|
||
page_size = int(data.get('page_size', 10))
|
||
biaoqian_id = data.get('biaoqian_id')
|
||
|
||
# 2. 构建基础查询条件
|
||
base_query = Q(zhuangtai__in=[1, 7])
|
||
if leixing_id:
|
||
base_query &= Q(leixing_id=leixing_id)
|
||
|
||
# 3. 标签筛选
|
||
if biaoqian_id and int(biaoqian_id) > 0:
|
||
base_query &= Q(dingdanbiaoqian__chenghao_id=int(biaoqian_id))
|
||
|
||
# 4. 构建基础查询集
|
||
base_qs = Dingdan.objects.filter(base_query).order_by('-create_time').distinct()
|
||
|
||
# 5. 获取总记录数
|
||
total = base_qs.count()
|
||
|
||
# 6. 计算分页偏移量
|
||
offset = (page - 1) * page_size
|
||
limit = page_size
|
||
|
||
# 7. 获取当前页数据,并通过 select_related 预取商家扩展表(反向关联)
|
||
page_qs = base_qs[offset:offset + limit].select_related('shangjia_kuozhan')
|
||
|
||
# 8. 获取当前页所有需要的字段
|
||
dingdan_values = page_qs.values(
|
||
'dingdan_id', 'zhuangtai', 'fadan_pingtai', 'dashou_fencheng',
|
||
'zhiding_id', 'leixing_id', 'tupian', 'yaoqiuleixing',
|
||
'huiyuan_id', 'yongjin', 'jieshao', 'beizhu', 'create_time',
|
||
'shangjia_kuozhan__sjnicheng', # 商家昵称
|
||
'shangjia_kuozhan__shangjia_id', # 商家ID
|
||
)
|
||
|
||
# 9. 转换为列表并格式化
|
||
dingdan_list = []
|
||
for order in dingdan_values:
|
||
dingdan_list.append({
|
||
'dingdan_id': order['dingdan_id'],
|
||
'zhuangtai': order['zhuangtai'],
|
||
'pingtai': order['fadan_pingtai'],
|
||
'fadan_pingtai': order['fadan_pingtai'],
|
||
'dashou_fencheng': float(order['dashou_fencheng']) if order['dashou_fencheng'] else 0.0,
|
||
'zhiding_id': order['zhiding_id'] or '',
|
||
'leixing_id': order['leixing_id'] or 0,
|
||
'tupian': order['tupian'] or '',
|
||
'yaoqiuleixing': order['yaoqiuleixing'] or 0,
|
||
'huiyuan_id': order['huiyuan_id'] or '',
|
||
'yajin': float(order['yongjin']) if order['yongjin'] else 0.0,
|
||
'yongjin': float(order['yongjin']) if order['yongjin'] else 0.0,
|
||
'jieshao': order['jieshao'] or '',
|
||
'beizhu': order['beizhu'] or '',
|
||
'creat_time': order['create_time'].strftime('%Y-%m-%d %H:%M:%S') if order['create_time'] else '',
|
||
'sjnicheng': order['shangjia_kuozhan__sjnicheng'] or '',
|
||
'sj_id': order['shangjia_kuozhan__shangjia_id'] or '',
|
||
# 其他字段后面补充
|
||
})
|
||
|
||
# 10. 批量获取指定打手的头像和昵称
|
||
zhiding_ids = {item['zhiding_id'] for item in dingdan_list if item.get('zhiding_id')}
|
||
zhiding_info = {}
|
||
if zhiding_ids:
|
||
dashou_query = UserMain.objects.filter(
|
||
yonghuid__in=zhiding_ids
|
||
).select_related('dashou_profile').annotate(
|
||
nicheng=F('dashou_profile__nicheng')
|
||
).values('yonghuid', 'avatar', 'nicheng')
|
||
zhiding_info = {
|
||
item['yonghuid']: {
|
||
'avatar': item['avatar'] or '',
|
||
'nicheng': item['nicheng'] or ''
|
||
}
|
||
for item in dashou_query
|
||
}
|
||
|
||
# 11. 批量获取三层标签(不变)
|
||
order_ids = [item['dingdan_id'] for item in dingdan_list]
|
||
|
||
# --- 订单需求标签 ---
|
||
xuqiu_tags_map = {}
|
||
if order_ids:
|
||
try:
|
||
dbq_qs = DingdanBiaoqian.objects.filter(
|
||
dingdan_id__in=order_ids
|
||
).select_related('chenghao')
|
||
for dbq in dbq_qs:
|
||
oid = dbq.dingdan_id
|
||
if oid not in xuqiu_tags_map:
|
||
xuqiu_tags_map[oid] = []
|
||
ch = dbq.chenghao
|
||
xuqiu_tags_map[oid].append({
|
||
'id': ch.id,
|
||
'mingcheng': ch.mingcheng,
|
||
'texiao_json': self._parse_texiao_json(ch.texiao_miaoshu)
|
||
})
|
||
except Exception as e:
|
||
logger.error(f"查询订单需求标签失败: {e}", exc_info=True)
|
||
|
||
# --- 用户标签(打手+商家) ---
|
||
yonghuid_set = set(zhiding_ids)
|
||
# 商家ID从订单列表中提取(已含 sj_id)
|
||
for item in dingdan_list:
|
||
if item['fadan_pingtai'] == 2 and item.get('sj_id'):
|
||
yonghuid_set.add(item['sj_id'])
|
||
|
||
user_id_map = {}
|
||
if yonghuid_set:
|
||
users = UserMain.objects.filter(yonghuid__in=yonghuid_set).values('id', 'yonghuid')
|
||
for u in users:
|
||
user_id_map[u['yonghuid']] = u['id']
|
||
|
||
user_main_ids = list(user_id_map.values())
|
||
user_tags_map = {}
|
||
if user_main_ids:
|
||
uc_qs = YonghuChenghao.objects.filter(
|
||
yonghu_id__in=user_main_ids
|
||
).select_related('chenghao')
|
||
for uc in uc_qs:
|
||
uid = uc.yonghu_id
|
||
if uid not in user_tags_map:
|
||
user_tags_map[uid] = []
|
||
user_tags_map[uid].append(uc.chenghao)
|
||
|
||
# 为每个订单组装标签
|
||
dashou_tags_map = {}
|
||
shangjia_tags_map = {}
|
||
for item in dingdan_list:
|
||
oid = item['dingdan_id']
|
||
# 打手标签
|
||
zhid = item['zhiding_id']
|
||
dashou_tags = []
|
||
if zhid and zhid in user_id_map:
|
||
uid = user_id_map[zhid]
|
||
if uid in user_tags_map:
|
||
for ch in user_tags_map[uid]:
|
||
if ch.leixing == 'dashou':
|
||
dashou_tags.append({
|
||
'id': ch.id,
|
||
'mingcheng': ch.mingcheng,
|
||
'texiao_json': self._parse_texiao_json(ch.texiao_miaoshu)
|
||
})
|
||
dashou_tags_map[oid] = dashou_tags
|
||
|
||
# 商家标签
|
||
sj_tags = []
|
||
if item['fadan_pingtai'] == 2:
|
||
sj_yonghuid = item.get('sj_id')
|
||
if sj_yonghuid and sj_yonghuid in user_id_map:
|
||
uid = user_id_map[sj_yonghuid]
|
||
if uid in user_tags_map:
|
||
for ch in user_tags_map[uid]:
|
||
if ch.leixing == 'shangjia':
|
||
sj_tags.append({
|
||
'id': ch.id,
|
||
'mingcheng': ch.mingcheng,
|
||
'texiao_json': self._parse_texiao_json(ch.texiao_miaoshu)
|
||
})
|
||
shangjia_tags_map[oid] = sj_tags
|
||
|
||
# 12. 组装最终返回数据
|
||
formatted_list = []
|
||
for item in dingdan_list:
|
||
oid = item['dingdan_id']
|
||
zid = item.get('zhiding_id')
|
||
info = zhiding_info.get(zid, {})
|
||
formatted_list.append({
|
||
**item,
|
||
'zhiding_uid': zid,
|
||
'zhiding_avatar': info.get('avatar', ''),
|
||
'zhiding_nicheng': info.get('nicheng', ''),
|
||
'xuqiu_biaoqian': xuqiu_tags_map.get(oid, []),
|
||
'dashou_biaoqian': dashou_tags_map.get(oid, []),
|
||
'shangjia_biaoqian': shangjia_tags_map.get(oid, []),
|
||
})
|
||
|
||
has_more = (page * page_size) < total
|
||
|
||
return Response({
|
||
'code': 200,
|
||
'msg': '获取成功',
|
||
'data': {
|
||
'list': formatted_list,
|
||
'has_more': has_more,
|
||
'total': total,
|
||
'current_page': page,
|
||
'page_size': page_size,
|
||
'total_pages': (total + page_size - 1) // page_size,
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"订单列表查询失败: {e}", exc_info=True)
|
||
return Response({'code': 500, 'msg': f'服务器内部错误: {str(e)}'}, status=500)
|
||
|
||
def _parse_texiao_json(self, texiao_miaoshu):
|
||
if not texiao_miaoshu:
|
||
return {}
|
||
try:
|
||
return json.loads(texiao_miaoshu)
|
||
except:
|
||
return {}
|
||
|
||
|
||
|
||
|
||
# apps/dingdan/views.py
|
||
from shangpin.models import ShangpinLeixing # 假设这是你的商品类型模型
|
||
|
||
from django.db.models import Q, OuterRef, Subquery, F, Prefetch
|
||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||
class ShangpinLeixingHuoquView(APIView):
|
||
"""
|
||
获取商品类型列表
|
||
POST /dingdan/ddlxhq
|
||
需要JWT Token认证
|
||
"""
|
||
authentication_classes = [JWTAuthentication]
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
# 1. 查询商品类型表
|
||
# 假设你的商品类型模型字段为:id, jieshao, tupian_url
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
# 2. 格式化返回数据
|
||
|
||
|
||
# 3. 返回响应
|
||
return Response({
|
||
'code': 200,
|
||
'msg': '获取成功',
|
||
'data': {
|
||
|
||
}
|
||
})
|
||
|
||
|
||
# apps/dingdan/views.py
|
||
from django.db import transaction
|
||
from django.utils import timezone
|
||
from django.db.models import F
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||
import logging
|
||
|
||
from .models import Dingdan, Liaotian
|
||
|
||
from utils.goeasy_service import GoEasyService
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
|
||
|
||
# apps/dingdan/views.py
|
||
from django.db import transaction
|
||
from django.utils import timezone
|
||
from django.db.models import F
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||
import logging
|
||
import requests
|
||
import json
|
||
|
||
from .models import Dingdan, DingdanPingtai, DingdanShangjia
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
from django.db import transaction
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from django.utils import timezone
|
||
from django.db.models import F
|
||
from django.conf import settings
|
||
import json
|
||
import requests
|
||
from datetime import datetime
|
||
|
||
|
||
|
||
|
||
|
||
from peizhi.models import Club
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
from peizhi.models import ClubConfig
|
||
from utils.chat_utils import establish_order_chat_with_retry
|
||
class QiangdanView(APIView):
|
||
authentication_classes = [JWTAuthentication]
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
data = request.data
|
||
dingdan_id = data.get('dingdan_id')
|
||
if not dingdan_id:
|
||
return Response({'code': 400, 'msg': '订单ID不能为空'}, status=400)
|
||
|
||
chat_success = False
|
||
try:
|
||
with transaction.atomic():
|
||
# 1. 验证打手身份和状态
|
||
dashou_profile = self._validate_dashou_status(request.user)
|
||
if not dashou_profile:
|
||
return Response({'code': 403, 'msg': '您不是打手或账号状态异常'}, status=403)
|
||
|
||
# ==================== 【新增】罚单检查 ====================
|
||
from utils.fadan_utils import check_fadan_qiangdan_eligible
|
||
fadan_ok, fadan_msg = check_fadan_qiangdan_eligible(request.user.yonghuid,1)
|
||
if not fadan_ok:
|
||
return Response({'code': 400, 'msg': fadan_msg})
|
||
# =========================================================
|
||
# ========== 标签快速校验(提前拒绝,避免锁资源) ==========
|
||
from dengji.utils import check_dashou_biaoqian_required
|
||
biaoqian_ok, biaoqian_msg = check_dashou_biaoqian_required(dingdan_id, request.user.yonghuid)
|
||
if not biaoqian_ok:
|
||
return Response({'code': 400, 'msg': biaoqian_msg})
|
||
|
||
# 2. 锁定并验证订单(行锁)
|
||
order_result = self._validate_and_lock_order(dingdan_id, request.user.yonghuid)
|
||
if isinstance(order_result, str):
|
||
return Response({'code': 400, 'msg': order_result})
|
||
order = order_result
|
||
|
||
is_cross = order.is_cross == 1
|
||
dispatch_type = order.dispatch_type
|
||
|
||
if is_cross and dispatch_type == 2:
|
||
validation_result = self._validate_order_requirements(order, dashou_profile, request.user.yonghuid)
|
||
if not validation_result['success']:
|
||
return Response({'code': 400, 'msg': validation_result['message']})
|
||
|
||
claim_allowed = self._request_partner_check_claim(order, request.user.yonghuid)
|
||
if claim_allowed is None:
|
||
return Response({'code': 500, 'msg': '跨平台通信失败,请重试'}, status=500)
|
||
elif claim_allowed is False:
|
||
if order.zhuangtai == 1:
|
||
order.zhuangtai = 21
|
||
order.save()
|
||
logger.info(f"订单 {dingdan_id} 被对方拒绝抢单,状态改为21")
|
||
else:
|
||
order.zhuangtai = 20
|
||
order.save()
|
||
logger.info(f"订单 {dingdan_id} 已被其他平台抢走,状态改为20")
|
||
return Response({'code': 400, 'msg': '订单已被其他平台打手抢走或不可抢'})
|
||
|
||
self._execute_qiangdan(order, dashou_profile, request.user.yonghuid)
|
||
|
||
try:
|
||
update_daily_dispatch_stat(
|
||
direction=2,
|
||
partner_club_id=order.partner_club_id or '000001',
|
||
claimed_amount=order.jine,
|
||
claimed_count=1
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"更新抢单统计失败: {e}", exc_info=True)
|
||
|
||
elif is_cross and dispatch_type == 1:
|
||
validation_result = self._validate_order_requirements(order, dashou_profile, request.user.yonghuid)
|
||
if not validation_result['success']:
|
||
return Response({'code': 400, 'msg': validation_result['message']})
|
||
|
||
self._execute_qiangdan(order, dashou_profile, request.user.yonghuid)
|
||
self._async_notify_partners_order_claimed(order)
|
||
|
||
else:
|
||
validation_result = self._validate_order_requirements(order, dashou_profile, request.user.yonghuid)
|
||
if not validation_result['success']:
|
||
return Response({'code': 400, 'msg': validation_result['message']})
|
||
self._execute_qiangdan(order, dashou_profile, request.user.yonghuid)
|
||
|
||
# 事务提交后再建群/建聊,避免长耗时 HTTP 拖住行锁,并支持重试
|
||
chat_success = establish_order_chat_with_retry(dingdan_id, max_rounds=5)
|
||
|
||
if not chat_success:
|
||
try:
|
||
from dingdan.tasks import retry_establish_order_chat_task
|
||
retry_establish_order_chat_task.apply_async(args=[dingdan_id], countdown=8)
|
||
retry_establish_order_chat_task.apply_async(args=[dingdan_id], countdown=30)
|
||
except Exception as chat_task_err:
|
||
logger.error(f"提交建聊补偿任务失败: {chat_task_err}")
|
||
|
||
try:
|
||
update_dashou_daily_by_action(
|
||
yonghuid=order.jiedan_dashou_id,
|
||
amount=order.dashou_fencheng,
|
||
action=1
|
||
)
|
||
logger.info(f"打手 {order.jiedan_dashou_id} 每日统计更新成功")
|
||
except Exception as e:
|
||
logger.error(f"打手每日统计更新失败: {e}")
|
||
|
||
return Response({
|
||
'code': 200,
|
||
'msg': '抢单成功',
|
||
'data': {
|
||
'dingdan_id': dingdan_id,
|
||
'chat_established': chat_success
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"抢单异常: {str(e)}", exc_info=True)
|
||
return Response({'code': 500, 'msg': '系统异常,请稍后重试'}, status=500)
|
||
|
||
# ---------- 以下所有方法原封不动照抄旧版 ----------
|
||
def _validate_dashou_status(self, user):
|
||
try:
|
||
dashou_profile = user.dashou_profile
|
||
if (dashou_profile.zhuangtai != 1 or
|
||
dashou_profile.zhanghaozhuangtai != 1):
|
||
return None
|
||
return dashou_profile
|
||
except UserDashou.DoesNotExist:
|
||
return None
|
||
|
||
def _validate_and_lock_order(self, dingdan_id, dashou_yonghuid):
|
||
try:
|
||
order = Dingdan.objects.select_for_update().get(dingdan_id=dingdan_id)
|
||
if order.zhuangtai not in [1, 7]:
|
||
return "订单状态不可抢"
|
||
if order.zhuangtai == 7:
|
||
if not order.zhiding_id or order.zhiding_id != dashou_yonghuid:
|
||
return "此订单为指定单,您不是指定打手"
|
||
if order.jiedan_dashou_id:
|
||
return "订单已被其他打手抢走"
|
||
return order
|
||
except Dingdan.DoesNotExist:
|
||
return "订单不存在"
|
||
|
||
def _validate_order_requirements(self, order, dashou_profile, dashou_yonghuid):
|
||
if order.yaoqiuleixing == 1:
|
||
return self._validate_huiyuan_requirement(order, dashou_yonghuid)
|
||
elif order.yaoqiuleixing == 2:
|
||
return self._validate_yajin_requirement(order, dashou_profile)
|
||
return {'success': True, 'message': '验证通过'}
|
||
|
||
def _validate_huiyuan_requirement(self, order, dashou_yonghuid):
|
||
try:
|
||
huiyuan_record = Huiyuangoumai.objects.get(
|
||
huiyuan_id=order.huiyuan_id,
|
||
yonghu_id=dashou_yonghuid
|
||
)
|
||
if huiyuan_record.jiance_shifou_daoqi() or huiyuan_record.huiyuan_zhuangtai != 1:
|
||
return {'success': False, 'message': '会员已过期或未开通对应会员'}
|
||
return {'success': True, 'message': '会员验证通过'}
|
||
except Huiyuangoumai.DoesNotExist:
|
||
return {'success': False, 'message': '未开通对应会员,无法抢此订单'}
|
||
|
||
def _validate_yajin_requirement(self, order, dashou_profile):
|
||
yajin_required = order.yongjin or 0
|
||
dashou_yajin = dashou_profile.yajin or 0
|
||
if dashou_yajin < yajin_required:
|
||
return {'success': False, 'message': f'押金不足,需要{yajin_required}元,当前有{dashou_yajin}元'}
|
||
return {'success': True, 'message': '押金验证通过'}
|
||
|
||
def _execute_qiangdan(self, order, dashou_profile, dashou_yonghuid):
|
||
order.zhuangtai = 2
|
||
order.jiedan_dashou_id = dashou_yonghuid
|
||
order.user2_id = f"Ds{dashou_yonghuid}"
|
||
order.save()
|
||
|
||
dashou_profile.zhuangtai = 0
|
||
dashou_profile.jiedanzongliang = F('jiedanzongliang') + 1
|
||
dashou_profile.jinrijiedan = F('jinrijiedan') + 1
|
||
dashou_profile.jinyuejiedan = F('jinyuejiedan') + 1
|
||
dashou_profile.save()
|
||
|
||
# ---------- 跨平台询问对方(照抄) ----------
|
||
def _request_partner_check_claim(self, order, dashou_id):
|
||
partner_club_id = order.partner_club_id or '000001'
|
||
try:
|
||
club = Club.objects.filter(partner_club_id=partner_club_id).first()
|
||
if not club or not club.partner_domain:
|
||
logger.error(f"未找到对方俱乐部配置: partner_club_id={partner_club_id}")
|
||
return None
|
||
|
||
my_club = Club.objects.filter(partner_club_id=club.partner_club_id).first()
|
||
if not my_club:
|
||
logger.error(f"未找到我方俱乐部配置: partner_club_id={club.partner_club_id}")
|
||
return None
|
||
|
||
payload = {
|
||
'token': settings.CROSS_PLATFORM_TOKEN,
|
||
'wofangdingdan_id': order.partner_order_id,
|
||
'partner_order_id': order.dingdan_id,
|
||
'our_club_id': my_club.club_id,
|
||
'partner_club_id': club.partner_club_id,
|
||
'dashou_id': dashou_id,
|
||
'sj_id': f"Sj{settings.AGENT_SHANGJIA_ID}"
|
||
}
|
||
|
||
url = club.partner_domain.rstrip('/') + '/dingdan/partner_check_claim'
|
||
resp = requests.post(url, json=payload, timeout=5)
|
||
|
||
if resp.status_code != 200:
|
||
logger.error(f"跨平台抢单询问HTTP失败: {resp.status_code}")
|
||
return None
|
||
|
||
data = resp.json()
|
||
if data.get('code') == 0:
|
||
return data.get('data', {}).get('can_claim') is True
|
||
else:
|
||
logger.error(f"跨平台抢单询问业务失败: {data}")
|
||
return False
|
||
except Exception as e:
|
||
logger.error(f"跨平台抢单询问异常: {e}", exc_info=True)
|
||
return None
|
||
|
||
# ---------- 异步通知其他俱乐部 ----------
|
||
def _async_notify_partners_order_claimed(self, order):
|
||
def notify_task():
|
||
clubs = Club.objects.filter(is_interop_enabled=True).select_related()
|
||
for club in clubs:
|
||
for attempt in range(1, 4):
|
||
success = self._send_claim_notification_to_partner(order, club)
|
||
if success:
|
||
break
|
||
if attempt < 3:
|
||
time.sleep(2 ** attempt)
|
||
else:
|
||
logger.error(f"通知俱乐部 {club.club_id} 订单已抢失败,重试3次后仍失败")
|
||
|
||
thread = threading.Thread(target=notify_task)
|
||
thread.daemon = True
|
||
thread.start()
|
||
|
||
def _send_claim_notification_to_partner(self, order, club):
|
||
try:
|
||
my_club = Club.objects.filter(partner_club_id=club.partner_club_id).first()
|
||
if not my_club:
|
||
logger.warning(f"未找到我方俱乐部配置: partner_club_id={club.partner_club_id}")
|
||
return False
|
||
|
||
payload = {
|
||
'token': settings.CROSS_PLATFORM_TOKEN,
|
||
'our_club_id': my_club.club_id,
|
||
'partner_club_id': club.partner_club_id,
|
||
'partner_order_id': order.dingdan_id,
|
||
'claimed': True
|
||
}
|
||
url = club.partner_domain.rstrip('/') + '/dingdan/partner_notify_claim'
|
||
resp = requests.post(url, json=payload, timeout=3)
|
||
return resp.status_code == 200
|
||
except Exception as e:
|
||
logger.error(f"通知俱乐部 {club.club_id} 异常: {e}")
|
||
return False
|
||
|
||
|
||
# dingdan/views.py
|
||
import logging
|
||
import requests
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework import status, permissions
|
||
from .models import Dingdan, DingdanPingtai, DingdanShangjia
|
||
from yonghu.models import UserMain, UserDashou, UserBoss, UserShangjia
|
||
from peizhi.models import ClubConfig
|
||
from utils.chat_utils import (
|
||
_send_group_message, _subscribe_users_to_group,
|
||
resolve_group_id_for_order, prepare_order_group_chat,
|
||
)
|
||
|
||
logger = logging.getLogger('dingdan')
|
||
|
||
|
||
# ----------------- 基础配置获取 -----------------
|
||
def get_self_goeasy_appkey():
|
||
try:
|
||
cfg = ClubConfig.objects.filter(is_self=1).first()
|
||
return cfg.chat_api_key if cfg else None
|
||
except Exception:
|
||
return None
|
||
|
||
|
||
def get_self_goeasy_secret():
|
||
try:
|
||
cfg = ClubConfig.objects.filter(is_self=1).first()
|
||
return cfg.chat_api_id if cfg else None
|
||
except Exception:
|
||
return None
|
||
|
||
|
||
def get_self_storage_domain():
|
||
try:
|
||
cfg = ClubConfig.objects.filter(is_self=1).first()
|
||
return cfg.storage_bucket_domain if cfg else ''
|
||
except Exception:
|
||
return ''
|
||
|
||
|
||
def full_avatar(relative_url, bucket_domain=None):
|
||
if not relative_url:
|
||
return ''
|
||
if relative_url.startswith('http'):
|
||
return relative_url
|
||
domain = bucket_domain or get_self_storage_domain()
|
||
if domain:
|
||
domain = domain.rstrip('/') + '/'
|
||
return domain + relative_url.lstrip('/')
|
||
return relative_url
|
||
|
||
|
||
# ----------------- GoEasy API 封装 -----------------
|
||
def subscribe_users_to_group(user_ids, group_ids, appkey=None, secret=None):
|
||
"""订阅用户到群组,确保能收发该群消息"""
|
||
if not appkey:
|
||
appkey = get_self_goeasy_appkey()
|
||
if not secret:
|
||
secret = get_self_goeasy_secret()
|
||
if not appkey:
|
||
logger.error("订阅失败:GoEasy AppKey 未配置")
|
||
return False
|
||
url = 'https://rest-hangzhou.goeasy.io/v2/im/subscribe-groups'
|
||
body = {"appkey": appkey, "userIds": user_ids, "groupIds": group_ids}
|
||
headers = {"Content-Type": "application/json"}
|
||
if secret:
|
||
headers["Authorization"] = f"Bearer {secret}"
|
||
try:
|
||
r = requests.post(url, headers=headers, json=body, timeout=10)
|
||
if r.status_code == 200:
|
||
logger.info(f"订阅成功:用户 {user_ids} -> 群 {group_ids}")
|
||
return True
|
||
logger.error(f"订阅失败:{r.status_code} {r.text}")
|
||
return False
|
||
except Exception as e:
|
||
logger.exception("订阅异常")
|
||
return False
|
||
|
||
|
||
def send_group_message(
|
||
appkey, secret, group_id, sender_id, sender_name, sender_avatar,
|
||
message_text='', custom_payload=None, group_name='', order_id='', is_cross=0
|
||
):
|
||
"""
|
||
通过 GoEasy REST API 发送群消息。
|
||
message_text: 普通文本内容
|
||
custom_payload: 自定义消息体(如订单卡片),此时 type 设为 'order'
|
||
"""
|
||
url = 'https://rest-hangzhou.goeasy.io/v2/im/message'
|
||
to_data = {
|
||
"name": group_name or group_id,
|
||
"avatar": sender_avatar or '',
|
||
"orderId": order_id,
|
||
"isCross": is_cross
|
||
}
|
||
msg_type = 'order' if custom_payload else 'text'
|
||
payload = custom_payload if custom_payload else message_text
|
||
|
||
body = {
|
||
"appkey": appkey,
|
||
"senderId": sender_id,
|
||
"senderData": {"avatar": sender_avatar, "name": sender_name},
|
||
"to": {"type": "group", "id": group_id, "data": to_data},
|
||
"type": msg_type,
|
||
"payload": payload
|
||
}
|
||
headers = {"Content-Type": "application/json"}
|
||
if secret:
|
||
headers["Authorization"] = f"Bearer {secret}"
|
||
|
||
for attempt in range(3): # 最多重试3次
|
||
try:
|
||
r = requests.post(url, headers=headers, json=body, timeout=10)
|
||
if r.status_code == 200:
|
||
logger.info(f"消息发送成功:sender={sender_id}, group={group_id}, type={msg_type}")
|
||
return True
|
||
logger.warning(f"消息发送失败 (尝试 {attempt+1}/3):状态码 {r.status_code}, 响应 {r.text}")
|
||
except Exception as e:
|
||
logger.warning(f"消息发送异常 (尝试 {attempt+1}/3):{e}")
|
||
if attempt < 2:
|
||
time.sleep(1)
|
||
logger.error(f"消息最终发送失败:sender={sender_id}, group={group_id}")
|
||
return False
|
||
|
||
|
||
# ----------------- 权限校验 -----------------
|
||
def check_user_permission(identity_type, uid, order):
|
||
"""验证用户身份、账号状态、以及是否属于该订单"""
|
||
try:
|
||
user = UserMain.objects.get(yonghuid=uid)
|
||
except UserMain.DoesNotExist:
|
||
return False, '用户不存在'
|
||
|
||
if identity_type == 'dashou':
|
||
try:
|
||
dashou = UserDashou.objects.get(user=user)
|
||
if dashou.zhanghaozhuangtai != 1 :
|
||
return False, '打手账号已被封禁或状态异常'
|
||
except UserDashou.DoesNotExist:
|
||
return False, '该用户不是打手'
|
||
if order.jiedan_dashou_id != uid:
|
||
return False, '你不是该订单的接单打手'
|
||
|
||
elif identity_type == 'shangjia':
|
||
try:
|
||
shop = UserShangjia.objects.get(user=user)
|
||
if shop.zhuangtai != 1:
|
||
return False, '商家账号已被禁用'
|
||
except UserShangjia.DoesNotExist:
|
||
return False, '该用户不是商家'
|
||
try:
|
||
if order.shangjia_kuozhan.shangjia_id != uid:
|
||
return False, '你不是该订单的发布商家'
|
||
except DingdanShangjia.DoesNotExist:
|
||
return False, '订单缺少商家信息'
|
||
|
||
elif identity_type == 'boss':
|
||
try:
|
||
UserBoss.objects.get(user=user) # 老板暂无封禁字段,仅需存在
|
||
except UserBoss.DoesNotExist:
|
||
return False, '该用户不是老板'
|
||
try:
|
||
if order.pingtai_kuozhan.laoban_id != uid:
|
||
return False, '你不是该订单的老板'
|
||
except DingdanPingtai.DoesNotExist:
|
||
return False, '订单缺少平台信息'
|
||
|
||
else:
|
||
return False, '不支持的身份类型'
|
||
return True, ''
|
||
|
||
|
||
# ----------------- 跨平台对方配置获取 -----------------
|
||
def get_partner_club_config(partner_club_id):
|
||
try:
|
||
return ClubConfig.objects.get(club_id=partner_club_id, is_self=0)
|
||
except ClubConfig.DoesNotExist:
|
||
return None
|
||
|
||
|
||
# ----------------- 主视图 -----------------
|
||
|
||
class DingdanLiaotianZhunbeiView(APIView):
|
||
"""订单详情进入聊天前:确保配对群订阅并返回 groupId"""
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
dingdan_id = request.data.get('dingdan_id') or request.data.get('orderId')
|
||
identity_type = request.data.get('identityType')
|
||
push_order_card = request.data.get('push_order_card', True)
|
||
if isinstance(push_order_card, str):
|
||
push_order_card = push_order_card.lower() in ('1', 'true', 'yes')
|
||
|
||
if not dingdan_id:
|
||
return Response({'code': 400, 'msg': '缺少 dingdan_id'})
|
||
if not identity_type:
|
||
return Response({'code': 400, 'msg': '缺少 identityType'})
|
||
if identity_type not in ('dashou', 'shangjia', 'boss'):
|
||
return Response({'code': 400, 'msg': 'identityType 无效'})
|
||
|
||
uid = request.user.yonghuid
|
||
try:
|
||
order = Dingdan.objects.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(dingdan_id=dingdan_id)
|
||
except Dingdan.DoesNotExist:
|
||
return Response({'code': 404, 'msg': '订单不存在'})
|
||
|
||
valid, err_msg = check_user_permission(identity_type, uid, order)
|
||
if not valid:
|
||
return Response({'code': 403, 'msg': err_msg})
|
||
|
||
prefix_map = {'dashou': 'Ds', 'shangjia': 'Sj', 'boss': 'Boss'}
|
||
caller_goeasy_id = prefix_map[identity_type] + uid
|
||
|
||
result = prepare_order_group_chat(dingdan_id, caller_goeasy_id, push_order_card=push_order_card)
|
||
if not result:
|
||
return Response({'code': 500, 'msg': '准备群聊失败,请稍后重试'})
|
||
|
||
return Response({'code': 0, 'msg': 'ok', 'data': result})
|
||
|
||
|
||
class DaiLiQunLiaoXiaoXiView(APIView):
|
||
"""
|
||
代理发送群聊消息
|
||
支持:
|
||
- 非跨平台订单:直接用我方 GoEasy Key 以真实身份发送
|
||
- 跨平台订单(双方任意一方派单):除了我方群外,还需用对方 GoEasy Key 以代理身份向对方群发送
|
||
跨平台判断条件:订单同时存在 partner_order_id 和 partner_club_id 即视为跨平台
|
||
"""
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
# ===== 1. 参数解析 =====
|
||
order_id = request.data.get('orderId')
|
||
identity_type = request.data.get('identityType') # dashou / shangjia / boss
|
||
sender_name = request.data.get('senderName', '')
|
||
sender_avatar_raw = request.data.get('senderAvatar', '')
|
||
message_type = request.data.get('messageType', 'text')
|
||
message_text = request.data.get('text', '')
|
||
order_payload = request.data.get('orderPayload', None)
|
||
|
||
logger.info(f"[群聊代理] 收到请求: orderId={order_id}, identityType={identity_type}, "
|
||
f"messageType={message_type}, text={message_text[:20]}...")
|
||
|
||
if not order_id or not identity_type:
|
||
return Response({'code': 400, 'msg': '缺少 orderId 或 identityType'})
|
||
if identity_type not in ('dashou', 'shangjia', 'boss'):
|
||
return Response({'code': 400, 'msg': 'identityType 无效'})
|
||
|
||
# ===== 2. 构造 senderId =====
|
||
uid = request.user.yonghuid
|
||
prefix_map = {'dashou': 'Ds', 'shangjia': 'Sj', 'boss': 'Boss'}
|
||
sender_id = prefix_map[identity_type] + uid
|
||
logger.info(f"[群聊代理] 登录用户 yonghuid={uid}, 构造 senderId={sender_id}")
|
||
|
||
# ===== 3. 查询订单 =====
|
||
try:
|
||
order = Dingdan.objects.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(dingdan_id=order_id)
|
||
except Dingdan.DoesNotExist:
|
||
logger.warning(f"[群聊代理] 订单 {order_id} 不存在")
|
||
return Response({'code': 404, 'msg': '订单不存在'})
|
||
|
||
# ===== 4. 权限校验 =====
|
||
valid, err_msg = check_user_permission(identity_type, uid, order)
|
||
if not valid:
|
||
logger.warning(f"[群聊代理] 权限校验失败: {err_msg}, uid={uid}, identity={identity_type}")
|
||
return Response({'code': 403, 'msg': err_msg})
|
||
|
||
# ===== 5. 我方 GoEasy 配置 =====
|
||
self_appkey = get_self_goeasy_appkey()
|
||
self_secret = get_self_goeasy_secret()
|
||
if not self_appkey:
|
||
logger.error("[群聊代理] 我方 GoEasy AppKey 未配置")
|
||
return Response({'code': 500, 'msg': '系统配置错误(APPKEY)'})
|
||
|
||
# ===== 6. 公共群信息 =====
|
||
real_group_id = resolve_group_id_for_order(order)
|
||
group_name = (order.jieshao[:20] + '…') if order.jieshao and len(order.jieshao) > 20 else f"订单{order.dingdan_id}"
|
||
sender_avatar = full_avatar(sender_avatar_raw) if sender_avatar_raw else full_avatar('')
|
||
|
||
# ===== 7. 订阅发送者到群 =====
|
||
if not subscribe_users_to_group([sender_id], [real_group_id], self_appkey, self_secret):
|
||
logger.error("[群聊代理] 订阅群组失败")
|
||
return Response({'code': 500, 'msg': '订阅群组失败'})
|
||
|
||
# ===== 8. 判断跨平台(只看对方订单ID和俱乐部ID) =====
|
||
partner_order_id = order.partner_order_id
|
||
partner_club_id = order.partner_club_id
|
||
is_cross = 1 if (partner_order_id and partner_club_id) else 0
|
||
logger.info(f"[群聊代理] 订单 {order_id} partner_order_id={partner_order_id}, partner_club_id={partner_club_id}, 判定跨平台={is_cross}")
|
||
|
||
custom_payload = order_payload if message_type == 'order' else None
|
||
text_content = message_text
|
||
|
||
# ---------- 先向我方群发送消息(永远执行) ----------
|
||
local_ok = send_group_message(
|
||
appkey=self_appkey,
|
||
secret=self_secret,
|
||
group_id=real_group_id,
|
||
sender_id=sender_id,
|
||
sender_name=sender_name,
|
||
sender_avatar=sender_avatar,
|
||
message_text=text_content,
|
||
custom_payload=custom_payload,
|
||
group_name=group_name,
|
||
order_id=order.dingdan_id,
|
||
is_cross=is_cross
|
||
)
|
||
if not local_ok:
|
||
return Response({'code': 500, 'msg': '消息发送失败'})
|
||
|
||
# ---------- 跨平台转发(如果有对方订单ID和俱乐部ID) ----------
|
||
if is_cross:
|
||
logger.info("[群聊代理] 满足跨平台条件,准备双平台发送")
|
||
partner_cfg = get_partner_club_config(partner_club_id)
|
||
if not partner_cfg or not partner_cfg.chat_api_key:
|
||
logger.warning("[群聊代理] 对方俱乐部配置不可用,仅发送我方群")
|
||
return Response({'code': 200, 'msg': '发送成功(对方GoEasy未配置)'})
|
||
|
||
partner_appkey = partner_cfg.chat_api_key
|
||
partner_secret = partner_cfg.chat_api_id or ''
|
||
partner_group_id = f"group_{partner_order_id}"
|
||
|
||
# 构造代理身份
|
||
agent_sender_id = f"{order.dingdan_id}_{sender_id}"
|
||
if identity_type == 'dashou':
|
||
agent_sender_name = f"[我方]{sender_name}"
|
||
elif identity_type == 'shangjia':
|
||
agent_sender_name = f"[我方商家]{sender_name}"
|
||
else:
|
||
agent_sender_name = f"[我方老板]{sender_name}"
|
||
agent_avatar = full_avatar('')
|
||
|
||
logger.info(f"[群聊代理] 对方群组={partner_group_id}, 代理身份={agent_sender_id}/{agent_sender_name}")
|
||
|
||
# 向对方群发送代理消息
|
||
partner_ok = send_group_message(
|
||
appkey=partner_appkey,
|
||
secret=partner_secret,
|
||
group_id=partner_group_id,
|
||
sender_id=agent_sender_id,
|
||
sender_name=agent_sender_name,
|
||
sender_avatar=agent_avatar,
|
||
message_text=text_content,
|
||
custom_payload=custom_payload,
|
||
group_name='跨平台订单交流',
|
||
order_id=partner_order_id,
|
||
is_cross=1
|
||
)
|
||
logger.info(f"[群聊代理] 对方群发送结果: {partner_ok}")
|
||
|
||
if partner_ok:
|
||
return Response({'code': 200, 'msg': '发送成功(双平台)'})
|
||
else:
|
||
return Response({'code': 200, 'msg': '我方发送成功,对方发送失败'})
|
||
else:
|
||
logger.info("[群聊代理] 非跨平台,仅发送我方群")
|
||
return Response({'code': 200, 'msg': '发送成功'})
|
||
|
||
|
||
|
||
|
||
|
||
|
||
from peizhi.models import ClubConfig, ShangjiaLianjie
|
||
logger = logging.getLogger('dingdan')
|
||
|
||
# ---------- 跨平台对方配置获取 ----------
|
||
def get_partner_club_config(partner_club_id):
|
||
try:
|
||
return ClubConfig.objects.get(club_id=partner_club_id, is_self=0)
|
||
except ClubConfig.DoesNotExist:
|
||
return None
|
||
|
||
|
||
class ShangjiaLianjieLiuYanZhuanFaView(APIView):
|
||
"""
|
||
商家链接留言转发接口
|
||
路径:POST /dingdan/sjljlbxxzf
|
||
固定以 B{商家ID} 身份发送群消息。通过 partner_order_id/partner_club_id 判断是否跨平台。
|
||
"""
|
||
permission_classes = []
|
||
|
||
def post(self, request):
|
||
token = request.data.get('token')
|
||
text = request.data.get('text', '')
|
||
|
||
if not token or not text:
|
||
return Response({'code': 400, 'msg': '缺少参数'})
|
||
|
||
try:
|
||
lianjie = ShangjiaLianjie.objects.get(lianjie_token=token)
|
||
except ShangjiaLianjie.DoesNotExist:
|
||
return Response({'code': 404, 'msg': '链接不存在或已失效'})
|
||
|
||
if not lianjie.dingdan_id:
|
||
return Response({'code': 400, 'msg': '链接缺少订单ID'})
|
||
|
||
try:
|
||
order = Dingdan.objects.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(dingdan_id=lianjie.dingdan_id)
|
||
except Dingdan.DoesNotExist:
|
||
return Response({'code': 500, 'msg': '订单数据异常'})
|
||
|
||
shangjia_uid = lianjie.yonghu_id
|
||
sender_id = f"B{shangjia_uid}"
|
||
sender_name = "老板"
|
||
sender_avatar = ''
|
||
|
||
appkey = get_self_goeasy_appkey()
|
||
secret = get_self_goeasy_secret()
|
||
if not appkey:
|
||
return Response({'code': 500, 'msg': '系统配置错误(APPKEY)'})
|
||
|
||
group_id = f"group_{order.dingdan_id}"
|
||
group_name = (order.jieshao[:20] + '…') if order.jieshao and len(order.jieshao) > 20 else f"订单{order.dingdan_id}"
|
||
|
||
local_cross_flag = 1 if (order.partner_order_id and order.partner_club_id) else 0
|
||
|
||
if not _subscribe_users_to_group([sender_id], [group_id], appkey, secret):
|
||
return Response({'code': 500, 'msg': '订阅群组失败'})
|
||
|
||
local_ok = _send_group_message(
|
||
appkey=appkey,
|
||
secret=secret,
|
||
group_id=group_id,
|
||
sender_id=sender_id,
|
||
sender_name=sender_name,
|
||
sender_avatar=sender_avatar,
|
||
message_text=text,
|
||
custom_payload=None,
|
||
group_name=group_name,
|
||
order_id=order.dingdan_id,
|
||
is_cross=local_cross_flag
|
||
)
|
||
if not local_ok:
|
||
return Response({'code': 500, 'msg': '消息发送失败'})
|
||
|
||
# 跨平台转发
|
||
partner_order_id = order.partner_order_id
|
||
partner_club_id = order.partner_club_id
|
||
|
||
if not partner_order_id or not partner_club_id:
|
||
return Response({'code': 200, 'msg': '发送成功'})
|
||
|
||
partner_cfg = get_partner_club_config(partner_club_id)
|
||
if not partner_cfg or not partner_cfg.chat_api_key:
|
||
return Response({'code': 200, 'msg': '发送成功(对方未配置)'})
|
||
|
||
agent_sender_id = f"{order.dingdan_id}_{sender_id}"
|
||
agent_sender_name = f"[我方老板]{shangjia_uid}"
|
||
|
||
partner_group_id = f"group_{partner_order_id}"
|
||
|
||
partner_ok = _send_group_message(
|
||
appkey=partner_cfg.chat_api_key,
|
||
secret=partner_cfg.chat_api_id or '',
|
||
group_id=partner_group_id,
|
||
sender_id=agent_sender_id,
|
||
sender_name=agent_sender_name,
|
||
sender_avatar='',
|
||
message_text=text,
|
||
custom_payload=None,
|
||
group_name='跨平台订单交流',
|
||
order_id=partner_order_id,
|
||
is_cross=1
|
||
)
|
||
|
||
if partner_ok:
|
||
return Response({'code': 200, 'msg': '发送成功(双平台)'})
|
||
else:
|
||
return Response({'code': 200, 'msg': '我方发送成功,对方发送失败'})
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
class DashouDingdanHuoquView1(APIView):
|
||
"""打手订单获取接口"""
|
||
permission_classes = [IsAuthenticated] # JWT认证
|
||
|
||
def post(self, request):
|
||
# 1. 获取当前登录用户的yonghuid
|
||
try:
|
||
current_user = request.user
|
||
yonghuid = current_user.yonghuid
|
||
|
||
except AttributeError:
|
||
return Response({
|
||
'code': 1,
|
||
'msg': '用户认证失败',
|
||
'data': None
|
||
}, status=status.HTTP_401_UNAUTHORIZED)
|
||
|
||
# 2. 获取和验证前端参数
|
||
try:
|
||
data = request.data
|
||
zhuangtai_list = data.get('zhuangtai_list', [])
|
||
page = max(1, int(data.get('page', 1)))
|
||
page_size = min(100, max(1, int(data.get('page_size', 10))))
|
||
|
||
|
||
|
||
except (ValueError, TypeError):
|
||
return Response({
|
||
'code': 2,
|
||
'msg': '参数格式错误',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 3. 验证状态列表参数
|
||
if not isinstance(zhuangtai_list, list) or len(zhuangtai_list) == 0:
|
||
return Response({
|
||
'code': 3,
|
||
'msg': '状态列表不能为空',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
# 4. 构建查询条件 - 直接查询订单主表
|
||
# 条件1: 当前打手是接单打手
|
||
# 条件2: 订单状态在指定列表中
|
||
|
||
# 注意:打手看不到状态1和7的订单(已下单、指定中)
|
||
# 但这里由前端控制传递的zhuangtai_list决定
|
||
dashou_query = Q(jiedan_dashou_id=yonghuid)
|
||
zhuangtai_query = Q(zhuangtai__in=zhuangtai_list)
|
||
|
||
# 5. 执行查询 - 只查询需要的字段,按创建时间倒序
|
||
dingdan_query = Dingdan.objects.filter(
|
||
dashou_query & zhuangtai_query
|
||
).order_by('-create_time')
|
||
|
||
# 只选择需要的字段,提高性能
|
||
dingdan_query = dingdan_query.only(
|
||
'dingdan_id',
|
||
'zhuangtai',
|
||
'dashou_fencheng', # ✅ 关键:打手分成,不是jine
|
||
'jieshao',
|
||
'tupian',
|
||
'nicheng',
|
||
'create_time'
|
||
)
|
||
|
||
total_count = dingdan_query.count()
|
||
|
||
|
||
# 6. 计算分页
|
||
start_index = (page - 1) * page_size
|
||
end_index = start_index + page_size
|
||
|
||
# 获取当前页的订单数据
|
||
current_page_orders = dingdan_query[start_index:end_index]
|
||
current_page_count = len(current_page_orders)
|
||
|
||
# 7. 构建返回数据 - 只返回前端需要的字段
|
||
order_list = []
|
||
for order in current_page_orders:
|
||
order_data = {
|
||
# 核心字段 - 必须与前端完全对应
|
||
'dingdan_id': order.dingdan_id, # 订单ID
|
||
'zhuangtai': order.zhuangtai, # 订单状态码
|
||
# ✅ 关键:这里返回的是打手分成,不是订单金额
|
||
'jine': float(order.dashou_fencheng) if order.dashou_fencheng else 0.0,
|
||
'jieshao': order.jieshao or '', # 订单介绍
|
||
'tupian': order.tupian or '', # 商品图片URL
|
||
'nicheng': order.nicheng or '', # 游戏昵称
|
||
'create_time': order.create_time.strftime('%Y-%m-%d %H:%M:%S') if order.create_time else ''
|
||
}
|
||
order_list.append(order_data)
|
||
|
||
|
||
|
||
# 8. 判断是否还有更多数据
|
||
# 条件1: 当前页获取了满page_size条数据
|
||
# 条件2: 还有更多数据没取完
|
||
has_more = current_page_count == page_size and end_index < total_count
|
||
|
||
# 如果总数为0,直接没有更多
|
||
if total_count == 0:
|
||
has_more = False
|
||
|
||
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '成功',
|
||
'data': {
|
||
'list': order_list, # 订单列表
|
||
'has_more': has_more, # 是否还有更多数据
|
||
'total': total_count # 总订单数(可选)
|
||
}
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
# 记录异常日志
|
||
import logging
|
||
logger = logging.getLogger(__name__)
|
||
logger.error(f"打手订单获取失败: {str(e)}", exc_info=True)
|
||
|
||
|
||
|
||
return Response({
|
||
'code': 4,
|
||
'msg': '服务器内部错误',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
# views.py - 打手COS凭证接口(完整替换)
|
||
import json
|
||
import time
|
||
from tencentcloud.common import credential
|
||
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
|
||
from tencentcloud.sts.v20180813 import sts_client, models
|
||
from django.conf import settings
|
||
from rest_framework.views import APIView
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from rest_framework.response import Response
|
||
from rest_framework import status
|
||
import logging
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class DashouCOSZhengshuView(APIView):
|
||
"""打手获取COS上传凭证接口(调试版本)"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 接收参数(修复 int 无 strip 问题)
|
||
dingdan_id = str(request.data.get('dingdan_id', '')).strip()
|
||
yongtu = request.data.get('yongtu', 'dingdan')
|
||
|
||
# 2. 条件判断修正:除了 fakuan 之外,其他用途均需提供 dingdan_id
|
||
if yongtu != 'fakuan' and not dingdan_id:
|
||
return Response(
|
||
{'code': 400, 'msg': '订单ID不能为空', 'error': 'dingdan_id is required when yongtu is not fakuan'},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 3. 业务验证逻辑(根据用途分别验证)
|
||
if yongtu == 'chufa':
|
||
try:
|
||
from .models import Chufajilu
|
||
chufa_record = Chufajilu.objects.filter(
|
||
dingdan_id=dingdan_id,
|
||
dashouid=request.user.yonghuid,
|
||
sqzhuangtai__in=[0, 3]
|
||
).first()
|
||
if not chufa_record:
|
||
return Response(
|
||
{'code': 403, 'msg': '处罚记录无权限或状态不对',
|
||
'error': f'No punishment record found for user {request.user.yonghuid} on order {dingdan_id}'},
|
||
status=status.HTTP_403_FORBIDDEN
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"查处罚记录异常: {str(e)}")
|
||
return Response(
|
||
{'code': 500, 'msg': '服务器内部错误', 'error': str(e)},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
elif yongtu == 'fakuan':
|
||
# 罚款申诉不需要订单验证
|
||
pass
|
||
else:
|
||
# 默认为订单相关,需要验证订单且打手必须接单
|
||
try:
|
||
from .models import Dingdan
|
||
dingdan = Dingdan.objects.get(
|
||
dingdan_id=dingdan_id,
|
||
jiedan_dashou_id=request.user.yonghuid,
|
||
zhuangtai__in=[2, 8]
|
||
)
|
||
except Dingdan.DoesNotExist:
|
||
return Response(
|
||
{'code': 403, 'msg': '此功能暂未开放', 'error': f'Order {dingdan_id} not found or not assigned to user {request.user.yonghuid}'},
|
||
status=status.HTTP_403_FORBIDDEN
|
||
)
|
||
|
||
# 4. 获取腾讯云STS临时密钥
|
||
try:
|
||
# 初始化固定凭证
|
||
cred = credential.Credential(
|
||
settings.COS_SECRET_ID,
|
||
settings.COS_SECRET_KEY
|
||
)
|
||
|
||
# 创建STS客户端
|
||
http_profile = HttpProfile()
|
||
http_profile.endpoint = "sts.tencentcloudapi.com"
|
||
client_profile = ClientProfile()
|
||
client_profile.httpProfile = http_profile
|
||
client = sts_client.StsClient(cred, "ap-shanghai", client_profile)
|
||
|
||
# 构建策略资源
|
||
resources = []
|
||
if yongtu == 'chufa':
|
||
resources.append(
|
||
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/a_long/chfajltp/dssstp/*"
|
||
)
|
||
elif yongtu == 'fakuan':
|
||
resources.append(
|
||
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/fakuan/dashoufakuan/dashoufakuanshensu/*"
|
||
)
|
||
else:
|
||
resources.append(
|
||
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/order/{dingdan_id}/*"
|
||
)
|
||
|
||
policy = {
|
||
"version": "2.0",
|
||
"statement": [{
|
||
"effect": "allow",
|
||
"action": [
|
||
"cos:PutObject",
|
||
"cos:PostObject",
|
||
"cos:InitiateMultipartUpload",
|
||
"cos:UploadPart",
|
||
"cos:CompleteMultipartUpload"
|
||
],
|
||
"resource": resources
|
||
}]
|
||
}
|
||
|
||
req = models.GetFederationTokenRequest()
|
||
req.Name = f"dashou_{dingdan_id}_{int(time.time())}"
|
||
req.Policy = json.dumps(policy)
|
||
req.DurationSeconds = 7200
|
||
|
||
resp = client.GetFederationToken(req)
|
||
credentials = resp.Credentials
|
||
|
||
except TencentCloudSDKException as e:
|
||
logger.error(f"获取STS临时密钥失败: {str(e)}")
|
||
return Response({
|
||
'code': 500,
|
||
'msg': f'获取上传凭证失败: {str(e)}',
|
||
'error_detail': str(e)
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
# 5. 构建响应
|
||
current_time = int(time.time())
|
||
upload_dir = 'fakuan/dashoufakuan/dashoufakuanshensu/' if yongtu == 'fakuan' else (
|
||
'a_long/chfajltp/dssstp/' if yongtu == 'chufa' else f'order/{dingdan_id}/'
|
||
)
|
||
response_data = {
|
||
'credentials': {
|
||
'tmpSecretId': credentials.TmpSecretId,
|
||
'tmpSecretKey': credentials.TmpSecretKey,
|
||
'sessionToken': credentials.Token,
|
||
},
|
||
'startTime': current_time,
|
||
'expiredTime': current_time + 7200,
|
||
'bucket': settings.COS_BUCKET,
|
||
'region': settings.COS_REGION,
|
||
'upload_dir': upload_dir,
|
||
}
|
||
return Response({'code': 0, 'msg': '成功', 'data': response_data})
|
||
|
||
except Exception as e:
|
||
# 捕捉所有未预期的异常,返回完整 traceback
|
||
logger.error(f'COS凭证接口未知错误: {traceback.format_exc()}')
|
||
return Response({
|
||
'code': 500,
|
||
'msg': f'服务器内部错误: {str(e)}',
|
||
'error_detail': traceback.format_exc()
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
class ShangjiaCOSZhengshuView(APIView):
|
||
"""商家获取COS上传凭证接口(用于处罚申诉图片上传)"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 接收参数
|
||
dingdan_id = request.data.get('dingdan_id', '').strip()
|
||
yongtu = request.data.get('yongtu', 'chufa') # 用途:chufa=处罚申诉
|
||
|
||
if not dingdan_id:
|
||
return Response({'code': 1, 'msg': '订单ID不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 2. 🔴【修改】验证商家身份 - 通过反向查询shop_profile判断是否为商家
|
||
try:
|
||
# 检查是否有商家扩展表
|
||
shangjia_profile = request.user.shop_profile
|
||
|
||
# 验证商家状态是否为1(正常)
|
||
if shangjia_profile.zhuangtai != 1:
|
||
return Response({'code': 3, 'msg': '商家账号状态异常,无法操作', 'data': None},
|
||
status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 🔴【重要】获取商家ID(从商家扩展表中获取)
|
||
shangjia_id = request.user.yonghuid # 假设商家扩展表中有yonghuid字段
|
||
|
||
except AttributeError:
|
||
# 没有shop_profile属性,说明不是商家
|
||
return Response({'code': 3, 'msg': '非商家用户无权限', 'data': None},
|
||
status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 3. 🔴【修改】验证订单权限 - 通过商家订单扩展表验证
|
||
try:
|
||
from .models import Dingdan, DingdanShangjia
|
||
|
||
# 获取订单
|
||
dingdan = Dingdan.objects.get(dingdan_id=dingdan_id)
|
||
|
||
# 🔴【重要】通过反向查询商家扩展表验证订单是否属于当前商家
|
||
try:
|
||
shangjia_kuozhan = dingdan.shangjia_kuozhan # 使用反向关系名
|
||
|
||
# 验证商家ID是否匹配
|
||
if str(shangjia_kuozhan.shangjia_id) != str(shangjia_id):
|
||
return Response({'code': 2, 'msg': '订单不属于当前商家', 'data': None},
|
||
status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 验证订单状态是否允许申请处罚(根据前端逻辑)
|
||
allowed_zhuangtai = [2, 4, 5, 6, 8]
|
||
if dingdan.zhuangtai not in allowed_zhuangtai:
|
||
return Response({'code': 4, 'msg': '当前订单状态不允许申请处罚', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 🔴【新增】检查是否已存在处罚申请(通过Chufajilu模型)
|
||
# 注意:这里假设Chufajilu模型存在,并且有shangjia_id字段
|
||
try:
|
||
from .models import Chufajilu
|
||
existing_chufa = Chufajilu.objects.filter(
|
||
dingdan_id=dingdan_id,
|
||
qingqiuid=shangjia_id,
|
||
sqzhuangtai__in=[0, 1] # 审核中或已处罚状态
|
||
).exists()
|
||
|
||
if existing_chufa:
|
||
return Response({'code': 5, 'msg': '该订单已有处罚申请,请勿重复上传', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
except ImportError:
|
||
# 如果没有Chufajilu模型,跳过这个检查
|
||
pass
|
||
|
||
except DingdanShangjia.DoesNotExist:
|
||
# 没有商家扩展记录,说明这不是商家订单
|
||
return Response({'code': 2, 'msg': '非商家订单无法操作', 'data': None},
|
||
status=status.HTTP_403_FORBIDDEN)
|
||
|
||
except Dingdan.DoesNotExist:
|
||
return Response({'code': 2, 'msg': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND)
|
||
except Exception as e:
|
||
logger.error(f"订单验证异常: {str(e)}")
|
||
return Response({'code': 500, 'msg': '服务器验证异常', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
# 4. 获取腾讯云STS临时密钥
|
||
try:
|
||
# 初始化固定凭证
|
||
cred = credential.Credential(
|
||
settings.COS_SECRET_ID,
|
||
settings.COS_SECRET_KEY
|
||
)
|
||
|
||
# 创建STS客户端
|
||
http_profile = HttpProfile()
|
||
http_profile.endpoint = "sts.tencentcloudapi.com"
|
||
client_profile = ClientProfile()
|
||
client_profile.httpProfile = http_profile
|
||
|
||
client = sts_client.StsClient(cred, "ap-shanghai", client_profile)
|
||
|
||
# 🔴【修改】构建策略 - 只允许上传到商家处罚证据图片目录
|
||
resources = [
|
||
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/a_long/chfajltp/sjzjtp/*"
|
||
]
|
||
|
||
policy = {
|
||
"version": "2.0",
|
||
"statement": [
|
||
{
|
||
"effect": "allow",
|
||
"action": [
|
||
"cos:PutObject",
|
||
"cos:PostObject",
|
||
"cos:InitiateMultipartUpload",
|
||
"cos:UploadPart",
|
||
"cos:CompleteMultipartUpload"
|
||
],
|
||
"resource": resources
|
||
}
|
||
]
|
||
}
|
||
|
||
# 请求STS临时凭证
|
||
req = models.GetFederationTokenRequest()
|
||
# 🔴【修改】命名包含商家ID
|
||
req.Name = f"shangjia_{shangjia_id}_{dingdan_id}_{int(time.time())}"
|
||
req.Policy = json.dumps(policy)
|
||
req.DurationSeconds = 7200 # 2小时有效期
|
||
|
||
resp = client.GetFederationToken(req)
|
||
|
||
# 提取临时密钥
|
||
credentials = resp.Credentials
|
||
|
||
except TencentCloudSDKException as e:
|
||
logger.error(f"商家获取STS临时密钥失败: {str(e)}")
|
||
return Response({
|
||
'code': 500,
|
||
'msg': f'获取上传凭证失败: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
# 5. 构建响应数据
|
||
current_time = int(time.time())
|
||
response_data = {
|
||
'credentials': {
|
||
'tmpSecretId': credentials.TmpSecretId,
|
||
'tmpSecretKey': credentials.TmpSecretKey,
|
||
'sessionToken': credentials.Token,
|
||
},
|
||
'startTime': current_time,
|
||
'expiredTime': current_time + 7200,
|
||
'bucket': settings.COS_BUCKET,
|
||
'region': settings.COS_REGION,
|
||
# 🔴【修改】商家专用上传目录
|
||
'upload_dir': 'a_long/chfajltp/sjzjtp/',
|
||
# 🔴【新增】返回商家ID和订单ID,便于前端生成文件名
|
||
'dingdan_id': dingdan_id,
|
||
'shangjia_id': shangjia_id,
|
||
# 🔴【新增】可选:返回商家昵称,便于前端显示
|
||
'shangjia_nicheng': shangjia_profile.nicheng if hasattr(shangjia_profile, 'nicheng') else ''
|
||
}
|
||
|
||
# 6. 返回成功
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '成功',
|
||
'data': response_data
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
logger.error(f'商家COS凭证接口未知错误: {str(e)}', exc_info=True)
|
||
return Response({
|
||
'code': 500,
|
||
'msg': f'服务器内部错误: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
'''# views.py - 打手COS凭证接口(完整替换)
|
||
import json
|
||
import time
|
||
from tencentcloud.common import credential
|
||
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
|
||
from tencentcloud.sts.v20180813 import sts_client, models
|
||
from django.conf import settings
|
||
from rest_framework.views import APIView
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from rest_framework.response import Response
|
||
from rest_framework import status
|
||
import logging
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class DashouCOSZhengshuView(APIView):
|
||
"""打手获取COS上传凭证接口(使用腾讯云STS临时密钥)"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
|
||
|
||
# 1. 接收参数
|
||
dingdan_id = request.data.get('dingdan_id', '').strip()
|
||
if not dingdan_id:
|
||
return Response({'code': 1, 'msg': '订单ID不能为空', 'data': None}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 2. 业务验证逻辑(保持你的原有逻辑)
|
||
try:
|
||
from .models import Dingdan
|
||
dingdan = Dingdan.objects.get(
|
||
dingdan_id=dingdan_id,
|
||
jiedan_dashou_id=request.user.yonghuid,
|
||
zhuangtai=2
|
||
)
|
||
|
||
except Dingdan.DoesNotExist:
|
||
return Response({'code': 5, 'msg': '订单无权限或状态不对', 'data': None},
|
||
status=status.HTTP_403_FORBIDDEN)
|
||
except Exception as e:
|
||
|
||
return Response({'code': 500, 'msg': '服务器验证异常', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
# 3. 获取腾讯云STS临时密钥
|
||
try:
|
||
# 初始化固定凭证
|
||
cred = credential.Credential(
|
||
settings.COS_SECRET_ID,
|
||
settings.COS_SECRET_KEY
|
||
)
|
||
|
||
# 创建STS客户端
|
||
http_profile = HttpProfile()
|
||
http_profile.endpoint = "sts.tencentcloudapi.com"
|
||
client_profile = ClientProfile()
|
||
client_profile.httpProfile = http_profile
|
||
|
||
client = sts_client.StsClient(cred, "ap-shanghai", client_profile)
|
||
|
||
# 构建策略(允许上传到订单目录)
|
||
policy = {
|
||
"version": "2.0",
|
||
"statement": [
|
||
{
|
||
"effect": "allow",
|
||
"action": [
|
||
"cos:PutObject",
|
||
"cos:PostObject",
|
||
"cos:InitiateMultipartUpload",
|
||
"cos:UploadPart",
|
||
"cos:CompleteMultipartUpload"
|
||
],
|
||
"resource": [
|
||
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/order/{dingdan_id}/*"
|
||
]
|
||
}
|
||
]
|
||
}
|
||
|
||
# 请求STS临时凭证
|
||
req = models.GetFederationTokenRequest()
|
||
req.Name = f"dashou_{dingdan_id}_{int(time.time())}"
|
||
req.Policy = json.dumps(policy)
|
||
req.DurationSeconds = 7200 # 2小时有效期
|
||
|
||
resp = client.GetFederationToken(req)
|
||
|
||
# 提取临时密钥
|
||
credentials = resp.Credentials
|
||
|
||
|
||
except TencentCloudSDKException as e:
|
||
logger.error(f"获取STS临时密钥失败: {str(e)}")
|
||
return Response({
|
||
'code': 500,
|
||
'msg': f'获取上传凭证失败: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
# 4. 构建响应数据(严格遵循小程序COS SDK要求)
|
||
current_time = int(time.time())
|
||
response_data = {
|
||
'credentials': {
|
||
'tmpSecretId': credentials.TmpSecretId, # 临时SecretId
|
||
'tmpSecretKey': credentials.TmpSecretKey, # 临时SecretKey
|
||
'sessionToken': credentials.Token, # 必须的Token
|
||
},
|
||
'startTime': current_time,
|
||
'expiredTime': current_time + 7200,
|
||
'bucket': settings.COS_BUCKET,
|
||
'region': settings.COS_REGION,
|
||
'upload_dir': f'order/{dingdan_id}/'
|
||
}
|
||
|
||
|
||
|
||
# 5. 返回成功
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '成功',
|
||
'data': response_data
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
logger.error(f'COS凭证接口未知错误: {str(e)}', exc_info=True)
|
||
return Response({
|
||
'code': 500,
|
||
'msg': f'服务器内部错误: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)'''
|
||
|
||
#
|
||
|
||
|
||
class DashouTijiaoView(APIView):
|
||
"""打手提交订单完成接口"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 获取参数
|
||
data = request.data
|
||
dingdan_id = data.get('dingdan_id', '')
|
||
liuyan = data.get('liuyan', '')
|
||
jiaofu_tupian_urls = data.get('jiaofu_tupian_urls', [])
|
||
|
||
# 2. 参数验证
|
||
if not dingdan_id:
|
||
return Response({
|
||
'code': 1,
|
||
'msg': '订单ID不能为空',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if not isinstance(jiaofu_tupian_urls, list):
|
||
return Response({
|
||
'code': 2,
|
||
'msg': '图片URL列表格式错误',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 限制图片数量(最多9张,最少1张)
|
||
if len(jiaofu_tupian_urls) == 0:
|
||
return Response({
|
||
'code': 3,
|
||
'msg': '至少需要上传一张图片',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if len(jiaofu_tupian_urls) > 9:
|
||
return Response({
|
||
'code': 4,
|
||
'msg': '最多只能上传9张图片',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 留言长度限制(50字)
|
||
if liuyan and len(liuyan) > 50:
|
||
return Response({
|
||
'code': 5,
|
||
'msg': '留言最多50个字',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 3. 获取当前用户并验证打手身份
|
||
current_user = request.user
|
||
yonghuid = current_user.yonghuid
|
||
|
||
try:
|
||
dashou_profile = current_user.dashou_profile
|
||
except UserDashou.DoesNotExist:
|
||
return Response({
|
||
'code': 6,
|
||
'msg': '用户不是打手身份',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 4. 验证订单状态和权限
|
||
try:
|
||
dingdan = Dingdan.objects.get(
|
||
dingdan_id=dingdan_id,
|
||
jiedan_dashou_id=yonghuid,
|
||
zhuangtai=2 # 必须是进行中的订单
|
||
)
|
||
except Dingdan.DoesNotExist:
|
||
return Response({
|
||
'code': 7,
|
||
'msg': '订单不存在或没有提交权限',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 5. 存储图片到Dashoutupian表
|
||
for tupian_url in jiaofu_tupian_urls:
|
||
# 创建打手提交图片记录
|
||
Dashoutupian.objects.create(
|
||
dingdan_id=dingdan_id,
|
||
dashou=yonghuid,
|
||
tupian=tupian_url,
|
||
create_time=timezone.now()
|
||
)
|
||
|
||
# 6. 更新订单主表
|
||
dingdan.dashou_liuyan = liuyan
|
||
dingdan.zhuangtai = 8 # 状态改为结算中
|
||
dingdan.update_time = timezone.now()
|
||
dingdan.save()
|
||
|
||
# 7. 更新打手扩展表状态
|
||
dashou_profile.zhuangtai = 1 # 改为数字1(根据你之前说的)
|
||
dashou_profile.save()
|
||
|
||
# 8. 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '订单提交成功',
|
||
'data': {
|
||
'dingdan_id': dingdan_id,
|
||
'new_zhuangtai': 8, # 新的订单状态
|
||
'dashou_zhuangtai': 1 # 打手新的状态
|
||
}
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
import logging
|
||
logger = logging.getLogger(__name__)
|
||
logger.error(f"打手提交订单失败: {str(e)}", exc_info=True)
|
||
|
||
return Response({
|
||
'code': 8,
|
||
'msg': '服务器内部错误',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
class DashouDingdanXiangqingView(APIView):
|
||
"""打手订单详情获取接口"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 获取订单ID
|
||
dingdan_id = request.data.get('dingdan_id', '')
|
||
if not dingdan_id:
|
||
return Response({
|
||
'code': 1,
|
||
'msg': '订单ID不能为空',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 2. 获取当前用户并验证权限
|
||
current_user = request.user
|
||
yonghuid = current_user.yonghuid
|
||
|
||
try:
|
||
dashou_profile = current_user.dashou_profile
|
||
except UserDashou.DoesNotExist:
|
||
return Response({
|
||
'code': 2,
|
||
'msg': '用户不是打手身份',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 3. 查询订单主表
|
||
try:
|
||
dingdan = Dingdan.objects.get(dingdan_id=dingdan_id)
|
||
except Dingdan.DoesNotExist:
|
||
return Response({
|
||
'code': 3,
|
||
'msg': '订单不存在',
|
||
'data': None
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 4. 验证订单是否属于当前打手
|
||
if dingdan.jiedan_dashou_id != yonghuid:
|
||
return Response({
|
||
'code': 4,
|
||
'msg': '没有权限查看此订单',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 5. 构建基础订单数据
|
||
dingdan_data = {
|
||
'dingdan_id': dingdan.dingdan_id,
|
||
'zhuangtai': dingdan.zhuangtai,
|
||
'jine': float(dingdan.dashou_fencheng) if dingdan.dashou_fencheng else 0.0,
|
||
'jieshao': dingdan.jieshao or '',
|
||
'tupian': dingdan.tupian or '',
|
||
'nicheng': dingdan.nicheng or '',
|
||
'beizhu': dingdan.beizhu or '',
|
||
'create_time': dingdan.create_time.strftime('%Y-%m-%d %H:%M:%S') if dingdan.create_time else '',
|
||
'dashou_liuyan': dingdan.dashou_liuyan or '',
|
||
'fuwudashou_id': dingdan.jiedan_dashou_id or '',
|
||
'fadanpingtai': dingdan.fadan_pingtai or '',
|
||
'tuikuan_liyou': dingdan.tkly or ''
|
||
}
|
||
|
||
# 6. 查询打手提交的图片
|
||
dashou_tupian_list = Dashoutupian.objects.filter(
|
||
dingdan_id=dingdan_id,
|
||
dashou=yonghuid
|
||
).order_by('create_time')
|
||
|
||
jiaofu_tupian_urls = []
|
||
for tupian_record in dashou_tupian_list:
|
||
if tupian_record.tupian:
|
||
jiaofu_tupian_urls.append(tupian_record.tupian)
|
||
|
||
dingdan_data['dashou_jiaofu'] = jiaofu_tupian_urls
|
||
|
||
# 7. 查询商家扩展表信息
|
||
shangjia_info = {}
|
||
try:
|
||
shangjia_kuozhan = dingdan.shangjia_kuozhan
|
||
shangjia_info = {
|
||
'shangjia_id': shangjia_kuozhan.shangjia_id or '',
|
||
'shangjia_nicheng': shangjia_kuozhan.sjnicheng or '',
|
||
'shangjia_liuyan': shangjia_kuozhan.shangjia_pingjia or '',
|
||
#'tuikuan_liyou': '',
|
||
'chufa_liyou': shangjia_kuozhan.cfliyou or '',
|
||
'chufa_zhuangtai': shangjia_kuozhan.sqzhuangtai or 0,
|
||
#'chufa_jieguo': shangjia_kuozhan.chufa_jieguo or '',
|
||
'bohui_liyou': shangjia_kuozhan.bhliyou or ''
|
||
}
|
||
except DingdanShangjia.DoesNotExist:
|
||
# 没有商家扩展信息,返回空值
|
||
shangjia_info = {
|
||
'shangjia_id': '',
|
||
'shangjia_nicheng': '',
|
||
'shangjia_liuyan': '',
|
||
'tuikuan_liyou': '',
|
||
'chufa_liyou': '',
|
||
'chufa_zhuangtai': 0,
|
||
'chufa_jieguo': '',
|
||
'bohui_liyou': ''
|
||
}
|
||
|
||
# 8. 查询平台扩展表信息(如果有的话)
|
||
pingtai_info = {}
|
||
try:
|
||
pingtai_kuozhan = dingdan.pingtai_kuozhan
|
||
pingtai_info = {
|
||
'laoban_id': pingtai_kuozhan.laoban_id or '',
|
||
'laoban_pingjia': pingtai_kuozhan.laoban_pingjia or ''
|
||
}
|
||
except DingdanPingtai.DoesNotExist:
|
||
pingtai_info = {
|
||
'laoban_id': '',
|
||
'laoban_pingjia': ''
|
||
}
|
||
|
||
# 9. 合并所有数据
|
||
response_data = {
|
||
**dingdan_data,
|
||
**shangjia_info,
|
||
**pingtai_info
|
||
}
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '成功',
|
||
'data': response_data
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
import logging
|
||
logger = logging.getLogger(__name__)
|
||
logger.error(f"获取打手订单详情失败: {str(e)}", exc_info=True)
|
||
|
||
return Response({
|
||
'code': 5,
|
||
'msg': '服务器内部错误',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
# dingdan/views.py
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework import status
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from rest_framework.parsers import JSONParser
|
||
import logging
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class AdGetDingdanXiangQing(APIView):
|
||
"""
|
||
后台订单详情获取接口
|
||
权限: JWT Token认证 + 管理员身份验证
|
||
前端调用: POST /dingdan/adddxq
|
||
参数: {"zhanghao": "管理员手机号", "dingdan_id": "订单ID"}
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
"""
|
||
获取订单详情数据
|
||
返回: 订单主表数据 + 扩展表数据 + 打手图片列表
|
||
"""
|
||
try:
|
||
# 1. 获取前端参数
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = request.data.get('dingdan_id', '').strip()
|
||
|
||
if not qianzhanghao or not dingdan_id:
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2. JWT Token身份验证 (IsAuthenticated已处理)
|
||
dangqianyonghu = request.user
|
||
|
||
# 3. 验证手机号是否匹配
|
||
if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 4. 验证用户类型是否为管理员
|
||
if dangqianyonghu.user_type != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 5. 验证管理员扩展表是否存在
|
||
try:
|
||
guanliyuan = dangqianyonghu.admin_profile
|
||
except AttributeError:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 6. 查询订单主表
|
||
try:
|
||
from dingdan.models import Dingdan, DingdanPingtai, DingdanShangjia, Dashoutupian
|
||
|
||
# 查询订单主表 - 使用select_related预加载关联表
|
||
dingdan_obj = Dingdan.objects.select_related(
|
||
'pingtai_kuozhan', 'shangjia_kuozhan'
|
||
).get(dingdan_id=dingdan_id)
|
||
|
||
except Dingdan.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 7. 构建返回数据 - 主表字段
|
||
response_data = {
|
||
# 基础信息字段
|
||
'dingdan_id': dingdan_obj.dingdan_id or '',
|
||
'jieshao': dingdan_obj.jieshao or '',
|
||
'zhuangtai': dingdan_obj.zhuangtai or 0,
|
||
'leixing': dingdan_obj.leixing_id or 0, # 前端需要 leixing,对应 leixing_id
|
||
'fadanpingtai': dingdan_obj.fadan_pingtai or 0, # 前端需要 fadanpingtai,对应 fadan_pingtai
|
||
'jiage': float(dingdan_obj.jine) if dingdan_obj.jine else 0.00, # 前端需要 jiage,对应 jine
|
||
'tupian_url': dingdan_obj.tupian or '', # 前端需要 tupian_url,对应 tupian
|
||
'beizhu': dingdan_obj.beizhu or '',
|
||
|
||
# 服务信息字段
|
||
'jiedan_dashou_id': dingdan_obj.jiedan_dashou_id or '',
|
||
'dashou_fencheng': float(dingdan_obj.dashou_fencheng) if dingdan_obj.dashou_fencheng else 0.00,
|
||
'zhiding_id': dingdan_obj.zhiding_id or '',
|
||
'dashou_liuyan': dingdan_obj.dashou_liuyan or '',
|
||
|
||
# 时间字段
|
||
'chuangjianshijian': dingdan_obj.create_time.strftime(
|
||
'%Y-%m-%d %H:%M:%S') if dingdan_obj.create_time else '',
|
||
'genggaishijian': dingdan_obj.update_time.strftime(
|
||
'%Y-%m-%d %H:%M:%S') if dingdan_obj.update_time else '',
|
||
}
|
||
|
||
# 8. 根据发单平台查询扩展表数据
|
||
fadan_pingtai = dingdan_obj.fadan_pingtai
|
||
|
||
if fadan_pingtai == 1:
|
||
# 平台发单 - 查询平台订单扩展表
|
||
try:
|
||
# 使用反向关联 pingtai_kuozhan
|
||
pingtai_kuozhan = dingdan_obj.pingtai_kuozhan
|
||
|
||
response_data.update({
|
||
'laoban_id': pingtai_kuozhan.laoban_id or '',
|
||
'nicheng': dingdan_obj.nicheng or '', # 这个字段在订单主表
|
||
'laoban_pingjia': pingtai_kuozhan.laoban_pingjia or '',
|
||
})
|
||
except DingdanPingtai.DoesNotExist:
|
||
# 扩展表不存在,返回空值
|
||
response_data.update({
|
||
'laoban_id': '',
|
||
'nicheng': dingdan_obj.nicheng or '',
|
||
'laoban_pingjia': '',
|
||
})
|
||
|
||
elif fadan_pingtai == 2:
|
||
# 商家发单 - 查询商家订单扩展表
|
||
try:
|
||
# 使用反向关联 shangjia_kuozhan
|
||
shangjia_kuozhan = dingdan_obj.shangjia_kuozhan
|
||
|
||
response_data.update({
|
||
'shangjia_id': shangjia_kuozhan.shangjia_id or '',
|
||
'sjnicheng': shangjia_kuozhan.sjnicheng or '',
|
||
'shangjia_pingjia': shangjia_kuozhan.shangjia_pingjia or '',
|
||
'cfliyou': shangjia_kuozhan.cfliyou or '', # 商家处罚理由
|
||
'bhliyou': shangjia_kuozhan.bhliyou or '', # 驳回理由
|
||
})
|
||
except DingdanShangjia.DoesNotExist:
|
||
# 扩展表不存在,返回空值
|
||
response_data.update({
|
||
'shangjia_id': '',
|
||
'sjnicheng': '',
|
||
'shangjia_pingjia': '',
|
||
'cfliyou': '',
|
||
'bhliyou': '',
|
||
})
|
||
|
||
# 9. 查询打手提交的图片
|
||
try:
|
||
# 查询该订单的所有打手提交图片
|
||
dashou_tupian_list = Dashoutupian.objects.filter(
|
||
dingdan_id=dingdan_id
|
||
).values_list('tupian', flat=True)
|
||
|
||
# 转换为列表,过滤空值
|
||
dashou_images = [img for img in dashou_tupian_list if img]
|
||
response_data['dashou_images'] = dashou_images
|
||
except Exception as e:
|
||
logger.warning(f"查询打手图片失败: {str(e)}")
|
||
response_data['dashou_images'] = []
|
||
|
||
# 🔥 新增:查询退款记录表中的商家退款理由
|
||
try:
|
||
# 导入退款记录模型
|
||
from dingdan.models import Tuikuanjilu
|
||
|
||
# 查询该订单的退款记录(一个订单可能有多次退款申请,我们取最新的)
|
||
tuikuan_record = Tuikuanjilu.objects.filter(
|
||
dingdan_id=dingdan_id
|
||
).order_by('-create_time').first() # 按创建时间倒序,取最新的
|
||
|
||
if tuikuan_record and tuikuan_record.liyou:
|
||
# 如果有退款记录且理由不为空,添加到返回数据
|
||
response_data['tuikuan_liyou'] = tuikuan_record.liyou
|
||
else:
|
||
# 没有找到退款记录或理由为空
|
||
response_data['tuikuan_liyou'] = ''
|
||
except Tuikuanjilu.DoesNotExist:
|
||
# 退款记录表不存在该订单的记录
|
||
response_data['tuikuan_liyou'] = ''
|
||
except Exception as e:
|
||
# 查询过程中出现其他异常
|
||
logger.warning(f"查询退款记录失败: {str(e)}")
|
||
response_data['tuikuan_liyou'] = ''
|
||
|
||
# 10. 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'message': '获取成功',
|
||
'data': response_data
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取订单详情异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
|
||
|
||
# 获取日志器
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class AdGengHuanDaShou(APIView):
|
||
"""
|
||
后台更换打手接口
|
||
权限: JWT Token认证 + 管理员身份验证
|
||
前端调用: POST /dingdan/adddgh
|
||
参数: {
|
||
"zhanghao": "管理员账号",
|
||
"dingdan_id": "订单ID",
|
||
"new_dashou_id": "新打手ID"
|
||
}
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
"""
|
||
更换订单的打手
|
||
核心逻辑:
|
||
1. 验证管理员身份
|
||
2. 验证订单存在且状态为进行中(2)或结算中(8)
|
||
3. 验证新打手ID存在且是打手身份
|
||
4. 验证新打手状态正常(zhuangtai=1且zhanghaozhuangtai=1)
|
||
5. 根据订单的抢单要求类型进行验证:
|
||
- 会员抢单(1):验证打手有对应会员且未过期
|
||
- 押金抢单(2):验证打手押金≥订单押金
|
||
6. 更新新打手状态和统计数据
|
||
7. 更新原打手状态和统计数据
|
||
8. 更新订单信息
|
||
"""
|
||
try:
|
||
# 1. 获取前端参数
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = request.data.get('dingdan_id', '').strip()
|
||
new_dashou_id = request.data.get('new_dashou_id', '').strip()
|
||
|
||
if not qianzhanghao or not dingdan_id or not new_dashou_id:
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2. JWT Token身份验证
|
||
dangqianyonghu = request.user
|
||
|
||
# 3. 验证手机号是否匹配
|
||
if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 4. 验证用户类型是否为管理员
|
||
if dangqianyonghu.user_type != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 5. 验证管理员扩展表是否存在
|
||
try:
|
||
guanliyuan = dangqianyonghu.admin_profile
|
||
except AttributeError:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 6. 查询订单主表
|
||
try:
|
||
from dingdan.models import Dingdan
|
||
dingdan_obj = Dingdan.objects.get(dingdan_id=dingdan_id)
|
||
except Dingdan.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 7. 验证订单状态是否为进行中(2)或结算中(8)
|
||
if dingdan_obj.zhuangtai not in [2, 8]:
|
||
return Response(
|
||
{'code': 400, 'message': '订单状态不允许更换打手', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 8. 验证新打手ID是否存在,并且是打手身份
|
||
try:
|
||
new_dashou_user = UserMain.objects.get(
|
||
yonghuid=new_dashou_id,
|
||
#user_type='dashou'
|
||
)
|
||
except UserMain.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '打手不存在或不是打手身份', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 9. 获取新打手扩展表
|
||
try:
|
||
new_dashou_profile = new_dashou_user.dashou_profile
|
||
except AttributeError:
|
||
return Response(
|
||
{'code': 404, 'message': '打手信息不完整', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 10. 验证新打手状态:zhuangtai=1且zhanghaozhuangtai=1
|
||
if new_dashou_profile.zhuangtai != 1 or new_dashou_profile.zhanghaozhuangtai != 1:
|
||
return Response(
|
||
{'code': 400, 'message': '打手状态不允许接单', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 11. 根据订单的抢单要求类型进行验证
|
||
yaoqiuleixing = dingdan_obj.yaoqiuleixing
|
||
|
||
if yaoqiuleixing == 1: # 会员抢单
|
||
# 获取订单的会员ID
|
||
huiyuan_id = dingdan_obj.huiyuan_id
|
||
if not huiyuan_id:
|
||
return Response(
|
||
{'code': 400, 'message': '订单会员ID为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 查询会员购买记录
|
||
try:
|
||
from shangpin.models import Huiyuangoumai
|
||
huiyuan_goumai = Huiyuangoumai.objects.get(
|
||
huiyuan_id=huiyuan_id,
|
||
yonghu_id=new_dashou_id # 注意:yonghu_id存储的是用户ID(yonghuid)
|
||
)
|
||
except Huiyuangoumai.DoesNotExist:
|
||
return Response(
|
||
{'code': 400, 'message': '打手未购买该订单要求的会员', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 检查会员是否过期
|
||
# ✅ 可以调用!因为jiance_shifou_daoqi()是Huiyuangoumai模型的实例方法
|
||
is_expired = huiyuan_goumai.jiance_shifou_daoqi()
|
||
if is_expired or huiyuan_goumai.huiyuan_zhuangtai != 1:
|
||
return Response(
|
||
{'code': 400, 'message': '打手的会员已过期', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
elif yaoqiuleixing == 2: # 押金抢单
|
||
# 获取订单的押金(yongjin字段)
|
||
order_yajin = dingdan_obj.yongjin
|
||
if order_yajin is None:
|
||
return Response(
|
||
{'code': 400, 'message': '订单押金为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 验证打手押金是否大于等于订单押金
|
||
if new_dashou_profile.yajin < order_yajin:
|
||
return Response(
|
||
{'code': 400, 'message': '打手押金不足', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
else:
|
||
# 未知的抢单要求类型
|
||
return Response(
|
||
{'code': 400, 'message': '订单抢单要求类型错误', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 12. 所有验证通过,开始更换打手(使用事务保证一致性)
|
||
with transaction.atomic():
|
||
# 12.1 更新新打手扩展表
|
||
new_dashou_profile.zhuangtai = 0 # 接单中
|
||
new_dashou_profile.jiedanzongliang += 1
|
||
new_dashou_profile.jinrijiedan += 1
|
||
new_dashou_profile.jinyuejiedan += 1
|
||
new_dashou_profile.save()
|
||
|
||
# 12.2 如果原订单有接单打手,则更新原打手状态
|
||
old_dashou_id = dingdan_obj.jiedan_dashou_id
|
||
if old_dashou_id:
|
||
try:
|
||
# 查询原打手主表
|
||
old_dashou_user = UserMain.objects.get(
|
||
yonghuid=old_dashou_id,
|
||
#user_type='dashou'
|
||
)
|
||
|
||
# 获取原打手扩展表
|
||
old_dashou_profile = old_dashou_user.dashou_profile
|
||
|
||
# 更新原打手状态
|
||
old_dashou_profile.zhuangtai = 1 # 空闲
|
||
old_dashou_profile.tuikuanliang += 1 # 退款订单总量+1
|
||
old_dashou_profile.save()
|
||
|
||
logger.info(f"成功更新原打手{old_dashou_id}状态")
|
||
except UserMain.DoesNotExist:
|
||
# 原打手不存在,继续执行不影响更换
|
||
logger.warning(f"原打手{old_dashou_id}不存在,跳过更新")
|
||
except AttributeError:
|
||
# 原打手扩展表不存在,继续执行不影响更换
|
||
logger.warning(f"原打手{old_dashou_id}扩展信息不存在,跳过更新")
|
||
except Exception as e:
|
||
# 其他异常,记录日志但继续执行
|
||
logger.error(f"更新原打手状态异常: {str(e)}", exc_info=True)
|
||
|
||
# 12.3 更新订单表
|
||
dingdan_obj.jiedan_dashou_id = new_dashou_id
|
||
dingdan_obj.zhuangtai = 2 # 进行中状态
|
||
dingdan_obj.save()
|
||
|
||
# 13. 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'message': '更换打手成功',
|
||
'data': None
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"更换打手接口异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
import logging
|
||
|
||
# 获取日志器
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class AdQiangZhiJieDan(APIView):
|
||
"""
|
||
后台强制结单接口
|
||
权限: JWT Token认证 + 管理员身份验证
|
||
前端调用: POST /dingdan/adddqzjd
|
||
参数: {
|
||
"zhanghao": "管理员账号",
|
||
"dingdan_id": "订单ID"
|
||
}
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
"""
|
||
强制结单接口
|
||
核心逻辑:
|
||
1. 验证管理员身份
|
||
2. 验证订单状态为2(进行中)、8(结算中)或4(退款审核中)才能强制结单
|
||
3. 根据发单平台(1=平台发单, 2=商家发单)分别处理
|
||
4. 更新订单状态为3(已完成)
|
||
5. 给打手结算分成金额
|
||
6. 如果是商家发单,还需要更新商家成交订单数量
|
||
"""
|
||
try:
|
||
# 1. 获取前端参数
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = request.data.get('dingdan_id', '').strip()
|
||
|
||
if not qianzhanghao or not dingdan_id:
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2. JWT Token身份验证
|
||
dangqianyonghu = request.user
|
||
|
||
# 3. 验证手机号是否匹配
|
||
if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 4. 验证用户类型是否为管理员
|
||
if dangqianyonghu.user_type != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 5. 验证管理员扩展表是否存在
|
||
try:
|
||
guanliyuan = dangqianyonghu.admin_profile
|
||
except AttributeError:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 6. 查询订单主表
|
||
try:
|
||
from dingdan.models import Dingdan, DingdanPingtai, DingdanShangjia
|
||
dingdan_obj = Dingdan.objects.get(dingdan_id=dingdan_id)
|
||
except Dingdan.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 7. 验证订单状态是否为2、8或4
|
||
if dingdan_obj.zhuangtai not in [2, 8, 4]:
|
||
return Response(
|
||
{'code': 400, 'message': '订单状态不允许强制结单', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 8. 获取订单的重要字段
|
||
jiedan_dashou_id = dingdan_obj.jiedan_dashou_id # 接单打手ID
|
||
dashou_fencheng = dingdan_obj.dashou_fencheng # 打手分成金额
|
||
fadan_pingtai = dingdan_obj.fadan_pingtai # 发单平台
|
||
|
||
# 9. 验证打手分成金额是否存在
|
||
if dashou_fencheng is None:
|
||
return Response(
|
||
{'code': 400, 'message': '订单缺少打手分成金额', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 10. 使用事务保证数据一致性
|
||
with transaction.atomic():
|
||
# 10.1 更新订单状态为3(已完成)
|
||
dingdan_obj.zhuangtai = 3
|
||
dingdan_obj.save()
|
||
|
||
# 10.2 处理打手结算
|
||
if jiedan_dashou_id:
|
||
try:
|
||
# 查询打手用户主表
|
||
dashou_user = UserMain.objects.get(
|
||
yonghuid=jiedan_dashou_id,
|
||
#user_type='dashou'
|
||
)
|
||
|
||
# 获取打手扩展表
|
||
dashou_profile = dashou_user.dashou_profile
|
||
|
||
# 更新打手扩展表数据
|
||
# ✅ 确认字段:chengjiaozongliang 成交单总量
|
||
dashou_profile.chengjiaozongliang += 1
|
||
|
||
# ✅ 确认字段:yue 可提现余额
|
||
dashou_profile.yue += dashou_fencheng
|
||
|
||
# ✅ 确认字段:zonge 打单赚取总额
|
||
dashou_profile.zonge += dashou_fencheng
|
||
|
||
# ✅ 确认字段:jinrishouyi 今日收益金额
|
||
dashou_profile.jinrishouyi += dashou_fencheng
|
||
|
||
# ✅ 确认字段:jinyueshouyi 今月收益金额
|
||
dashou_profile.jinyueshouyi += dashou_fencheng
|
||
#dashou_profile.zhuangtai = 1
|
||
|
||
dashou_profile.save()
|
||
logger.info(f"强制结单成功,打手{jiedan_dashou_id}结算分成:{dashou_fencheng}")
|
||
|
||
except UserMain.DoesNotExist:
|
||
# 打手用户不存在,记录日志但不影响强制结单
|
||
logger.warning(f"强制结单:打手{jiedan_dashou_id}不存在,跳过打手结算")
|
||
except AttributeError:
|
||
# 打手扩展表不存在,记录日志但不影响强制结单
|
||
logger.warning(f"强制结单:打手{jiedan_dashou_id}扩展信息不存在,跳过打手结算")
|
||
|
||
# 10.3 如果是商家发单,还需要更新商家扩展表
|
||
if fadan_pingtai == 2:
|
||
try:
|
||
# 获取商家订单扩展表
|
||
shangjia_kuozhan = dingdan_obj.shangjia_kuozhan
|
||
shangjia_id = shangjia_kuozhan.shangjia_id
|
||
|
||
if shangjia_id:
|
||
# 查询商家用户主表
|
||
shangjia_user = UserMain.objects.get(
|
||
yonghuid=shangjia_id,
|
||
#user_type='shop'
|
||
)
|
||
|
||
# 获取商家扩展表
|
||
shangjia_profile = shangjia_user.shop_profile
|
||
|
||
# ✅ 确认字段:chengjiao 商家成交订单总量
|
||
shangjia_profile.chengjiao += 1
|
||
shangjia_profile.save()
|
||
|
||
logger.info(f"强制结单成功,商家{shangjia_id}成交订单+1")
|
||
|
||
except DingdanShangjia.DoesNotExist:
|
||
# 商家订单扩展表不存在,跳过商家更新
|
||
logger.warning(f"强制结单:订单{dingdan_id}商家扩展表不存在,跳过商家更新")
|
||
except UserMain.DoesNotExist:
|
||
# 商家用户不存在,跳过商家更新
|
||
logger.warning(f"强制结单:商家{shangjia_id}不存在,跳过商家更新")
|
||
except AttributeError:
|
||
# 商家扩展表不存在,跳过商家更新
|
||
logger.warning(f"强制结单:商家{shangjia_id}扩展信息不存在,跳过商家更新")
|
||
|
||
if fadan_pingtai == 2 and jiedan_dashou_id:
|
||
from dingdan.utils import settle_shangjia_order_guanshi_fenhong
|
||
settle_order = Dingdan.objects.get(dingdan_id=dingdan_id)
|
||
settle_shangjia_order_guanshi_fenhong(settle_order, jiedan_dashou_id)
|
||
|
||
# 11. 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'message': '强制结单成功',
|
||
'data': None
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"强制结单接口异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
class AdJuJueTuiKuan(APIView):
|
||
"""
|
||
后台拒绝退款接口
|
||
权限: JWT Token认证 + 管理员身份验证
|
||
前端调用: POST /dingdan/adddjj
|
||
参数: {
|
||
"zhanghao": "管理员账号",
|
||
"dingdan_id": "订单ID",
|
||
"jujue_liyou": "拒绝退款理由(可选)"
|
||
}
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
"""
|
||
拒绝退款接口
|
||
核心逻辑:
|
||
1. 验证管理员身份
|
||
2. 拒绝退款后订单状态改为6(退款失败)
|
||
3. 给打手结算分成金额
|
||
4. 如果是商家发单,更新商家成交订单数量
|
||
5. 更新退款记录表,状态改为2(驳回)
|
||
"""
|
||
try:
|
||
# 1. 获取前端参数
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = request.data.get('dingdan_id', '').strip()
|
||
jujue_liyou = request.data.get('jujue_liyou', '').strip()
|
||
|
||
if not qianzhanghao or not dingdan_id:
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2. JWT Token身份验证
|
||
dangqianyonghu = request.user
|
||
|
||
# 3. 验证手机号是否匹配
|
||
if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 4. 验证用户类型是否为管理员
|
||
if dangqianyonghu.user_type != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 5. 验证管理员扩展表是否存在
|
||
try:
|
||
guanliyuan = dangqianyonghu.admin_profile
|
||
except AttributeError:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 6. 查询订单主表
|
||
try:
|
||
from dingdan.models import Dingdan, DingdanShangjia, Tuikuanjilu
|
||
dingdan_obj = Dingdan.objects.get(dingdan_id=dingdan_id)
|
||
except Dingdan.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 7. 获取订单的重要字段
|
||
jiedan_dashou_id = dingdan_obj.jiedan_dashou_id # 接单打手ID
|
||
dashou_fencheng = dingdan_obj.dashou_fencheng # 打手分成金额
|
||
fadan_pingtai = dingdan_obj.fadan_pingtai # 发单平台
|
||
|
||
# 8. 验证打手分成金额是否存在
|
||
if dashou_fencheng is None:
|
||
return Response(
|
||
{'code': 400, 'message': '订单缺少打手分成金额', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 9. 使用事务保证数据一致性
|
||
with transaction.atomic():
|
||
# 9.1 更新订单状态为6(退款失败)
|
||
dingdan_obj.zhuangtai = 6
|
||
dingdan_obj.save()
|
||
|
||
# 9.2 处理打手结算
|
||
if jiedan_dashou_id:
|
||
try:
|
||
# 查询打手用户主表
|
||
dashou_user = UserMain.objects.get(
|
||
yonghuid=jiedan_dashou_id,
|
||
#user_type='dashou'
|
||
)
|
||
|
||
# 获取打手扩展表
|
||
dashou_profile = dashou_user.dashou_profile
|
||
|
||
# 更新打手扩展表数据
|
||
# ✅ 确认字段:chengjiaozongliang 成交单总量
|
||
dashou_profile.chengjiaozongliang += 1
|
||
|
||
# ✅ 确认字段:yue 可提现余额
|
||
dashou_profile.yue += dashou_fencheng
|
||
|
||
# ✅ 确认字段:zonge 打单赚取总额
|
||
dashou_profile.zonge += dashou_fencheng
|
||
|
||
# ✅ 确认字段:jinrishouyi 今日收益金额
|
||
dashou_profile.jinrishouyi += dashou_fencheng
|
||
|
||
# ✅ 确认字段:jinyueshouyi 今月收益金额
|
||
dashou_profile.jinyueshouyi += dashou_fencheng
|
||
|
||
dashou_profile.save()
|
||
logger.info(f"拒绝退款成功,打手{jiedan_dashou_id}结算分成:{dashou_fencheng}")
|
||
|
||
except UserMain.DoesNotExist:
|
||
# 打手用户不存在,记录日志但不影响操作
|
||
logger.warning(f"拒绝退款:打手{jiedan_dashou_id}不存在,跳过打手结算")
|
||
except AttributeError:
|
||
# 打手扩展表不存在,记录日志但不影响操作
|
||
logger.warning(f"拒绝退款:打手{jiedan_dashou_id}扩展信息不存在,跳过打手结算")
|
||
|
||
# 9.3 如果是商家发单,还需要更新商家扩展表
|
||
if fadan_pingtai == 2:
|
||
try:
|
||
# 获取商家订单扩展表
|
||
shangjia_kuozhan = dingdan_obj.shangjia_kuozhan
|
||
shangjia_id = shangjia_kuozhan.shangjia_id
|
||
|
||
if shangjia_id:
|
||
# 查询商家用户主表
|
||
shangjia_user = UserMain.objects.get(
|
||
yonghuid=shangjia_id,
|
||
#user_type='shop'
|
||
)
|
||
|
||
# 获取商家扩展表
|
||
shangjia_profile = shangjia_user.shop_profile
|
||
|
||
# ✅ 确认字段:chengjiao 商家成交订单总量
|
||
shangjia_profile.chengjiao += 1
|
||
shangjia_profile.save()
|
||
|
||
logger.info(f"拒绝退款成功,商家{shangjia_id}成交订单+1")
|
||
|
||
except DingdanShangjia.DoesNotExist:
|
||
# 商家订单扩展表不存在,跳过商家更新
|
||
logger.warning(f"拒绝退款:订单{dingdan_id}商家扩展表不存在,跳过商家更新")
|
||
except UserMain.DoesNotExist:
|
||
# 商家用户不存在,跳过商家更新
|
||
logger.warning(f"拒绝退款:商家{shangjia_id}不存在,跳过商家更新")
|
||
except AttributeError:
|
||
# 商家扩展表不存在,跳过商家更新
|
||
logger.warning(f"拒绝退款:商家{shangjia_id}扩展信息不存在,跳过商家更新")
|
||
|
||
# 9.4 更新退款记录表
|
||
try:
|
||
# 查询退款记录表
|
||
tuikuan_jilu = Tuikuanjilu.objects.get(dingdan_id=dingdan_id)
|
||
|
||
# ✅ 确认字段:sqzhuangtai 申请状态,改为2(驳回)
|
||
tuikuan_jilu.sqzhuangtai = 2
|
||
|
||
# ✅ 确认字段:bhliyou 驳回理由
|
||
tuikuan_jilu.bhliyou = jujue_liyou
|
||
|
||
# ✅ 确认字段:chuliid 处理人ID(使用管理员账号,即手机号)
|
||
tuikuan_jilu.chuliid = qianzhanghao
|
||
|
||
tuikuan_jilu.save()
|
||
logger.info(f"拒绝退款成功,更新退款记录表,订单:{dingdan_id}")
|
||
|
||
except Tuikuanjilu.DoesNotExist:
|
||
# 退款记录表不存在,记录日志但不影响主流程
|
||
logger.warning(f"拒绝退款:订单{dingdan_id}退款记录不存在,跳过更新")
|
||
|
||
# 10. 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'message': '拒绝退款成功',
|
||
'data': None
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"拒绝退款接口异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
class AdTongYiTuiKuanShangJia(APIView):
|
||
"""
|
||
后台同意退款接口(商家发单)
|
||
权限: JWT Token认证 + 管理员身份验证 + 密码验证
|
||
前端调用: POST /dingdan/adddsjtk
|
||
参数: {
|
||
"zhanghao": "管理员账号",
|
||
"dingdan_id": "订单ID",
|
||
"tuikuan_liyou": "退款理由(可选)",
|
||
"pay_password": "支付密码(openid)"
|
||
}
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
"""
|
||
同意退款接口(商家发单)
|
||
核心逻辑:
|
||
1. 验证管理员身份
|
||
2. 验证支付密码(openid)
|
||
3. 检查订单状态为4(退款审核中)
|
||
4. 更新订单状态为5(已退款)
|
||
5. 更新打手退款订单数量
|
||
6. 更新商家退款订单数量和余额(加上订单金额)
|
||
7. 更新退款记录表,状态改为1(成功)
|
||
"""
|
||
try:
|
||
# 1. 获取前端参数
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = request.data.get('dingdan_id', '').strip()
|
||
tuikuan_liyou = request.data.get('tuikuan_liyou', '').strip()
|
||
pay_password = request.data.get('pay_password', '').strip()
|
||
|
||
if not qianzhanghao or not dingdan_id or not pay_password:
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2. JWT Token身份验证
|
||
dangqianyonghu = request.user
|
||
|
||
# 3. 验证手机号是否匹配
|
||
if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 4. 验证用户类型是否为管理员
|
||
if dangqianyonghu.user_type != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 5. 验证管理员扩展表是否存在
|
||
try:
|
||
guanliyuan = dangqianyonghu.admin_profile
|
||
except AttributeError:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 6. 验证支付密码(对比openid)
|
||
if pay_password != dangqianyonghu.openid:
|
||
return Response(
|
||
{'code': 401, 'message': '支付密码错误', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 7. 查询订单主表
|
||
try:
|
||
from dingdan.models import Dingdan, DingdanShangjia, Tuikuanjilu
|
||
dingdan_obj = Dingdan.objects.get(dingdan_id=dingdan_id)
|
||
except Dingdan.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 8. 验证订单状态为4(退款审核中)
|
||
# 8. 验证订单状态是否为允许退款的状态
|
||
allowed_statuses = [4, 8, 1, 2, 7] # 允许退款的状态:退款审核中、已下单、结算中、进行中、指定中
|
||
if dingdan_obj.zhuangtai not in allowed_statuses:
|
||
return Response(
|
||
{
|
||
'code': 400,
|
||
'message': f'订单当前状态不允许退款。允许退款的状态为:退款审核中(4)、已下单(8)、结算中(1)、进行中(2)、指定中(7)',
|
||
'data': None
|
||
},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
|
||
|
||
|
||
'''if dingdan_obj.zhuangtai != 4:
|
||
return Response(
|
||
{'code': 400, 'message': '订单状态不是退款审核中', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)'''
|
||
|
||
# 9. 验证订单为商家发单
|
||
if dingdan_obj.fadan_pingtai != 2:
|
||
return Response(
|
||
{'code': 400, 'message': '此接口只处理商家发单的退款', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 10. 获取订单的重要字段
|
||
jiedan_dashou_id = dingdan_obj.jiedan_dashou_id # 接单打手ID
|
||
jine = dingdan_obj.jine # 订单金额(不是打手分成)
|
||
|
||
# 11. 验证订单金额是否存在
|
||
if jine is None:
|
||
return Response(
|
||
{'code': 400, 'message': '订单缺少订单金额', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 12. 获取商家ID
|
||
try:
|
||
shangjia_kuozhan = dingdan_obj.shangjia_kuozhan
|
||
shangjia_id = shangjia_kuozhan.shangjia_id
|
||
except DingdanShangjia.DoesNotExist:
|
||
return Response(
|
||
{'code': 400, 'message': '订单商家信息不存在', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 13. 使用事务保证数据一致性
|
||
with transaction.atomic():
|
||
# 13.1 更新订单状态为5(已退款)
|
||
dingdan_obj.zhuangtai = 5
|
||
dingdan_obj.save()
|
||
|
||
# 13.2 更新打手扩展表(退款订单总量+1)
|
||
if jiedan_dashou_id:
|
||
try:
|
||
# 查询打手用户主表
|
||
dashou_user = UserMain.objects.get(
|
||
yonghuid=jiedan_dashou_id,
|
||
#user_type='dashou'
|
||
)
|
||
|
||
# 获取打手扩展表
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
dashou_profile = dashou_user.dashou_profile
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
dashou_profile.zhuangtai = 1
|
||
|
||
# ✅ 确认字段:tuikuanliang 退款订单总量
|
||
dashou_profile.tuikuanliang += 1
|
||
|
||
dashou_profile.save()
|
||
logger.info(f"同意退款成功,打手{jiedan_dashou_id}退款订单+1")
|
||
|
||
except UserMain.DoesNotExist:
|
||
# 打手用户不存在,记录日志但不影响操作
|
||
logger.warning(f"同意退款:打手{jiedan_dashou_id}不存在,跳过打手更新")
|
||
except AttributeError:
|
||
# 打手扩展表不存在,记录日志但不影响操作
|
||
logger.warning(f"同意退款:打手{jiedan_dashou_id}扩展信息不存在,跳过打手更新")
|
||
|
||
# 13.3 更新商家扩展表
|
||
if shangjia_id:
|
||
try:
|
||
# 查询商家用户主表
|
||
shangjia_user = UserMain.objects.get(
|
||
yonghuid=shangjia_id,
|
||
#user_type='shop'
|
||
)
|
||
|
||
# 获取商家扩展表
|
||
shangjia_profile = shangjia_user.shop_profile
|
||
|
||
# ✅ 确认字段:tuikuan 退款订单总量
|
||
shangjia_profile.tuikuan += 1
|
||
|
||
# ✅ 确认字段:yue 商家余额(加上订单金额)
|
||
shangjia_profile.yue += jine
|
||
|
||
shangjia_profile.save()
|
||
logger.info(f"同意退款成功,商家{shangjia_id}退款订单+1,余额增加:{jine}")
|
||
|
||
except UserMain.DoesNotExist:
|
||
# 商家用户不存在,记录日志但不影响操作
|
||
logger.warning(f"同意退款:商家{shangjia_id}不存在,跳过商家更新")
|
||
except AttributeError:
|
||
# 商家扩展表不存在,记录日志但不影响操作
|
||
logger.warning(f"同意退款:商家{shangjia_id}扩展信息不存在,跳过商家更新")
|
||
|
||
# 13.4 更新退款记录表
|
||
try:
|
||
# 查询退款记录表
|
||
tuikuan_jilu = Tuikuanjilu.objects.get(dingdan_id=dingdan_id)
|
||
|
||
# ✅ 确认字段:sqzhuangtai 申请状态,改为1(成功)
|
||
tuikuan_jilu.sqzhuangtai = 1
|
||
|
||
# 可以添加同意理由字段,如:tongyi_liyou
|
||
# tuikuan_jilu.tongyi_liyou = tuikuan_liyou
|
||
|
||
# ✅ 确认字段:chuliid 处理人ID(使用管理员账号,即手机号)
|
||
tuikuan_jilu.chuliid = qianzhanghao
|
||
|
||
tuikuan_jilu.save()
|
||
logger.info(f"同意退款成功,更新退款记录表,订单:{dingdan_id}")
|
||
|
||
except Tuikuanjilu.DoesNotExist:
|
||
# 退款记录表不存在,记录日志但不影响主流程
|
||
logger.warning(f"同意退款:订单{dingdan_id}退款记录不存在,跳过更新")
|
||
|
||
# 14. 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'message': '同意退款成功',
|
||
'data': None
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"同意退款接口异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
# dingdan/views.py
|
||
import logging
|
||
import time
|
||
import random
|
||
import hashlib
|
||
import requests
|
||
import xml.etree.ElementTree as ET
|
||
from decimal import Decimal
|
||
from django.db import transaction
|
||
from django.conf import settings
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework import status
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from rest_framework.parsers import JSONParser
|
||
|
||
from yonghu.models import UserMain, UserDashou, UserBoss
|
||
from dingdan.models import Dingdan, DingdanPingtai, Tuikuanjilu
|
||
from peizhi.models import Szjilu
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class AdTongYiTuiKuanPingTai(APIView):
|
||
"""
|
||
后台同意退款接口(平台发单)
|
||
权限: JWT Token认证 + 管理员身份验证 + 密码验证
|
||
前端调用: POST /dingdan/adddpttk
|
||
参数: {
|
||
"zhanghao": "管理员账号",
|
||
"dingdan_id": "订单ID",
|
||
"tuikuan_liyou": "退款理由(可选)",
|
||
"pay_password": "支付密码(openid)"
|
||
}
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 获取前端参数
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = request.data.get('dingdan_id', '').strip()
|
||
tuikuan_liyou = request.data.get('tuikuan_liyou', '').strip()
|
||
pay_password = request.data.get('pay_password', '').strip()
|
||
|
||
if not qianzhanghao or not dingdan_id or not pay_password:
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2. JWT Token身份验证
|
||
dangqianyonghu = request.user
|
||
|
||
# 3. 验证手机号是否匹配
|
||
if str(getattr(dangqianyonghu, 'phone', '')) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 4. 验证用户类型是否为管理员
|
||
if dangqianyonghu.user_type != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 5. 验证管理员扩展表是否存在
|
||
try:
|
||
guanliyuan = dangqianyonghu.admin_profile
|
||
except AttributeError:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 6. 验证支付密码(对比openid)
|
||
if pay_password != dangqianyonghu.openid:
|
||
return Response(
|
||
{'code': 401, 'message': '支付密码错误', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 7. 查询订单主表(加锁)
|
||
with transaction.atomic():
|
||
try:
|
||
dingdan_obj = Dingdan.objects.select_related('pingtai_kuozhan').select_for_update().get(dingdan_id=dingdan_id)
|
||
except Dingdan.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 8. 验证订单状态为1、7、2、8、4(允许退款的状态)
|
||
if dingdan_obj.zhuangtai not in [1, 7, 2, 8, 4]:
|
||
return Response(
|
||
{'code': 400, 'message': '订单状态不允许退款', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 9. 验证订单为平台发单
|
||
if dingdan_obj.fadan_pingtai != 1:
|
||
return Response(
|
||
{'code': 400, 'message': '此接口只处理平台发单的退款', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 10. 获取订单的重要字段
|
||
jiedan_dashou_id = dingdan_obj.jiedan_dashou_id
|
||
jine = dingdan_obj.jine
|
||
if jine is None:
|
||
return Response(
|
||
{'code': 400, 'message': '订单缺少订单金额', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 11. 获取老板ID(从平台订单扩展表)
|
||
if not hasattr(dingdan_obj, 'pingtai_kuozhan') or not dingdan_obj.pingtai_kuozhan:
|
||
return Response(
|
||
{'code': 400, 'message': '订单老板信息不存在', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
laoban_id = dingdan_obj.pingtai_kuozhan.laoban_id
|
||
|
||
# 12. 调用微信退款接口(优先使用 wechat_transaction_id)
|
||
transaction_id = getattr(dingdan_obj, 'wechat_transaction_id', None)
|
||
weixin_refund_result = self.call_weixin_refund(
|
||
out_trade_no=dingdan_id,
|
||
jine=jine,
|
||
transaction_id=transaction_id
|
||
)
|
||
|
||
if not weixin_refund_result.get('success'):
|
||
logger.error(f"微信退款失败,订单:{dingdan_id},错误:{weixin_refund_result.get('msg')}")
|
||
return Response(
|
||
{'code': 400, 'message': weixin_refund_result.get('msg', '退款失败')},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 13. 更新订单状态为5(已退款)
|
||
dingdan_obj.zhuangtai = 5
|
||
dingdan_obj.save()
|
||
|
||
# 14. 更新打手扩展表
|
||
if jiedan_dashou_id:
|
||
try:
|
||
dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id)
|
||
dashou_profile = dashou_user.dashou_profile
|
||
dashou_profile.zhuangtai = 1 # 打手状态设为空闲
|
||
dashou_profile.tuikuanliang += 1
|
||
dashou_profile.save()
|
||
except (UserMain.DoesNotExist, AttributeError):
|
||
logger.warning(f"平台退款:打手{jiedan_dashou_id}不存在或扩展信息缺失,跳过")
|
||
|
||
# 15. 更新老板扩展表
|
||
if laoban_id:
|
||
try:
|
||
laoban_user = UserMain.objects.get(yonghuid=laoban_id)
|
||
laoban_profile = laoban_user.boss_profile
|
||
laoban_profile.alltui += 1
|
||
laoban_profile.zonge -= jine
|
||
if laoban_profile.zonge < 0:
|
||
laoban_profile.zonge = 0
|
||
laoban_profile.save()
|
||
except (UserMain.DoesNotExist, AttributeError):
|
||
logger.warning(f"平台退款:老板{laoban_id}不存在或扩展信息缺失,跳过")
|
||
|
||
# 16. 更新收支记录表(id=1)
|
||
try:
|
||
szjilu_obj = Szjilu.objects.get(id=1)
|
||
szjilu_obj.zongsy -= jine
|
||
szjilu_obj.zongzc += jine
|
||
szjilu_obj.jrzc += jine
|
||
szjilu_obj.save()
|
||
except Szjilu.DoesNotExist:
|
||
logger.error(f"平台退款:收支记录表(id=1)不存在,跳过")
|
||
except Exception as e:
|
||
logger.error(f"更新收支记录表异常: {str(e)}")
|
||
|
||
# 17. 更新退款记录表
|
||
try:
|
||
tuikuan_jilu, created = Tuikuanjilu.objects.get_or_create(
|
||
dingdan_id=dingdan_id,
|
||
defaults={
|
||
'dashouid': jiedan_dashou_id if jiedan_dashou_id else '',
|
||
'qingqiuid': laoban_id if laoban_id else '',
|
||
'chuliid': qianzhanghao,
|
||
'liyou': tuikuan_liyou if tuikuan_liyou else '给老板退款',
|
||
'sqzhuangtai': 1,
|
||
'jine': jine,
|
||
'dashou_fencheng': dingdan_obj.dashou_fencheng or 0,
|
||
'jieshao': dingdan_obj.jieshao or '',
|
||
'beizhu': '给老板退款',
|
||
'nicheng': dingdan_obj.nicheng or '',
|
||
}
|
||
)
|
||
if not created:
|
||
tuikuan_jilu.sqzhuangtai = 1
|
||
if tuikuan_liyou:
|
||
tuikuan_jilu.liyou = tuikuan_liyou
|
||
tuikuan_jilu.chuliid = qianzhanghao
|
||
tuikuan_jilu.save()
|
||
except Exception as e:
|
||
logger.error(f"更新退款记录表异常: {str(e)}")
|
||
|
||
logger.info(f"平台退款成功,订单:{dingdan_id},退款单号:{weixin_refund_result.get('refund_id')}")
|
||
|
||
return Response({'code': 0, 'message': '退款处理成功', 'data': None})
|
||
|
||
except Exception as e:
|
||
logger.error(f"平台退款接口异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
def call_weixin_refund(self, out_trade_no, jine, transaction_id=None):
|
||
"""
|
||
调用微信退款接口
|
||
:param out_trade_no: 商户订单号
|
||
:param jine: 退款金额
|
||
:param transaction_id: 微信支付订单号(优先使用)
|
||
:return: {'success': True/False, 'msg': '消息', 'refund_id': '退款单号'}
|
||
"""
|
||
try:
|
||
# 检查配置
|
||
required_settings = ['WEIXIN_APPID', 'WEIXIN_MCHID', 'WEIXIN_SHANGHUMIYAO', 'WEIXIN_CERT_PATH', 'WEIXIN_KEY_PATH']
|
||
for setting in required_settings:
|
||
if not hasattr(settings, setting) or not getattr(settings, setting):
|
||
return {'success': False, 'msg': f'缺少微信支付配置: {setting}'}
|
||
|
||
# 生成退款单号
|
||
refund_id = f"TK{int(time.time())}{random.randint(1000, 9999)}"
|
||
total_fee_in_fen = int(float(jine) * 100)
|
||
|
||
# 构造请求参数
|
||
refund_data = {
|
||
'appid': settings.WEIXIN_APPID,
|
||
'mch_id': settings.WEIXIN_MCHID,
|
||
'nonce_str': hashlib.md5(str(time.time()).encode()).hexdigest(),
|
||
'out_refund_no': refund_id,
|
||
'total_fee': total_fee_in_fen,
|
||
'refund_fee': total_fee_in_fen,
|
||
'op_user_id': settings.WEIXIN_MCHID,
|
||
'refund_account': 'REFUND_SOURCE_UNSETTLED_FUNDS'
|
||
}
|
||
# 优先使用 transaction_id,否则用 out_trade_no
|
||
if transaction_id:
|
||
refund_data['transaction_id'] = transaction_id
|
||
else:
|
||
refund_data['out_trade_no'] = out_trade_no
|
||
|
||
# 生成签名
|
||
sorted_params = sorted(refund_data.items())
|
||
sign_string = '&'.join([f"{k}={v}" for k, v in sorted_params if v])
|
||
sign_string += f"&key={settings.WEIXIN_SHANGHUMIYAO}"
|
||
sign = hashlib.md5(sign_string.encode()).hexdigest().upper()
|
||
refund_data['sign'] = sign
|
||
|
||
xml_data = self.dict_to_xml(refund_data)
|
||
url = "https://api.mch.weixin.qq.com/secapi/pay/refund"
|
||
response = requests.post(
|
||
url,
|
||
data=xml_data.encode('utf-8'),
|
||
headers={'Content-Type': 'application/xml'},
|
||
cert=(settings.WEIXIN_CERT_PATH, settings.WEIXIN_KEY_PATH),
|
||
timeout=30
|
||
)
|
||
|
||
if response.status_code == 200:
|
||
result = self.xml_to_dict(response.content)
|
||
if result.get('return_code') == 'SUCCESS' and result.get('result_code') == 'SUCCESS':
|
||
logger.info(f"微信退款成功,订单:{out_trade_no},退款单号:{refund_id}")
|
||
return {'success': True, 'msg': '退款申请提交成功', 'refund_id': refund_id}
|
||
else:
|
||
error_msg = result.get('err_code_des', result.get('return_msg', '退款申请失败'))
|
||
logger.error(f"微信退款失败,订单:{out_trade_no},错误:{error_msg}")
|
||
return {'success': False, 'msg': f'微信退款失败: {error_msg}'}
|
||
else:
|
||
logger.error(f"微信退款接口调用失败,状态码:{response.status_code}")
|
||
return {'success': False, 'msg': '微信退款接口调用失败'}
|
||
|
||
except requests.exceptions.Timeout:
|
||
logger.error(f"微信退款接口请求超时,订单:{out_trade_no}")
|
||
return {'success': False, 'msg': '微信退款接口请求超时'}
|
||
except Exception as e:
|
||
logger.error(f"调用微信退款接口异常: {str(e)}")
|
||
return {'success': False, 'msg': f'退款处理异常: {str(e)}'}
|
||
|
||
def dict_to_xml(self, dict_data):
|
||
xml = ["<xml>"]
|
||
for k, v in dict_data.items():
|
||
xml.append(f"<{k}>{v}</{k}>")
|
||
xml.append("</xml>")
|
||
return "".join(xml)
|
||
|
||
def xml_to_dict(self, xml_data):
|
||
try:
|
||
root = ET.fromstring(xml_data)
|
||
return {child.tag: child.text for child in root}
|
||
except ET.ParseError as e:
|
||
logger.error(f"XML解析失败: {str(e)}")
|
||
return {'return_code': 'FAIL', 'return_msg': 'XML解析失败'}
|
||
|
||
|
||
|
||
|
||
'''from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from rest_framework.parsers import JSONParser
|
||
from rest_framework import status
|
||
from django.db import transaction
|
||
from django.conf import settings
|
||
import logging
|
||
import time
|
||
import random
|
||
import hashlib
|
||
import requests
|
||
import xml.etree.ElementTree as ET
|
||
|
||
# 获取日志器
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 在 dingdan/utils.py(如果没有就创建)或任何合适的公共工具文件中添加
|
||
|
||
|
||
|
||
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from rest_framework.parsers import JSONParser
|
||
from rest_framework import status
|
||
from django.db import transaction
|
||
from django.conf import settings
|
||
import logging
|
||
import time
|
||
import random
|
||
import hashlib
|
||
import requests
|
||
import xml.etree.ElementTree as ET
|
||
|
||
# 获取日志器
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
|
||
class AdTongYiTuiKuanPingTai(APIView):
|
||
"""
|
||
后台同意退款接口(平台发单)
|
||
权限: JWT Token认证 + 管理员身份验证 + 密码验证
|
||
前端调用: POST /dingdan/adddpttk
|
||
参数: {
|
||
"zhanghao": "管理员账号",
|
||
"dingdan_id": "订单ID",
|
||
"tuikuan_liyou": "退款理由(可选)",
|
||
"pay_password": "支付密码(openid)"
|
||
}
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
"""
|
||
同意退款接口(平台发单)
|
||
核心逻辑:
|
||
1. 验证管理员身份和支付密码
|
||
2. 验证订单状态为2、8或4(进行中、结算中、退款审核中)
|
||
3. 调用微信退款接口
|
||
4. 如果微信受理成功,更新数据库(订单、打手、老板、收支、退款记录)
|
||
5. 如果微信受理失败,返回错误,数据库不变
|
||
"""
|
||
try:
|
||
# 1. 获取前端参数
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = request.data.get('dingdan_id', '').strip()
|
||
tuikuan_liyou = request.data.get('tuikuan_liyou', '').strip()
|
||
pay_password = request.data.get('pay_password', '').strip()
|
||
|
||
if not qianzhanghao or not dingdan_id or not pay_password:
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2. JWT Token身份验证
|
||
dangqianyonghu = request.user
|
||
|
||
# 3. 验证手机号是否匹配
|
||
if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 4. 验证用户类型是否为管理员
|
||
if dangqianyonghu.user_type != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 5. 验证管理员扩展表是否存在
|
||
try:
|
||
guanliyuan = dangqianyonghu.admin_profile
|
||
except AttributeError:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 6. 验证支付密码(对比openid)
|
||
if pay_password != dangqianyonghu.openid:
|
||
return Response(
|
||
{'code': 401, 'message': '支付密码错误', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 7. 查询订单主表
|
||
try:
|
||
from dingdan.models import Dingdan, DingdanPingtai, Tuikuanjilu
|
||
from peizhi.models import Szjilu
|
||
|
||
# 使用select_related预加载平台订单扩展表
|
||
dingdan_obj = Dingdan.objects.select_related('pingtai_kuozhan').get(dingdan_id=dingdan_id)
|
||
except Dingdan.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 8. 验证订单状态为2、8或4
|
||
if dingdan_obj.zhuangtai not in [1,7,2, 8, 4]:
|
||
return Response(
|
||
{'code': 400, 'message': '订单状态不允许退款', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 9. 验证订单为平台发单
|
||
if dingdan_obj.fadan_pingtai != 1:
|
||
return Response(
|
||
{'code': 400, 'message': '此接口只处理平台发单的退款', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 10. 获取订单的重要字段
|
||
jiedan_dashou_id = dingdan_obj.jiedan_dashou_id # 接单打手ID
|
||
jine = dingdan_obj.jine # 订单金额(全额退款)
|
||
|
||
# 11. 验证订单金额是否存在
|
||
if jine is None:
|
||
return Response(
|
||
{'code': 400, 'message': '订单缺少订单金额', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 12. 获取老板ID(从平台订单扩展表)
|
||
try:
|
||
pingtai_kuozhan = dingdan_obj.pingtai_kuozhan
|
||
laoban_id = pingtai_kuozhan.laoban_id
|
||
except DingdanPingtai.DoesNotExist:
|
||
return Response(
|
||
{'code': 400, 'message': '订单老板信息不存在', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# ========== 修改点开始 ==========
|
||
# 13. 先调用微信退款接口,成功后才更新数据库
|
||
weixin_refund_result = self.call_weixin_refund(dingdan_id, jine)
|
||
|
||
if not weixin_refund_result.get('success'):
|
||
# 微信退款失败,直接返回错误,数据库不更新
|
||
logger.error(f"微信退款失败,订单:{dingdan_id},错误:{weixin_refund_result.get('msg')}")
|
||
return Response(
|
||
{'code': 400, 'message': weixin_refund_result.get('msg', '退款失败')},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 14. 微信退款受理成功,开始事务更新数据库
|
||
with transaction.atomic():
|
||
# 14.1 更新订单状态为5(已退款)
|
||
dingdan_obj.zhuangtai = 5
|
||
dingdan_obj.save()
|
||
|
||
# 14.2 更新打手扩展表(退款订单总量+1)
|
||
if jiedan_dashou_id:
|
||
try:
|
||
# 查询打手用户主表
|
||
dashou_user = UserMain.objects.get(
|
||
yonghuid=jiedan_dashou_id,
|
||
#user_type='dashou'
|
||
)
|
||
dashou_profile = dashou_user.dashou_profile
|
||
dashou_profile.zhuangtai = 1
|
||
dashou_profile.tuikuanliang += 1
|
||
dashou_profile.save()
|
||
except UserMain.DoesNotExist:
|
||
logger.warning(f"平台退款:打手{jiedan_dashou_id}不存在,跳过打手更新")
|
||
except AttributeError:
|
||
logger.warning(f"平台退款:打手{jiedan_dashou_id}扩展信息不存在,跳过打手更新")
|
||
|
||
# 14.3 更新老板扩展表
|
||
if laoban_id:
|
||
try:
|
||
laoban_user = UserMain.objects.get(
|
||
yonghuid=laoban_id,
|
||
#user_type='normal'
|
||
)
|
||
laoban_profile = laoban_user.boss_profile
|
||
laoban_profile.alltui += 1
|
||
laoban_profile.zonge -= jine
|
||
if laoban_profile.zonge < 0:
|
||
laoban_profile.zonge = 0
|
||
laoban_profile.save()
|
||
except UserMain.DoesNotExist:
|
||
logger.warning(f"平台退款:老板{laoban_id}不存在,跳过老板更新")
|
||
except AttributeError:
|
||
logger.warning(f"平台退款:老板{laoban_id}扩展信息不存在,跳过老板更新")
|
||
|
||
# 14.4 更新收支记录表(id=1的记录)
|
||
try:
|
||
|
||
#更新支出记录
|
||
from dingdan.utils import update_daily_payout
|
||
update_daily_payout(jine)
|
||
|
||
szjilu_obj = Szjilu.objects.get(id=1)
|
||
szjilu_obj.zongsy -= jine
|
||
szjilu_obj.zongzc += jine
|
||
szjilu_obj.jrzc += jine
|
||
szjilu_obj.save()
|
||
except Szjilu.DoesNotExist:
|
||
logger.error(f"平台退款:收支记录表(id=1)不存在,跳过更新")
|
||
except Exception as e:
|
||
logger.error(f"更新收支记录表异常: {str(e)}")
|
||
|
||
# 14.5 更新退款记录表
|
||
try:
|
||
# 查询退款记录表,是否已有该订单的记录
|
||
try:
|
||
tuikuan_jilu = Tuikuanjilu.objects.get(dingdan_id=dingdan_id)
|
||
tuikuan_jilu.sqzhuangtai = 1
|
||
if tuikuan_liyou:
|
||
tuikuan_jilu.liyou = tuikuan_liyou
|
||
tuikuan_jilu.chuliid = qianzhanghao
|
||
tuikuan_jilu.save()
|
||
except Tuikuanjilu.DoesNotExist:
|
||
# 退款记录表不存在,创建新的记录
|
||
Tuikuanjilu.objects.create(
|
||
dingdan_id=dingdan_id,
|
||
dashouid=jiedan_dashou_id if jiedan_dashou_id else '',
|
||
qingqiuid=laoban_id if laoban_id else '',
|
||
chuliid=qianzhanghao,
|
||
liyou=tuikuan_liyou if tuikuan_liyou else '给老板退款',
|
||
sqzhuangtai=1,
|
||
jine=jine,
|
||
dashou_fencheng=dingdan_obj.dashou_fencheng if dingdan_obj.dashou_fencheng else 0,
|
||
jieshao=dingdan_obj.jieshao if dingdan_obj.jieshao else '',
|
||
beizhu='给老板退款',
|
||
nicheng=dingdan_obj.nicheng if dingdan_obj.nicheng else '',
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"更新退款记录表异常: {str(e)}")
|
||
|
||
logger.info(f"平台退款成功,订单:{dingdan_id},退款单号:{weixin_refund_result.get('refund_id')}")
|
||
# ========== 修改点结束 ==========
|
||
|
||
# 15. 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'message': '退款处理成功',
|
||
'data': None
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"平台退款接口异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
|
||
def call_weixin_refund(self, dingdan_id, jine):
|
||
"""
|
||
调用微信退款接口
|
||
参考你提供的接口,但根据现有模型和配置调整
|
||
|
||
需要在settings.py中配置以下参数:
|
||
WEIXIN_APPID = '你的小程序appid'
|
||
WEIXIN_MCHID = '你的商户号'
|
||
WEIXIN_SHANGHUMIYAO = '你的商户密钥'
|
||
WEIXIN_CERT_PATH = '证书路径/cert/apiclient_cert.pem'
|
||
WEIXIN_KEY_PATH = '证书路径/cert/apiclient_key.pem'
|
||
|
||
返回:{'success': True/False, 'msg': '消息'}
|
||
"""
|
||
try:
|
||
# 检查必要的配置
|
||
required_settings = ['WEIXIN_APPID', 'WEIXIN_MCHID', 'WEIXIN_SHANGHUMIYAO', 'WEIXIN_CERT_PATH',
|
||
'WEIXIN_KEY_PATH']
|
||
for setting in required_settings:
|
||
if not hasattr(settings, setting) or not getattr(settings, setting):
|
||
return {'success': False, 'msg': f'缺少微信支付配置: {setting}'}
|
||
|
||
# 生成退款单号
|
||
refund_id = f"TK{int(time.time())}{random.randint(1000, 9999)}"
|
||
|
||
# 金额转换为分(必须是整数)
|
||
total_fee_in_fen = int(float(jine) * 100)
|
||
|
||
# 准备微信退款参数
|
||
refund_data = {
|
||
'appid': settings.WEIXIN_APPID,
|
||
'mch_id': settings.WEIXIN_MCHID,
|
||
'nonce_str': hashlib.md5(str(time.time()).encode()).hexdigest(),
|
||
'out_trade_no': dingdan_id, # 商户订单号(我们的订单ID)
|
||
'out_refund_no': refund_id, # 商户退款单号
|
||
'total_fee': total_fee_in_fen, # 订单总金额(分)
|
||
'refund_fee': total_fee_in_fen, # 退款金额(分),全额退款
|
||
'op_user_id': settings.WEIXIN_MCHID, # 操作员ID,默认为商户号
|
||
'refund_account': 'REFUND_SOURCE_UNSETTLED_FUNDS' # 退款资金来源
|
||
}
|
||
|
||
# 生成签名
|
||
sorted_params = sorted(refund_data.items())
|
||
sign_string = '&'.join([f"{key}={value}" for key, value in sorted_params if value])
|
||
sign_string += f"&key={settings.WEIXIN_SHANGHUMIYAO}"
|
||
sign = hashlib.md5(sign_string.encode()).hexdigest().upper()
|
||
refund_data['sign'] = sign
|
||
|
||
# 将字典转换为XML
|
||
xml_data = self.dict_to_xml(refund_data)
|
||
|
||
# 微信退款API地址
|
||
weixin_refund_url = "https://api.mch.weixin.qq.com/secapi/pay/refund"
|
||
|
||
# 设置超时时间
|
||
timeout = 30
|
||
|
||
# 调用微信退款API(需要证书)
|
||
headers = {'Content-Type': 'application/xml'}
|
||
response = requests.post(
|
||
weixin_refund_url,
|
||
data=xml_data.encode('utf-8'),
|
||
headers=headers,
|
||
cert=(settings.WEIXIN_CERT_PATH, settings.WEIXIN_KEY_PATH),
|
||
timeout=timeout
|
||
)
|
||
|
||
# 解析返回结果
|
||
if response.status_code == 200:
|
||
result = self.xml_to_dict(response.content)
|
||
|
||
if result.get('return_code') == 'SUCCESS' and result.get('result_code') == 'SUCCESS':
|
||
logger.info(f"微信退款成功,订单:{dingdan_id},退款单号:{refund_id}")
|
||
return {'success': True, 'msg': '退款申请提交成功', 'refund_id': refund_id}
|
||
else:
|
||
error_msg = result.get('err_code_des', result.get('return_msg', '退款申请失败'))
|
||
logger.error(f"微信退款失败,订单:{dingdan_id},错误:{error_msg}")
|
||
return {'success': False, 'msg': f'微信退款失败: {error_msg}'}
|
||
else:
|
||
logger.error(f"微信退款接口调用失败,状态码:{response.status_code}")
|
||
return {'success': False, 'msg': '微信退款接口调用失败'}
|
||
|
||
except requests.exceptions.Timeout:
|
||
logger.error(f"微信退款接口请求超时,订单:{dingdan_id}")
|
||
return {'success': False, 'msg': '微信退款接口请求超时'}
|
||
except Exception as e:
|
||
logger.error(f"调用微信退款接口异常: {str(e)}")
|
||
return {'success': False, 'msg': f'退款处理异常: {str(e)}'}
|
||
|
||
|
||
|
||
def dict_to_xml(self, dict_data):
|
||
"""将字典转换为XML字符串"""
|
||
xml = ["<xml>"]
|
||
for k, v in dict_data.items():
|
||
xml.append(f"<{k}>{v}</{k}>")
|
||
xml.append("</xml>")
|
||
return "".join(xml)
|
||
|
||
def xml_to_dict(self, xml_data):
|
||
"""将XML转换为字典"""
|
||
try:
|
||
root = ET.fromstring(xml_data)
|
||
result = {}
|
||
for child in root:
|
||
result[child.tag] = child.text
|
||
return result
|
||
except ET.ParseError as e:
|
||
logger.error(f"XML解析失败: {str(e)}")
|
||
return {'return_code': 'FAIL', 'return_msg': 'XML解析失败'}'''
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class DashouHuoquLeixingView(APIView):
|
||
"""
|
||
打手获取商品类型接口 - 抢单页面使用
|
||
接口地址:POST /dingdan/dsqdhqddlx
|
||
功能:返回所有正常的商品类型,已按排序权重排序
|
||
|
||
返回格式(与前端原数据格式完全一致):
|
||
{
|
||
"code": 200,
|
||
"msg": "成功",
|
||
"data": {
|
||
"list": [
|
||
{
|
||
"id": 1,
|
||
"jieshao": "王者荣耀",
|
||
"tupian_url": "leixing/wzry.jpg",
|
||
"yaoqiuleixing": null,
|
||
"huiyuan_id": null,
|
||
"yongjin": null,
|
||
"paixu": 100,
|
||
"shenhezhuangtai": 1
|
||
},
|
||
// ... 更多类型
|
||
]
|
||
}
|
||
}
|
||
"""
|
||
|
||
# 🔴 使用JWT Token认证
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
"""
|
||
处理POST请求,返回商品类型列表
|
||
|
||
请求参数:无特殊参数,只验证用户身份
|
||
|
||
安全验证:
|
||
1. JWT Token验证用户身份
|
||
2. 只返回 shenhezhuangtai=1(正常状态)的类型
|
||
3. 数据已排序:paixu降序 → id升序
|
||
"""
|
||
try:
|
||
logger.info(f"打手获取商品类型请求 - 用户ID: {request.user.yonghuid}")
|
||
|
||
# 🔴 导入商品类型模型
|
||
from shangpin.models import ShangpinLeixing
|
||
|
||
# 🔴 查询所有正常状态的商品类型
|
||
# 条件:shenhezhuangtai=1(正常状态)
|
||
# 排序:先按 paixu 降序(数值大的在前),再按 id 升序
|
||
leixing_queryset = ShangpinLeixing.objects.filter(
|
||
shenhezhuangtai__in=[3] # 查询状态为1或9的记录
|
||
).order_by('-paixu', 'id')
|
||
'''leixing_queryset = ShangpinLeixing.objects.filter(
|
||
shenhezhuangtai__in= [1,9] # 🔴 只取正常状态,排除审核专用
|
||
).order_by('-paixu', 'id')'''
|
||
|
||
# 🔴 构建返回数据列表
|
||
leixing_list = []
|
||
for leixing in leixing_queryset:
|
||
leixing_list.append({
|
||
"id": leixing.id,
|
||
"jieshao": leixing.jieshao or "",
|
||
"tupian_url": leixing.tupian_url or "",
|
||
"yaoqiuleixing": leixing.yaoqiuleixing,
|
||
"huiyuan_id": leixing.huiyuan_id,
|
||
"yongjin": float(leixing.yongjin) if leixing.yongjin else None,
|
||
"paixu": leixing.paixu or 0,
|
||
"shenhezhuangtai": leixing.shenhezhuangtai or 1
|
||
})
|
||
|
||
# 🔴 记录日志
|
||
logger.info(f"返回商品类型数据:{len(leixing_list)}条,用户: {request.user.yonghuid}")
|
||
|
||
# 🔴 返回成功响应
|
||
# 注意:保持与前端原数据格式完全一致
|
||
return Response({
|
||
"code": 200,
|
||
"msg": "成功",
|
||
"data": {
|
||
"list": leixing_list # 🔴 保持原字段名 "list"
|
||
}
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
# 🔴 异常处理
|
||
logger.error(f"打手获取商品类型失败 - 用户ID: {request.user.yonghuid}, 错误: {str(e)}", exc_info=True)
|
||
|
||
return Response({
|
||
"code": 500,
|
||
"msg": f"服务器内部错误: {str(e)}",
|
||
"data": None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class AdJuJueJieSuan(APIView):
|
||
"""
|
||
后台拒绝结算接口(平台发单)
|
||
权限: JWT Token认证 + 管理员身份验证
|
||
前端调用: POST /dingdan/adddjjjs
|
||
参数: {
|
||
"zhanghao": "管理员账号(手机号)",
|
||
"dingdan_id": "订单ID",
|
||
"tkly": "拒绝结算理由(必填)"
|
||
}
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 获取参数
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = request.data.get('dingdan_id', '').strip()
|
||
tkly = request.data.get('tkly', '').strip()
|
||
|
||
if not all([qianzhanghao, dingdan_id, tkly]):
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整(需要zhanghao, dingdan_id, tkly)', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2. JWT用户验证
|
||
dangqianyonghu = request.user
|
||
|
||
# 3. 验证手机号匹配(管理员账号通常为手机号)
|
||
if str(dangqianyonghu.phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 4. 验证用户类型为管理员
|
||
if dangqianyonghu.user_type != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 5. 查询订单
|
||
try:
|
||
dingdan = Dingdan.objects.get(dingdan_id=dingdan_id)
|
||
except Dingdan.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 6. 校验订单条件:平台发单(1)且状态为2或8
|
||
if dingdan.fadan_pingtai != 1 or dingdan.zhuangtai not in [2, 8]:
|
||
return Response(
|
||
{'code': 400, 'message': '仅平台发单且状态为进行中(2)或结算中(8)的订单可拒绝结算', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
jiedan_dashou_id = dingdan.jiedan_dashou_id
|
||
if not jiedan_dashou_id:
|
||
return Response(
|
||
{'code': 400, 'message': '订单暂无接单打手', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
with transaction.atomic():
|
||
# 7. 更新订单状态为5(已退款),并保存拒绝理由
|
||
dingdan.zhuangtai = 5
|
||
dingdan.tkly = tkly
|
||
dingdan.save()
|
||
|
||
# 8. 更新打手扩展表
|
||
try:
|
||
dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id)
|
||
dashou_profile = dashou_user.dashou_profile
|
||
dashou_profile.zhuangtai = 1 # 打手状态设为正常
|
||
dashou_profile.tuikuanliang += 1 # 退款订单总量+1
|
||
dashou_profile.save()
|
||
except (UserMain.DoesNotExist, AttributeError) as e:
|
||
logger.warning(f"拒绝结算:打手{jiedan_dashou_id}信息更新失败 - {str(e)}")
|
||
|
||
return Response({'code': 0, 'message': '拒绝结算成功', 'data': None})
|
||
|
||
except Exception as e:
|
||
logger.error(f"拒绝结算接口异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
class AdZhuanYiDaTing(APIView):
|
||
"""
|
||
后台转移大厅接口
|
||
权限: JWT Token认证 + 管理员身份验证
|
||
前端调用: POST /dingdan/adddzydt
|
||
参数: {
|
||
"zhanghao": "管理员账号(手机号)",
|
||
"dingdan_id": "订单ID"
|
||
}
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
try:
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = request.data.get('dingdan_id', '').strip()
|
||
|
||
if not all([qianzhanghao, dingdan_id]):
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整(需要zhanghao, dingdan_id)', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
dangqianyonghu = request.user
|
||
|
||
if str(dangqianyonghu.phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
if dangqianyonghu.user_type != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
try:
|
||
dingdan = Dingdan.objects.get(dingdan_id=dingdan_id)
|
||
except Dingdan.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 校验订单状态:允许2、8、4
|
||
if dingdan.zhuangtai not in [2, 8, 4]:
|
||
return Response(
|
||
{'code': 400, 'message': '仅状态为进行中(2)、结算中(8)或退款审核(4)的订单可转移大厅', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
jiedan_dashou_id = dingdan.jiedan_dashou_id
|
||
|
||
with transaction.atomic():
|
||
# 更新订单:状态改为1(下单中),清空接单打手ID
|
||
dingdan.zhuangtai = 1
|
||
dingdan.jiedan_dashou_id = None
|
||
dingdan.save()
|
||
|
||
# 如果有接单打手,更新其扩展表
|
||
if jiedan_dashou_id:
|
||
try:
|
||
dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id)
|
||
dashou_profile = dashou_user.dashou_profile
|
||
dashou_profile.zhuangtai = 1
|
||
dashou_profile.tuikuanliang += 1
|
||
dashou_profile.save()
|
||
except (UserMain.DoesNotExist, AttributeError) as e:
|
||
logger.warning(f"转移大厅:打手{jiedan_dashou_id}信息更新失败 - {str(e)}")
|
||
|
||
return Response({'code': 0, 'message': '转移大厅成功', 'data': None})
|
||
|
||
except Exception as e:
|
||
logger.error(f"转移大厅接口异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
class AdQuXiaoZhiDing(APIView):
|
||
"""
|
||
后台取消指定接口
|
||
权限: JWT Token认证 + 管理员身份验证
|
||
前端调用: POST /dingdan/adddqxzd
|
||
参数: {
|
||
"zhanghao": "管理员账号(手机号)",
|
||
"dingdan_id": "订单ID"
|
||
}
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
try:
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = request.data.get('dingdan_id', '').strip()
|
||
|
||
if not all([qianzhanghao, dingdan_id]):
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整(需要zhanghao, dingdan_id)', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
dangqianyonghu = request.user
|
||
|
||
if str(dangqianyonghu.phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
if dangqianyonghu.user_type != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
try:
|
||
dingdan = Dingdan.objects.get(dingdan_id=dingdan_id)
|
||
except Dingdan.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 校验订单状态:必须为7(指定中)
|
||
if dingdan.zhuangtai != 7:
|
||
return Response(
|
||
{'code': 400, 'message': '仅状态为指定中(7)的订单可取消指定', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
with transaction.atomic():
|
||
# 更新订单:状态改为1(下单中),清空指定ID
|
||
dingdan.zhuangtai = 1
|
||
dingdan.zhiding_id = None
|
||
dingdan.save()
|
||
|
||
return Response({'code': 0, 'message': '取消指定成功', 'data': None})
|
||
|
||
except Exception as e:
|
||
logger.error(f"取消指定接口异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
|
||
|
||
|
||
from peizhi.models import Club ,OrderTypeMapping
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
from peizhi.models import Club ,OrderTypeMapping
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
|
||
|
||
class PartnerSyncOrderView(APIView):
|
||
"""
|
||
接收对方平台派单通知接口
|
||
对方平台商家派单成功后,异步调用此接口,将订单信息同步过来。
|
||
"""
|
||
throttle_classes = [AnonRateThrottle]
|
||
permission_classes = [AllowAny]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
# 1. 验证 token
|
||
token = request.data.get('token')
|
||
if token != settings.CROSS_PLATFORM_TOKEN:
|
||
logger.warning(f"跨平台派单 token 无效: {token}")
|
||
return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 2. 获取必要参数
|
||
our_club_id = request.data.get('our_club_id') # 对方平台发送的我方俱乐部ID(即我方在对方表中的 partner_club_id)
|
||
partner_club_id = request.data.get('partner_club_id') # 对方平台的俱乐部ID(对方自己的ID)
|
||
partner_order_id = request.data.get('partner_order_id')
|
||
jine = request.data.get('jine')
|
||
dashou_fencheng = request.data.get('dashou_fencheng')
|
||
jieshao = request.data.get('jieshao')
|
||
beizhu = request.data.get('beizhu', '')
|
||
create_time_str = request.data.get('create_time')
|
||
nicheng = request.data.get('nicheng')
|
||
leixing_id = request.data.get('leixing_id') # 对方平台的商品类型ID
|
||
partner_yonghu_id = request.data.get('user_id') # 对方下单用户ID
|
||
|
||
# 必需字段校验
|
||
required_fields = [our_club_id, partner_club_id, partner_order_id, jine, dashou_fencheng,
|
||
jieshao, create_time_str, nicheng, leixing_id]
|
||
if not all(required_fields):
|
||
logger.error(f"跨平台派单参数缺失: {request.data}")
|
||
return Response({'code': 400, 'message': 'Missing required parameters'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 3. 验证我方俱乐部是否存在且同意接收订单
|
||
try:
|
||
my_club = Club.objects.get(partner_club_id=our_club_id)
|
||
except Club.DoesNotExist:
|
||
logger.warning(f"我方俱乐部不存在,partner_club_id={our_club_id}")
|
||
return Response({'code': 404, 'message': 'Club not found'}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
if not my_club.is_receive_enabled:
|
||
logger.warning(f"我方俱乐部 {my_club.club_id} 未同意接收订单")
|
||
return Response({'code': 403, 'message': 'Receiving not allowed'}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 4. 查找订单类型映射
|
||
try:
|
||
mapping = OrderTypeMapping.objects.get(
|
||
partner_club_id=our_club_id, # 对方在我方的标识
|
||
partner_type_id=leixing_id
|
||
)
|
||
our_type_id = mapping.our_type_id
|
||
except OrderTypeMapping.DoesNotExist:
|
||
logger.error(f"未找到订单类型映射: 对方俱乐部={our_club_id}, 对方类型ID={leixing_id}")
|
||
return Response({'code': 400, 'message': 'Order type mapping not found'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 5. 获取我方商品类型信息(用于获取会员ID、佣金等)
|
||
try:
|
||
our_leixing = ShangpinLeixing.objects.get(id=our_type_id)
|
||
except ShangpinLeixing.DoesNotExist:
|
||
logger.error(f"我方商品类型不存在: {our_type_id}")
|
||
return Response({'code': 400, 'message': 'Local product type not found'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 6. 幂等检查:如果已存在相同 partner_order_id 的订单,直接返回(附带 status='existed')
|
||
existing = Dingdan.objects.filter(partner_order_id=partner_order_id).first()
|
||
if existing:
|
||
logger.info(f"跨平台派单幂等返回: partner_order_id={partner_order_id} 已存在")
|
||
return Response({
|
||
'code': 0,
|
||
'data': {
|
||
'local_order_id': existing.dingdan_id,
|
||
'status': 'existed' # 明确告知已存在
|
||
}
|
||
})
|
||
|
||
# 7. 生成我方订单ID(确保唯一性)
|
||
def generate_dingdan_id():
|
||
timestamp = int(time.time() * 1000)
|
||
process_id = os.getpid() % 1000
|
||
random_num = random.randint(1000, 9999)
|
||
return f"SJ{timestamp}{process_id:03d}{random_num}"
|
||
|
||
# 8. 获取代理商家头像(用于订单图片)
|
||
agent_user = UserMain.objects.filter(yonghuid=settings.AGENT_SHANGJIA_ID).first()
|
||
agent_avatar = agent_user.avatar if agent_user else ''
|
||
|
||
# 9. 计算我方实际打手分成金额:对方传来的打手分成 × 我方接收费率
|
||
try:
|
||
actual_dashou_fencheng = Decimal(dashou_fencheng) * my_club.receive_rate
|
||
except Exception:
|
||
actual_dashou_fencheng = Decimal(dashou_fencheng)
|
||
|
||
# 10. 事务创建订单
|
||
try:
|
||
with transaction.atomic():
|
||
new_id = generate_dingdan_id()
|
||
# 创建主订单
|
||
dingdan = Dingdan.objects.create(
|
||
dingdan_id=new_id,
|
||
zhuangtai=1, # 普通订单(待接单)
|
||
fadan_pingtai=2, # 商家发单
|
||
jine=jine, # 原始订单金额
|
||
dashou_fencheng=actual_dashou_fencheng, # 调整后的打手分成
|
||
jieshao=jieshao,
|
||
beizhu=beizhu,
|
||
create_time=create_time_str,
|
||
yaoqiuleixing=our_leixing.yaoqiuleixing,
|
||
nicheng=nicheng,
|
||
huiyuan_id=our_leixing.huiyuan_id,
|
||
yongjin=our_leixing.yongjin,
|
||
leixing_id=our_type_id,
|
||
partner_order_id=partner_order_id, # 存储对方订单ID
|
||
is_cross=1,
|
||
dispatch_type=2, # 对方派单
|
||
partner_club_id=our_club_id, # 对方俱乐部ID
|
||
tupian=agent_avatar, # 使用代理商家头像作为订单图片
|
||
)
|
||
# 创建商家扩展表(使用代理商家)
|
||
DingdanShangjia.objects.create(
|
||
dingdan=dingdan,
|
||
shangjia_id=settings.AGENT_SHANGJIA_ID,
|
||
sjnicheng='跨平台代理',
|
||
partner_yonghu_id=partner_yonghu_id or '',
|
||
)
|
||
|
||
# 成功创建后更新每日派单统计(方向=对方派单)
|
||
try:
|
||
update_daily_dispatch_stat(
|
||
direction=2, # 对方派单
|
||
partner_club_id=our_club_id,
|
||
dispatch_amount=Decimal(jine), # 统计使用原始订单金额
|
||
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"更新派单统计失败: {e}", exc_info=True)
|
||
|
||
logger.info(f"跨平台订单同步成功: partner_order_id={partner_order_id}, local_id={new_id}")
|
||
return Response({
|
||
'code': 0,
|
||
'data': {
|
||
'local_order_id': new_id,
|
||
'status': 'created'
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"跨平台订单同步失败: {str(e)}", exc_info=True)
|
||
return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
from dingdan.utils import update_daily_dispatch_stat
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
class PartnerCheckClaimView(APIView):
|
||
"""
|
||
接收对方平台抢单询问(对方打手抢我方派出的订单)
|
||
如果允许抢单:
|
||
1. 订单状态改为2(已接单)
|
||
2. 记录对方打手ID、对方俱乐部ID、对方订单ID
|
||
3. 更新派单成交统计(success_count/success_amount)
|
||
"""
|
||
throttle_classes = [AnonRateThrottle]
|
||
permission_classes = [AllowAny]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
token = request.data.get('token')
|
||
if token != settings.CROSS_PLATFORM_TOKEN:
|
||
logger.warning(f"token无效: {token}")
|
||
return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
wofangdingdan_id = request.data.get('wofangdingdan_id')
|
||
partner_order_id = request.data.get('partner_order_id')
|
||
our_club_id = request.data.get('our_club_id')
|
||
dashou_id = request.data.get('dashou_id')
|
||
sj_id = request.data.get('sj_id')
|
||
|
||
if not all([wofangdingdan_id, partner_order_id, our_club_id, dashou_id, sj_id]):
|
||
logger.error(f"参数缺失: {request.data}")
|
||
return Response({'code': 400, 'message': 'Missing parameters'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
with transaction.atomic():
|
||
# 查询我方俱乐部配置
|
||
try:
|
||
club = Club.objects.select_for_update().get(partner_club_id=our_club_id)
|
||
except Club.DoesNotExist:
|
||
logger.warning(f"俱乐部配置不存在 partner_club_id={our_club_id}")
|
||
return Response({'code': 0, 'data': {'can_claim': False}})
|
||
|
||
if not club.is_interop_enabled:
|
||
logger.warning(f"俱乐部 {club.club_id} 未开启互通")
|
||
return Response({'code': 0, 'data': {'can_claim': False}})
|
||
|
||
# 锁定订单
|
||
try:
|
||
order = Dingdan.objects.select_for_update().get(dingdan_id=wofangdingdan_id)
|
||
except Dingdan.DoesNotExist:
|
||
logger.warning(f"订单不存在: {wofangdingdan_id}")
|
||
return Response({'code': 404, 'message': 'Order not found'}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 只有状态1可抢
|
||
if order.zhuangtai != 1:
|
||
logger.info(f"订单 {order.dingdan_id} 状态 {order.zhuangtai} 不可抢")
|
||
return Response({'code': 0, 'data': {'can_claim': False}})
|
||
|
||
# 商品类型检查
|
||
try:
|
||
leixing = ShangpinLeixing.objects.get(id=order.leixing_id)
|
||
except ShangpinLeixing.DoesNotExist:
|
||
logger.warning(f"商品类型不存在: {order.leixing_id}")
|
||
return Response({'code': 0, 'data': {'can_claim': False}})
|
||
|
||
if not leixing.is_cross_enabled:
|
||
logger.warning(f"商品类型 {leixing.jieshao} 未开启跨平台")
|
||
return Response({'code': 0, 'data': {'can_claim': False}})
|
||
|
||
# 执行抢单:状态改为2(已接单)
|
||
order.zhuangtai = 2
|
||
order.jiedan_dashou_id = dashou_id
|
||
order.partner_club_id = our_club_id
|
||
order.partner_order_id = partner_order_id
|
||
if order.is_cross != 1:
|
||
order.is_cross = 1
|
||
# dispatch_type 保持原值(1-我方派单),不变
|
||
order.save()
|
||
|
||
# 更新派单成交统计(我方派单被成功接单)
|
||
update_daily_dispatch_stat(
|
||
direction=1,
|
||
partner_club_id=our_club_id,
|
||
claimed_amount=order.jine,
|
||
|
||
)
|
||
|
||
logger.info(f"对方抢单成功: 订单 {order.dingdan_id}, 对方打手 {dashou_id}")
|
||
return Response({'code': 0, 'data': {'can_claim': True}})
|
||
|
||
except Exception as e:
|
||
logger.error(f"异常: {e}", exc_info=True)
|
||
return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
class PartnerNotifyClaimView(APIView):
|
||
"""
|
||
接收对方平台抢单成功通知(对方派单被他们自己的打手抢了)
|
||
我方根据 partner_order_id 找到订单,如果状态为1则改为20,不更新任何统计。
|
||
"""
|
||
throttle_classes = [AnonRateThrottle]
|
||
permission_classes = [AllowAny]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
token = request.data.get('token')
|
||
if token != settings.CROSS_PLATFORM_TOKEN:
|
||
logger.warning(f"token无效: {token}")
|
||
return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
partner_order_id = request.data.get('partner_order_id')
|
||
if not partner_order_id:
|
||
logger.error("缺少 partner_order_id")
|
||
return Response({'code': 400, 'message': 'Missing partner_order_id'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
with transaction.atomic():
|
||
order = Dingdan.objects.select_for_update().get(partner_order_id=partner_order_id)
|
||
# 只有状态为1(未被抢)时才改为20
|
||
if order.zhuangtai == 1:
|
||
order.zhuangtai = 20
|
||
order.save()
|
||
logger.info(f"订单 {order.dingdan_id} 状态改为20(被其他俱乐部抢走)")
|
||
else:
|
||
logger.info(f"订单 {order.dingdan_id} 当前状态 {order.zhuangtai},无需修改")
|
||
return Response({'code': 0, 'message': 'OK'})
|
||
except Dingdan.DoesNotExist:
|
||
logger.warning(f"订单不存在 partner_order_id={partner_order_id}")
|
||
return Response({'code': 404, 'message': 'Order not found'}, status=status.HTTP_404_NOT_FOUND)
|
||
except Exception as e:
|
||
logger.error(f"异常: {e}", exc_info=True)
|
||
return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
'''class PartnerSyncOrderView(APIView):
|
||
"""
|
||
接收对方平台派单通知接口
|
||
对方平台商家派单成功后,异步调用此接口,将订单信息同步过来。
|
||
请求方式: POST
|
||
接口地址: /dingdan/partner_sync_order
|
||
"""
|
||
throttle_classes = [AnonRateThrottle]
|
||
permission_classes = [AllowAny]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
# 1. 验证 token
|
||
token = request.data.get('token')
|
||
if token != settings.CROSS_PLATFORM_TOKEN:
|
||
logger.warning(f"跨平台派单 token 无效: {token}")
|
||
return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 2. 获取必要参数
|
||
our_club_id = request.data.get('our_club_id') # 对方平台发送的我方俱乐部ID(即对方在 Club 表中 partner_club_id)
|
||
partner_club_id = request.data.get('partner_club_id') # 对方平台的俱乐部ID(对方自己的ID)
|
||
partner_order_id = request.data.get('partner_order_id')
|
||
jine = request.data.get('jine')
|
||
dashou_fencheng = request.data.get('dashou_fencheng')
|
||
jieshao = request.data.get('jieshao')
|
||
beizhu = request.data.get('beizhu', '')
|
||
create_time_str = request.data.get('create_time')
|
||
nicheng = request.data.get('nicheng')
|
||
leixing_id = request.data.get('leixing_id') # 对方平台的商品类型ID
|
||
|
||
# 必需字段校验
|
||
required_fields = [our_club_id, partner_club_id, partner_order_id, jine, dashou_fencheng,
|
||
jieshao, create_time_str, nicheng, leixing_id]
|
||
if not all(required_fields):
|
||
logger.error(f"跨平台派单参数缺失: {request.data}")
|
||
return Response({'code': 400, 'message': 'Missing required parameters'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 3. 验证我方俱乐部是否存在且同意接收订单
|
||
# 注意:our_club_id 是对方发送过来的,代表我方在对方系统中的ID,所以用 partner_club_id 匹配
|
||
try:
|
||
my_club = Club.objects.get(partner_club_id=our_club_id)
|
||
except Club.DoesNotExist:
|
||
logger.warning(f"我方俱乐部不存在,partner_club_id={our_club_id}")
|
||
return Response({'code': 404, 'message': 'Club not found'}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
if not my_club.is_receive_enabled:
|
||
logger.warning(f"我方俱乐部 {my_club.club_id} 未同意接收订单")
|
||
return Response({'code': 403, 'message': 'Receiving not allowed'}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 4. 查找订单类型映射
|
||
try:
|
||
mapping = OrderTypeMapping.objects.get(
|
||
partner_club_id=our_club_id,
|
||
partner_type_id=leixing_id
|
||
)
|
||
our_type_id = mapping.our_type_id
|
||
except OrderTypeMapping.DoesNotExist:
|
||
logger.error(f"未找到订单类型映射: 对方俱乐部={our_club_id}, 对方类型ID={leixing_id}")
|
||
return Response({'code': 400, 'message': 'Order type mapping not found'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 5. 获取我方商品类型信息(用于获取会员ID、佣金等)
|
||
try:
|
||
our_leixing = ShangpinLeixing.objects.get(id=our_type_id)
|
||
except ShangpinLeixing.DoesNotExist:
|
||
logger.error(f"我方商品类型不存在: {our_type_id}")
|
||
return Response({'code': 400, 'message': 'Local product type not found'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 6. 幂等检查:如果已存在相同 partner_order_id 的订单,直接返回
|
||
existing = Dingdan.objects.filter(partner_order_id=partner_order_id).first()
|
||
if existing:
|
||
logger.info(f"跨平台派单幂等返回: partner_order_id={partner_order_id} 已存在")
|
||
return Response({
|
||
'code': 0,
|
||
'data': {'local_order_id': existing.dingdan_id}
|
||
})
|
||
|
||
# 7. 生成我方订单ID
|
||
def generate_dingdan_id():
|
||
import time, random, os
|
||
timestamp = int(time.time() * 1000)
|
||
process_id = os.getpid() % 1000
|
||
random_num = random.randint(1000, 9999)
|
||
return f"SJ{timestamp}{process_id:03d}{random_num}"
|
||
|
||
# 8. 获取代理商家头像(用于订单图片)
|
||
agent_user = UserMain.objects.filter(yonghuid=settings.AGENT_SHANGJIA_ID).first()
|
||
agent_avatar = agent_user.avatar if agent_user else ''
|
||
|
||
# 9. 事务创建订单
|
||
try:
|
||
with transaction.atomic():
|
||
new_id = generate_dingdan_id()
|
||
# 创建主订单
|
||
dingdan = Dingdan.objects.create(
|
||
dingdan_id=new_id,
|
||
zhuangtai=1, # 普通订单(默认)
|
||
fadan_pingtai=2, # 商家发单
|
||
jine=jine,
|
||
dashou_fencheng=dashou_fencheng,
|
||
jieshao=jieshao,
|
||
beizhu=beizhu,
|
||
create_time=create_time_str,
|
||
yaoqiuleixing=our_leixing.yaoqiuleixing,
|
||
nicheng=nicheng,
|
||
huiyuan_id=our_leixing.huiyuan_id,
|
||
yongjin=our_leixing.yongjin,
|
||
leixing_id=our_type_id,
|
||
partner_order_id=partner_order_id,
|
||
is_cross=1,
|
||
dispatch_type=2, # 对方派给我方
|
||
partner_club_id=our_club_id, # 对方的俱乐部ID
|
||
tupian=agent_avatar, # 使用代理商家头像作为订单图片
|
||
)
|
||
# 创建商家扩展表(使用代理商家)
|
||
DingdanShangjia.objects.create(
|
||
dingdan=dingdan,
|
||
shangjia_id=settings.AGENT_SHANGJIA_ID,
|
||
sjnicheng='跨平台代理'
|
||
)
|
||
logger.info(f"跨平台订单同步成功: partner_order_id={partner_order_id}, local_id={new_id}")
|
||
return Response({
|
||
'code': 0,
|
||
'data': {'local_order_id': new_id}
|
||
},status = 200)
|
||
except Exception as e:
|
||
logger.error(f"跨平台订单同步失败: {str(e)}", exc_info=True)
|
||
return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
from dingdan.utils import update_daily_dispatch_stat
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
|
||
|
||
class PartnerCheckClaimView(APIView):
|
||
"""
|
||
接收对方平台抢单询问(对方打手抢我方派出的订单)
|
||
如果允许抢单:
|
||
1. 订单状态改为2(已接单)
|
||
2. 记录对方打手ID、对方俱乐部ID、对方订单ID
|
||
3. 更新派单成交统计(success_count/success_amount)
|
||
"""
|
||
throttle_classes = [AnonRateThrottle]
|
||
permission_classes = [AllowAny]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
token = request.data.get('token')
|
||
if token != settings.CROSS_PLATFORM_TOKEN:
|
||
logger.warning(f"token无效: {token}")
|
||
return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
wofangdingdan_id = request.data.get('wofangdingdan_id')
|
||
partner_order_id = request.data.get('partner_order_id')
|
||
our_club_id = request.data.get('our_club_id')
|
||
dashou_id = request.data.get('dashou_id')
|
||
sj_id = request.data.get('sj_id')
|
||
|
||
if not all([wofangdingdan_id, partner_order_id, our_club_id, dashou_id, sj_id]):
|
||
logger.error(f"参数缺失: {request.data}")
|
||
return Response({'code': 400, 'message': 'Missing parameters'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
with transaction.atomic():
|
||
# 查询我方俱乐部配置
|
||
try:
|
||
club = Club.objects.select_for_update().get(partner_club_id=our_club_id)
|
||
except Club.DoesNotExist:
|
||
logger.warning(f"俱乐部配置不存在 partner_club_id={our_club_id}")
|
||
return Response({'code': 0, 'data': {'can_claim': False}})
|
||
|
||
if not club.is_interop_enabled:
|
||
logger.warning(f"俱乐部 {club.club_id} 未开启互通")
|
||
return Response({'code': 0, 'data': {'can_claim': False}})
|
||
|
||
# 锁定订单
|
||
try:
|
||
order = Dingdan.objects.select_for_update().get(dingdan_id=wofangdingdan_id)
|
||
except Dingdan.DoesNotExist:
|
||
logger.warning(f"订单不存在: {wofangdingdan_id}")
|
||
return Response({'code': 404, 'message': 'Order not found'}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 只有状态1可抢
|
||
if order.zhuangtai != 1:
|
||
logger.info(f"订单 {order.dingdan_id} 状态 {order.zhuangtai} 不可抢")
|
||
return Response({'code': 0, 'data': {'can_claim': False}})
|
||
|
||
# 商品类型检查
|
||
try:
|
||
leixing = ShangpinLeixing.objects.get(id=order.leixing_id)
|
||
except ShangpinLeixing.DoesNotExist:
|
||
logger.warning(f"商品类型不存在: {order.leixing_id}")
|
||
return Response({'code': 0, 'data': {'can_claim': False}})
|
||
|
||
if not leixing.is_cross_enabled:
|
||
logger.warning(f"商品类型 {leixing.jieshao} 未开启跨平台")
|
||
return Response({'code': 0, 'data': {'can_claim': False}})
|
||
|
||
# 执行抢单:状态改为2(已接单)
|
||
order.zhuangtai = 2
|
||
order.jiedan_dashou_id = dashou_id
|
||
order.partner_club_id = our_club_id
|
||
order.partner_order_id = partner_order_id
|
||
if order.is_cross != 1:
|
||
order.is_cross = 1
|
||
# dispatch_type 保持原值(1-我方派单),不变
|
||
order.save()
|
||
|
||
# 更新派单成交统计(我方派单被成功接单)
|
||
update_daily_dispatch_stat(
|
||
direction=1,
|
||
partner_club_id=our_club_id,
|
||
claimed_amount=order.jine,
|
||
|
||
)
|
||
|
||
logger.info(f"对方抢单成功: 订单 {order.dingdan_id}, 对方打手 {dashou_id}")
|
||
return Response({'code': 0, 'data': {'can_claim': True}})
|
||
|
||
except Exception as e:
|
||
logger.error(f"异常: {e}", exc_info=True)
|
||
return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
class PartnerNotifyClaimView(APIView):
|
||
"""
|
||
接收对方平台抢单成功通知接口
|
||
对方平台打手抢单成功后,调用此接口,我方处理本地订单。
|
||
请求方式: POST
|
||
接口地址: /dingdan/partner_notify_claim
|
||
请求参数:
|
||
token: 认证令牌,需与settings.CROSS_PLATFORM_TOKEN一致
|
||
order_id: 对方平台的订单ID(即我方记录的 partner_order_id)
|
||
返回:
|
||
code: 0成功,其他失败
|
||
message: 描述信息
|
||
"""
|
||
throttle_classes = [AnonRateThrottle]
|
||
permission_classes = [AllowAny]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
# 1. 验证token
|
||
token = request.data.get('token')
|
||
if token != settings.CROSS_PLATFORM_TOKEN:
|
||
logger.warning(f"跨平台抢单通知 token 无效: {token}")
|
||
return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
order_id = request.data.get('local_order_id') # 对方平台的订单ID(即我方的 partner_order_id)
|
||
if not order_id:
|
||
logger.error("跨平台抢单通知参数缺失: order_id")
|
||
return Response({'code': 400, 'message': 'Missing order_id'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
with transaction.atomic():
|
||
# 2. 根据 partner_order_id 查找订单
|
||
order = Dingdan.objects.select_for_update().get(partner_order_id=order_id)
|
||
|
||
# 3. 根据订单状态处理
|
||
if order.zhuangtai == 1:
|
||
# 订单未被抢,直接删除(包括关联的商家扩展表)
|
||
# 先删除扩展表
|
||
DingdanShangjia.objects.filter(dingdan=order).delete()
|
||
# 再删除主订单
|
||
order.delete()
|
||
logger.info(f"跨平台抢单通知:订单状态为1,已删除,partner_order_id={order_id}")
|
||
return Response({'code': 0, 'message': 'Order deleted'})
|
||
else:
|
||
# 订单状态不是1(已被抢或异常),改为状态15(异常订单)
|
||
order.zhuangtai = 15
|
||
order.save()
|
||
logger.info(f"跨平台抢单通知:订单状态异常,已改为15,partner_order_id={order_id}")
|
||
return Response({'code': 0, 'message': 'Order marked as abnormal'})
|
||
|
||
except Dingdan.DoesNotExist:
|
||
logger.warning(f"跨平台抢单通知:订单不存在 partner_order_id={order_id}")
|
||
return Response({'code': 404, 'message': 'Order not found'}, status=status.HTTP_404_NOT_FOUND)
|
||
except Exception as e:
|
||
logger.error(f"跨平台抢单通知异常: {str(e)}", exc_info=True)
|
||
return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)'''
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
class PartnerRefundNotifyView(APIView):
|
||
"""
|
||
接收对方平台退款通知接口
|
||
对方平台商家申请退款时,调用此接口通知我方,我方将本地对应订单改为退款状态。
|
||
请求方式: POST
|
||
接口地址: /dingdan/partner_refund_notify
|
||
请求参数:
|
||
token: 认证令牌,需与settings.CROSS_PLATFORM_TOKEN一致
|
||
order_id: 对方平台的订单ID(即我方记录的 partner_order_id)
|
||
返回:
|
||
code: 0成功,其他失败
|
||
message: 描述信息
|
||
"""
|
||
throttle_classes = [AnonRateThrottle]
|
||
permission_classes = [AllowAny]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
# 1. 验证token
|
||
token = request.data.get('token')
|
||
if token != settings.CROSS_PLATFORM_TOKEN:
|
||
logger.warning(f"跨平台退款通知 token 无效: {token}")
|
||
return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
order_id = request.data.get('order_id') # 对方平台的订单ID(即我方的 partner_order_id)
|
||
if not order_id:
|
||
logger.error("跨平台退款通知参数缺失: order_id")
|
||
return Response({'code': 400, 'message': 'Missing order_id'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
with transaction.atomic():
|
||
# 2. 根据 partner_order_id 查找订单(必须存在且为跨平台订单)
|
||
order = Dingdan.objects.select_for_update().get(partner_order_id=order_id, is_cross=1)
|
||
# 如果订单已完成,不允许退款
|
||
if order.zhuangtai == 3:
|
||
logger.warning(f"跨平台退款通知:订单 {order_id} 已完成,不可退款")
|
||
return Response({'code': 400, 'message': 'Order already completed, cannot refund'},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 3. 如果订单状态不是5,则改为5(已退款),并更新打手信息
|
||
if order.zhuangtai != 5:
|
||
# 记录原打手ID
|
||
old_dashou_id = order.jiedan_dashou_id
|
||
if old_dashou_id:
|
||
try:
|
||
# 查询打手用户主表
|
||
dashou_user = UserMain.objects.get(yonghuid=old_dashou_id)
|
||
# 通过反向关系获取打手扩展表
|
||
if hasattr(dashou_user, 'dashou_profile'):
|
||
dashou_profile = dashou_user.dashou_profile
|
||
dashou_profile.zhuangtai = 1 # 恢复打手状态
|
||
dashou_profile.tuikuanliang += 1 # 退款订单量+1
|
||
dashou_profile.save()
|
||
logger.info(f"退款通知:打手 {old_dashou_id} 状态已恢复,退款订单+1")
|
||
except UserMain.DoesNotExist:
|
||
logger.warning(f"退款通知:打手用户不存在 {old_dashou_id}")
|
||
except Exception as e:
|
||
logger.error(f"退款通知:更新打手信息失败 {str(e)}")
|
||
|
||
# 更新订单状态为5
|
||
order.zhuangtai = 5
|
||
# 可选:清空接单打手ID
|
||
#order.jiedan_dashou_id = None
|
||
order.save()
|
||
logger.info(f"跨平台退款通知成功:订单 {order_id} 已改为退款状态")
|
||
else:
|
||
logger.info(f"跨平台退款通知:订单 {order_id} 已是退款状态,无需重复处理")
|
||
|
||
return Response({'code': 0, 'message': 'Refund processed successfully'})
|
||
|
||
except Dingdan.DoesNotExist:
|
||
logger.warning(f"跨平台退款通知:订单不存在或不是跨平台订单,partner_order_id={order_id}")
|
||
return Response({'code': 404, 'message': 'Order not found'}, status=status.HTTP_404_NOT_FOUND)
|
||
except Exception as e:
|
||
logger.error(f"跨平台退款通知异常: {str(e)}", exc_info=True)
|
||
return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
class DashouXiugaiView(APIView):
|
||
"""
|
||
打手修改订单交付内容接口
|
||
请求方式:POST
|
||
请求路径:/dingdan/dsxiugaidd
|
||
权限:需携带有效JWT Token,且用户为打手身份
|
||
请求参数:
|
||
dingdan_id : 订单ID(必填)
|
||
liuyan : 新的留言内容(可选,最长50字)
|
||
deleted_images : 要删除的图片URL列表(数组,每个元素为完整COS URL)
|
||
new_images : 新增的图片URL列表(数组,每个元素为完整COS URL)
|
||
返回:
|
||
成功:{"code":0,"msg":"修改成功","data":null}
|
||
失败:返回相应错误码和提示
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
# 获取请求数据
|
||
data = request.data
|
||
dingdan_id = data.get('dingdan_id', '')
|
||
liuyan = data.get('liuyan', '')
|
||
deleted_images = data.get('deleted_images', [])
|
||
new_images = data.get('new_images', [])
|
||
|
||
# ---------- 参数校验 ----------
|
||
if not dingdan_id:
|
||
return Response({'code': 1, 'msg': '订单ID不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if not isinstance(deleted_images, list):
|
||
return Response({'code': 2, 'msg': 'deleted_images 必须为数组', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if not isinstance(new_images, list):
|
||
return Response({'code': 3, 'msg': 'new_images 必须为数组', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if liuyan and len(liuyan) > 50:
|
||
return Response({'code': 4, 'msg': '留言最多50个字', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# ---------- 用户身份验证 ----------
|
||
current_user = request.user
|
||
yonghuid = current_user.yonghuid
|
||
|
||
# 检查是否为打手
|
||
try:
|
||
dashou_profile = current_user.dashou_profile # 假设User模型有related_name='dashou_profile'
|
||
except UserDashou.DoesNotExist:
|
||
return Response({'code': 5, 'msg': '用户不是打手身份', 'data': None},
|
||
status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# ---------- 订单查询及权限验证 ----------
|
||
try:
|
||
# 订单必须属于该打手,且状态为4(退款中)或8(结算中)
|
||
dingdan = Dingdan.objects.get(
|
||
dingdan_id=dingdan_id,
|
||
jiedan_dashou_id=yonghuid,
|
||
zhuangtai__in=[4, 8] # 只允许修改退款中或结算中的订单
|
||
)
|
||
except Dingdan.DoesNotExist:
|
||
return Response({'code': 6, 'msg': '订单不存在或没有修改权限', 'data': None},
|
||
status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# ---------- 执行修改(使用事务保证原子性) ----------
|
||
try:
|
||
with transaction.atomic():
|
||
# 1. 处理删除的图片:从 Dashoutupian 表中删除记录
|
||
if deleted_images:
|
||
# 根据订单ID、打手ID和图片URL删除记录
|
||
delete_count, _ = Dashoutupian.objects.filter(
|
||
dingdan_id=dingdan_id,
|
||
dashou=yonghuid,
|
||
tupian__in=deleted_images
|
||
).delete()
|
||
# 可选:记录删除数量
|
||
logger.info(f"订单 {dingdan_id} 删除图片 {delete_count} 张")
|
||
|
||
# 2. 处理新增的图片:插入新记录
|
||
if new_images:
|
||
# 批量创建
|
||
new_records = []
|
||
for url in new_images:
|
||
new_records.append(Dashoutupian(
|
||
dingdan_id=dingdan_id,
|
||
dashou=yonghuid,
|
||
tupian=url,
|
||
create_time=timezone.now()
|
||
))
|
||
Dashoutupian.objects.bulk_create(new_records)
|
||
logger.info(f"订单 {dingdan_id} 新增图片 {len(new_records)} 张")
|
||
|
||
# 3. 更新订单留言(如果发生变化)
|
||
# 注意:即使留言为空字符串,也允许更新,表示清空留言
|
||
if 'liuyan' in data: # 判断是否传了liuyan字段(可能为空字符串)
|
||
dingdan.dashou_liuyan = liuyan
|
||
dingdan.update_time = timezone.now()
|
||
dingdan.save(update_fields=['dashou_liuyan', 'update_time'])
|
||
|
||
# 全部成功,返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '修改成功',
|
||
'data': None
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
logger.error(f"修改订单失败: {str(e)}", exc_info=True)
|
||
return Response({
|
||
'code': 7,
|
||
'msg': '服务器内部错误',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
from django.core.exceptions import ObjectDoesNotExist
|
||
from dingdan.tongzhi_tasks import dingdan_guangbo
|
||
|
||
class ZxsjghdsView(APIView):
|
||
"""
|
||
商家更换打手接口
|
||
路径:POST /dingdan/zxsjghds
|
||
权限:仅限已认证商家(通过JWT)
|
||
功能:
|
||
1. 将原订单状态改为已退款(5),释放接单打手(若有)
|
||
2. 更新打手统计(退款量、状态、每日统计)
|
||
3. 写入退款记录
|
||
4. 若为跨平台订单,通知对方平台退款(必须成功)
|
||
5. 基于原订单生成新订单(状态直接为1),复制核心数据
|
||
6. 不操作商家余额、退款次数等(替换订单)
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
# ========== 1. 获取当前商家用户 ==========
|
||
current_user = request.user
|
||
try:
|
||
yonghuid = current_user.yonghuid
|
||
except AttributeError:
|
||
return Response({'code': 1, 'msg': '用户认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
||
|
||
# ========== 2. 提取订单ID ==========
|
||
dingdan_id = request.data.get('dingdan_id', '').strip()
|
||
if not dingdan_id:
|
||
return Response({'code': 2, 'msg': '订单ID不能为空'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# ========== 3. 查询订单并校验权限 ==========
|
||
try:
|
||
# 关联查询商家扩展表,确保当前商家是该订单的发布者
|
||
shangjia_kuozhan = DingdanShangjia.objects.select_related('dingdan').get(
|
||
dingdan__dingdan_id=dingdan_id,
|
||
shangjia_id=yonghuid
|
||
)
|
||
except DingdanShangjia.DoesNotExist:
|
||
return Response({'code': 3, 'msg': '订单不存在或您无权操作'}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
old_order = shangjia_kuozhan.dingdan
|
||
|
||
# ========== 4. 状态校验(仅允许进行中或结算中) ==========
|
||
if old_order.zhuangtai not in [2, 8]:
|
||
return Response({'code': 4, 'msg': '当前订单状态不允许更换打手'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# ========== 5. 收集订单关键数据(用于新订单复制) ==========
|
||
# 这些字段将在退款处理后用于创建新订单
|
||
jine = old_order.jine
|
||
dashou_fencheng = old_order.dashou_fencheng
|
||
leixing_id = old_order.leixing_id
|
||
yaoqiuleixing = old_order.yaoqiuleixing
|
||
huiyuan_id = old_order.huiyuan_id
|
||
yongjin = old_order.yongjin
|
||
jieshao = old_order.jieshao
|
||
nicheng = old_order.nicheng
|
||
beizhu = old_order.beizhu
|
||
user1_id = old_order.user1_id
|
||
tupian = old_order.tupian
|
||
fadan_pingtai = old_order.fadan_pingtai
|
||
|
||
# 跨平台相关
|
||
is_cross = old_order.is_cross
|
||
partner_club_id = old_order.partner_club_id
|
||
partner_order_id = old_order.partner_order_id
|
||
need_notify_partner = is_cross and partner_club_id and partner_order_id
|
||
|
||
dashou_id = old_order.jiedan_dashou_id # 可能为空
|
||
|
||
# ========== 6. 退款处理(事务块1:退款原订单 + 打手统计 + 跨平台通知) ==========
|
||
try:
|
||
with transaction.atomic():
|
||
# 6.1 订单状态更新为已退款
|
||
old_order.zhuangtai = 5
|
||
old_order.save(update_fields=['zhuangtai'])
|
||
|
||
# 6.2 释放打手
|
||
if dashou_id:
|
||
try:
|
||
dashou_user = UserMain.objects.get(yonghuid=dashou_id)
|
||
dashou_profile = dashou_user.dashou_profile
|
||
dashou_profile.tuikuanliang = (dashou_profile.tuikuanliang or 0) + 1
|
||
dashou_profile.zhuangtai = 1 # 空闲
|
||
dashou_profile.save(update_fields=['tuikuanliang', 'zhuangtai'])
|
||
except (UserMain.DoesNotExist, ObjectDoesNotExist):
|
||
logger.warning(f"打手 {dashou_id} 不存在或扩展表缺失,跳过更新")
|
||
|
||
# 6.3 更新退款记录
|
||
Tuikuanjilu.objects.update_or_create(
|
||
dingdan_id=dingdan_id,
|
||
defaults={
|
||
'dashouid': dashou_id or '',
|
||
'qingqiuid': yonghuid, # 申请人为当前商家
|
||
'chuliid': yonghuid, # 处理人也是商家自己
|
||
'liyou': '商家更换打手',
|
||
'sqzhuangtai': 1, # 状态 1=成功
|
||
'jine': jine or 0,
|
||
'dashou_fencheng': dashou_fencheng or 0,
|
||
'jieshao': jieshao or '',
|
||
'beizhu': '更换打手系统自动退款',
|
||
'nicheng': nicheng or '',
|
||
}
|
||
)
|
||
|
||
# 6.4 打手每日统计(退款行为)
|
||
if dashou_id:
|
||
try:
|
||
from houtai.utils import update_dashou_daily_by_action
|
||
update_dashou_daily_by_action(
|
||
yonghuid=dashou_id,
|
||
amount=dashou_fencheng,
|
||
action=3 # 3=退款
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"更新打手每日统计失败: {e}")
|
||
# 统计失败不应阻断主流程,但可记录
|
||
|
||
# 6.5 跨平台退款通知
|
||
if need_notify_partner:
|
||
try:
|
||
from peizhi.models import ClubConfig, Club
|
||
my_club_config = ClubConfig.objects.filter(is_self=1).first()
|
||
if not my_club_config:
|
||
raise Exception("我方俱乐部配置不存在")
|
||
self_club_id = my_club_config.club_id
|
||
club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id)
|
||
partner_domain = club_rel.partner_domain.rstrip('/')
|
||
|
||
notify_url = f"{partner_domain}/houtai/dfddtk"
|
||
payload = {
|
||
'order_id': partner_order_id,
|
||
'club_id': self_club_id,
|
||
'status': 5,
|
||
'reason': '商家更换打手退款'
|
||
}
|
||
resp = requests.post(notify_url, json=payload, timeout=5)
|
||
if resp.status_code != 200:
|
||
transaction.set_rollback(True)
|
||
logger.error(f"对方平台接口HTTP错误: {resp.status_code}")
|
||
return Response({'code': 500, 'msg': '通知对方平台失败'})
|
||
resp_json = resp.json()
|
||
if resp_json.get('code') != 0:
|
||
transaction.set_rollback(True)
|
||
logger.error(f"对方平台退款通知失败: {resp_json.get('msg')}")
|
||
return Response({'code': 500, 'msg': f"对方平台通知失败: {resp_json.get('msg')}"})
|
||
|
||
# 更新跨平台扩展数据
|
||
from dingdan.models import CrossPlatformOrderData
|
||
CrossPlatformOrderData.objects.update_or_create(
|
||
dingdan_id=dingdan_id,
|
||
defaults={
|
||
'partner_order_id': partner_order_id,
|
||
'partner_club_id': partner_club_id,
|
||
'partner_order_status': 5,
|
||
}
|
||
)
|
||
except Exception as e:
|
||
transaction.set_rollback(True)
|
||
logger.error(f"通知对方平台异常: {e}")
|
||
return Response({'code': 500, 'msg': f'通知对方平台失败: {str(e)}'})
|
||
|
||
except Exception as e:
|
||
logger.error(f"退款处理事务失败: {e}", exc_info=True)
|
||
return Response({'code': 500, 'msg': '退款处理失败'})
|
||
|
||
# ========== 7. 生成新订单(事务块2) ==========
|
||
try:
|
||
with transaction.atomic():
|
||
# 7.1 生成新的订单ID
|
||
def generate_dingdan_id():
|
||
timestamp = int(time.time() * 1000)
|
||
process_id = os.getpid() % 1000
|
||
random_num = random.randint(1000, 9999)
|
||
return f"SJ{timestamp}{process_id:03d}{random_num}"
|
||
|
||
new_dingdan_id = generate_dingdan_id()
|
||
# 确保唯一
|
||
while Dingdan.objects.filter(dingdan_id=new_dingdan_id).exists():
|
||
new_dingdan_id = generate_dingdan_id()
|
||
|
||
# 7.2 创建新订单(复制数据,状态为 1)
|
||
new_order = Dingdan.objects.create(
|
||
dingdan_id=new_dingdan_id,
|
||
zhuangtai=1, # 已下单
|
||
fadan_pingtai=fadan_pingtai,
|
||
jine=jine,
|
||
dashou_fencheng=dashou_fencheng,
|
||
leixing_id=leixing_id,
|
||
yaoqiuleixing=yaoqiuleixing,
|
||
huiyuan_id=huiyuan_id,
|
||
yongjin=yongjin,
|
||
jieshao=jieshao,
|
||
nicheng=nicheng,
|
||
beizhu=beizhu,
|
||
user1_id=user1_id, # 商家标识
|
||
tupian=tupian,
|
||
# 不继承:zhiding_id, jiedan_dashou_id, dashou_liuyan 等
|
||
)
|
||
|
||
# 7.3 创建商家扩展表(关联新订单)
|
||
DingdanShangjia.objects.create(
|
||
dingdan=new_order,
|
||
shangjia_id=yonghuid,
|
||
sjnicheng=shangjia_kuozhan.sjnicheng or '',
|
||
)
|
||
|
||
# 7.4 异步通知打手(广播新订单)
|
||
try:
|
||
order_info = {
|
||
'dingdan_id': new_dingdan_id,
|
||
'game_type': self._get_game_type_name(leixing_id),
|
||
'amount': str(jine),
|
||
'order_desc': (jieshao or '')[:50],
|
||
}
|
||
dingdan_guangbo.delay(order_info)
|
||
except Exception as e:
|
||
logger.error(f"广播通知失败: {e}")
|
||
|
||
# 7.5 跨平台同步(如果适用)
|
||
try:
|
||
sync_order_to_partners(new_dingdan_id)
|
||
except Exception as e:
|
||
logger.error(f"跨平台同步失败: {e}")
|
||
|
||
# 注意:此处不更新商家余额、发布量等统计,因为是替换订单
|
||
|
||
logger.info(f"商家{yonghuid}更换打手成功,旧订单{dingdan_id}已退款,新订单{new_dingdan_id}")
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '更换成功',
|
||
'data': {
|
||
'xin_dingdan_id': new_dingdan_id,
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"生成新订单事务失败: {e}", exc_info=True)
|
||
return Response({'code': 500, 'msg': '生成新订单失败'})
|
||
|
||
def _get_game_type_name(self, leixing_id):
|
||
"""根据类型ID获取游戏类型名称"""
|
||
try:
|
||
gt = ShangpinLeixing.objects.filter(id=leixing_id).first()
|
||
return gt.jieshao if gt else '游戏订单'
|
||
except Exception:
|
||
return '游戏订单'
|
||
|
||
|
||
|
||
# ===================== 视图:商家罚款申请 =====================
|
||
# 文件路径:dingdan/views.py 或单独文件,根据项目结构放置
|
||
|
||
|
||
|
||
|
||
class ShangjiaFakuanApplyView(APIView):
|
||
"""
|
||
商家罚款申请接口
|
||
路径:POST /dingdan/sjfksq
|
||
权限:JWT认证的商家用户
|
||
参数:
|
||
dingdan_id - 订单ID (必填)
|
||
chufa_liyou - 罚款原因 (必填)
|
||
fakuanjine - 罚款金额,元 (必填)
|
||
yingxiang_qiangdan - 是否影响抢单 1是 0否 (必填)
|
||
说明:
|
||
1. 自动从订单中获取被打手ID,防止商家篡改目标。
|
||
2. 同一订单同一打手不允许重复罚款。
|
||
3. 跨平台订单若为我方派单,则通知对方平台处罚;否则本地生成罚单。
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
# ========== 1. 身份验证 ==========
|
||
current_user = request.user
|
||
try:
|
||
shangjia_id = current_user.yonghuid # 商家的用户ID
|
||
except AttributeError:
|
||
return Response({'code': 1, 'msg': '用户认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
||
|
||
# ========== 2. 参数提取与校验 ==========
|
||
dingdan_id = request.data.get('dingdan_id', '').strip()
|
||
chufa_liyou = request.data.get('chufa_liyou', '').strip()
|
||
fakuanjine = request.data.get('fakuanjine', 0)
|
||
yingxiang_qiangdan = int(request.data.get('yingxiang_qiangdan', 1))
|
||
|
||
if not dingdan_id or not chufa_liyou or not fakuanjine:
|
||
return Response({'code': 2, 'msg': '参数不完整'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
fakuanjine = float(fakuanjine)
|
||
if fakuanjine <= 0:
|
||
raise ValueError
|
||
except (ValueError, TypeError):
|
||
return Response({'code': 2, 'msg': '罚款金额格式错误'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# ========== 3. 获取订单,校验归属商家 ==========
|
||
try:
|
||
# 关联查询商家扩展表,确保订单属于当前商家
|
||
shangjia_kuozhan = DingdanShangjia.objects.select_related('dingdan').get(
|
||
dingdan__dingdan_id=dingdan_id,
|
||
shangjia_id=shangjia_id
|
||
)
|
||
order = shangjia_kuozhan.dingdan
|
||
except DingdanShangjia.DoesNotExist:
|
||
return Response({'code': 3, 'msg': '订单不存在或您无权操作'}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# ========== 4. 获取被打手ID ==========
|
||
dashou_id = order.jiedan_dashou_id
|
||
if not dashou_id:
|
||
return Response({'code': 4, 'msg': '该订单暂无接单打手,无法罚款'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# ========== 5. 重复罚款检查 ==========
|
||
if Fadan.objects.filter(beichufa_id=dashou_id, guanliandingdan_id=dingdan_id).exists():
|
||
return Response({'code': 5, 'msg': '该打手在此订单已被罚款,不能重复申请'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# ========== 6. 跨平台信息 ==========
|
||
is_cross = order.is_cross
|
||
partner_order_id = order.partner_order_id or ''
|
||
partner_club_id = order.partner_club_id or ''
|
||
dispatch_type = order.dispatch_type or 0 # 1=我方派单,2=对方派单
|
||
|
||
# 是否需要通知对方平台:我方派单且存在对方订单ID
|
||
need_notify_partner = is_cross and partner_order_id and dispatch_type == 1
|
||
|
||
# ========== 7. 本地创建罚单(可重用) ==========
|
||
def create_local_fadan():
|
||
Fadan.objects.create(
|
||
beichufa_id=dashou_id,
|
||
shenqing_chufa=shangjia_id, # 申请人为当前商家
|
||
shenfen=1, # 被打者身份:1=打手
|
||
chufaliyou=chufa_liyou,
|
||
fakuanjine=fakuanjine,
|
||
guanliandingdan_id=dingdan_id,
|
||
zhuangtai=1, # 待缴纳
|
||
yingxiang_qiangdan=yingxiang_qiangdan,
|
||
shenqingren_shenfen=5, # 申请人是商家
|
||
partner_club_id='',
|
||
)
|
||
|
||
# ========== 8. 本地处罚分支 ==========
|
||
if not need_notify_partner:
|
||
with transaction.atomic():
|
||
create_local_fadan()
|
||
logger.info(f"商家{shangjia_id}对订单{dingdan_id}打手{dashou_id}罚款成功,金额{fakuanjine}")
|
||
return Response({'code': 0, 'msg': '罚款申请已提交'})
|
||
|
||
# ========== 9. 跨平台通知分支 ==========
|
||
try:
|
||
# 9.1 获取我方俱乐部ID
|
||
my_club = ClubConfig.objects.filter(is_self=1).first()
|
||
if not my_club:
|
||
return Response({'code': 500, 'msg': '系统配置错误'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
self_club_id = my_club.club_id
|
||
|
||
# 9.2 获取对方平台域名
|
||
club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id)
|
||
partner_domain = club_rel.partner_domain.rstrip('/')
|
||
except ObjectDoesNotExist:
|
||
return Response({'code': 500, 'msg': '对方平台信息未配置'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
# 9.3 构造通知参数
|
||
notify_url = f"{partner_domain}/houtai/kffkdssq_notify" # 与后台接口保持一致
|
||
payload = {
|
||
'order_id': partner_order_id,
|
||
'club_id': self_club_id,
|
||
'chufaliyou': chufa_liyou,
|
||
'fakuanjine': fakuanjine,
|
||
'yingxiang_qiangdan': yingxiang_qiangdan,
|
||
'shenqingren_shenfen': 5, # 商家身份
|
||
'operator': shangjia_id, # 操作人ID
|
||
}
|
||
|
||
# 9.4 带重试的通知
|
||
timeout = 3
|
||
retries = 2
|
||
success = False
|
||
for attempt in range(retries):
|
||
try:
|
||
resp = requests.post(notify_url, json=payload, timeout=timeout)
|
||
if resp.status_code == 200:
|
||
resp_data = resp.json()
|
||
if resp_data.get('code') == 0:
|
||
success = True
|
||
break
|
||
else:
|
||
logger.warning(f"对方平台罚款通知失败,响应: {resp_data}")
|
||
else:
|
||
logger.warning(f"对方平台HTTP错误: {resp.status_code}")
|
||
except Exception as e:
|
||
logger.warning(f"通知对方平台异常,第{attempt+1}次尝试: {e}")
|
||
continue
|
||
|
||
if not success:
|
||
return Response({'code': 500, 'msg': '通知对方平台失败,请稍后重试'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
# 9.5 通知成功后,本地也创建罚单(标记跨平台)
|
||
with transaction.atomic():
|
||
Fadan.objects.create(
|
||
beichufa_id=dashou_id,
|
||
shenqing_chufa=shangjia_id,
|
||
shenfen=1,
|
||
chufaliyou=chufa_liyou,
|
||
fakuanjine=fakuanjine,
|
||
guanliandingdan_id=dingdan_id,
|
||
zhuangtai=1,
|
||
yingxiang_qiangdan=yingxiang_qiangdan,
|
||
shenqingren_shenfen=5,
|
||
partner_club_id=partner_club_id, # 记录对方俱乐部ID
|
||
)
|
||
|
||
logger.info(f"商家{shangjia_id}跨平台罚款成功: {dingdan_id}")
|
||
return Response({'code': 0, 'msg': '罚款已提交至对方平台'})
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
class ShangjiaFakuaiXiugaiView(APIView):
|
||
"""
|
||
商家端统一修改接口:罚款修改/撤销/再次申请,订单退款撤销/修改理由
|
||
URL: /dingdan/sjfkxgycx
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
user = request.user
|
||
# 验证商家身份
|
||
try:
|
||
shangjia = user.shop_profile
|
||
except UserShangjia.DoesNotExist:
|
||
return Response({'code': 403, 'msg': '您不是商家'}, status=status.HTTP_403_FORBIDDEN)
|
||
if shangjia.zhuangtai != 1:
|
||
return Response({'code': 403, 'msg': '商家状态异常'}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
operation = request.data.get('operation')
|
||
dingdan_id = request.data.get('dingdan_id')
|
||
if not dingdan_id:
|
||
return Response({'code': 400, 'msg': '缺少订单ID'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 验证订单属于该商家
|
||
try:
|
||
dingdan = Dingdan.objects.select_related('shangjia_kuozhan').get(dingdan_id=dingdan_id)
|
||
except Dingdan.DoesNotExist:
|
||
return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND)
|
||
if not hasattr(dingdan, 'shangjia_kuozhan') or dingdan.shangjia_kuozhan.shangjia_id != user.yonghuid:
|
||
return Response({'code': 403, 'msg': '无权操作此订单'}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 处理订单相关操作
|
||
if operation == 'cancel_refund':
|
||
return self._cancel_refund(dingdan, request.data.get('liyou', ''))
|
||
elif operation == 'modify_refund_reason':
|
||
return self._modify_refund_reason(dingdan, request.data.get('liyou', ''))
|
||
|
||
# 处理罚款相关操作
|
||
fadan_id = request.data.get('fadan_id')
|
||
if not fadan_id:
|
||
return Response({'code': 400, 'msg': '缺少罚单ID'}, status=status.HTTP_400_BAD_REQUEST)
|
||
try:
|
||
fadan = Fadan.objects.get(id=fadan_id)
|
||
except Fadan.DoesNotExist:
|
||
return Response({'code': 404, 'msg': '罚单不存在'}, status=status.HTTP_404_NOT_FOUND)
|
||
if fadan.guanliandingdan_id != dingdan_id:
|
||
return Response({'code': 403, 'msg': '罚单与订单不匹配'}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
if operation == 'modify_penalty':
|
||
return self._modify_penalty(fadan, request.data.get('liyou', ''), request.data.get('jine'))
|
||
elif operation == 'cancel_penalty':
|
||
return self._cancel_penalty(fadan, request.data.get('liyou', ''))
|
||
elif operation == 'resubmit_penalty':
|
||
return self._resubmit_penalty(fadan, request.data.get('liyou', ''), request.data.get('jine'))
|
||
else:
|
||
return Response({'code': 400, 'msg': '无效的操作类型'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
def _cancel_refund(self, dingdan, liyou):
|
||
"""撤销退款申请(状态4 → 状态8,清空退款理由)"""
|
||
if dingdan.zhuangtai != 4:
|
||
return Response({'code': 400, 'msg': '只有退款中的订单可以撤销退款申请'})
|
||
with transaction.atomic():
|
||
dingdan.zhuangtai = 8
|
||
dingdan.tkly = '' # 清空退款理由
|
||
dingdan.save()
|
||
# 记录撤销理由可存入日志(如需要可另建日志表,此处简单处理)
|
||
logger.info(f"商家撤销退款订单 {dingdan.dingdan_id},理由: {liyou}")
|
||
return Response({'code': 0, 'msg': '已撤销退款申请'})
|
||
|
||
def _modify_refund_reason(self, dingdan, liyou):
|
||
"""修改退款理由(仅状态4时)"""
|
||
if dingdan.zhuangtai != 4:
|
||
return Response({'code': 400, 'msg': '只有退款中的订单可以修改退款理由'})
|
||
if not liyou.strip():
|
||
return Response({'code': 400, 'msg': '退款理由不能为空'})
|
||
dingdan.tkly = liyou[:500]
|
||
dingdan.save()
|
||
return Response({'code': 0, 'msg': '退款理由修改成功'})
|
||
|
||
def _modify_penalty(self, fadan, liyou, jine_str):
|
||
"""修改罚款(仅状态1待缴纳 或 3申诉中)"""
|
||
if fadan.zhuangtai not in (1, 3):
|
||
return Response({'code': 400, 'msg': '当前罚单状态不可修改'})
|
||
if not liyou.strip():
|
||
return Response({'code': 400, 'msg': '罚款理由不能为空'})
|
||
try:
|
||
jine = Decimal(jine_str)
|
||
if jine <= 0:
|
||
raise ValueError
|
||
except:
|
||
return Response({'code': 400, 'msg': '罚款金额必须为正数'})
|
||
with transaction.atomic():
|
||
fadan.chufaliyou = liyou[:500]
|
||
fadan.fakuanjine = jine
|
||
# 如果之前是驳回状态修改,需要重置状态为待缴纳?不,修改不应改变状态
|
||
fadan.save()
|
||
return Response({'code': 0, 'msg': '罚款修改成功'})
|
||
|
||
def _cancel_penalty(self, fadan, liyou):
|
||
"""撤销罚款(状态1或3 → 状态4 已驳回)"""
|
||
if fadan.zhuangtai not in (1, 3):
|
||
return Response({'code': 400, 'msg': '当前罚单状态不可撤销'})
|
||
if not liyou.strip():
|
||
return Response({'code': 400, 'msg': '驳回理由不能为空'})
|
||
with transaction.atomic():
|
||
fadan.zhuangtai = 4 # 已驳回
|
||
fadan.bohuiliyou = liyou[:500]
|
||
fadan.save()
|
||
return Response({'code': 0, 'msg': '罚款已撤销(驳回)'})
|
||
|
||
def _resubmit_penalty(self, fadan, liyou, jine_str):
|
||
"""再次申请罚款(状态4 → 状态1,修改理由和金额)"""
|
||
if fadan.zhuangtai != 4:
|
||
return Response({'code': 400, 'msg': '只有已驳回的罚单可再次申请'})
|
||
if not liyou.strip():
|
||
return Response({'code': 400, 'msg': '罚款理由不能为空'})
|
||
try:
|
||
jine = Decimal(jine_str)
|
||
if jine <= 0:
|
||
raise ValueError
|
||
except:
|
||
return Response({'code': 400, 'msg': '罚款金额必须为正数'})
|
||
with transaction.atomic():
|
||
fadan.zhuangtai = 1 # 待缴纳
|
||
fadan.chufaliyou = liyou[:500]
|
||
fadan.fakuanjine = jine
|
||
fadan.bohuiliyou = '' # 清空驳回理由
|
||
fadan.save()
|
||
return Response({'code': 0, 'msg': '已重新申请罚款'}) |