Files
Django/users/views/tixian.py
XingQue b54904a9f2 fix: 严格校验体验会员不可提现,抢单按会员类型匹配
展示层 clumber 与提现/抢单权限分离:佣金提现须正式会员并双重拦截体验期;抢单走 resolve_order_membership_ids + 总会员包含判断。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-08 14:26:16 +08:00

1426 lines
60 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""users.views.tixian - auto-generated by split script."""
import os
import sys
import json
import re
import time
import uuid
import random
import string
import requests
import hashlib
import traceback
import decimal
import jwt
import ipaddress
import xmltodict
from datetime import datetime, timedelta
from decimal import Decimal
import defusedxml.ElementTree as ET
from django.conf import settings
from django.db import models, transaction, IntegrityError, DatabaseError, connection
from django.db.models import Q, F, Sum, Count, Case, When, IntegerField, Prefetch
from django.core.cache import cache
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django.http import HttpResponse
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.hashers import make_password
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import permissions, status
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
from rest_framework.throttling import AnonRateThrottle
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.authentication import JWTAuthentication
from gvsdsdk.fluent import db, func, FQ
from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss
from utils.money import yuan_to_fen
from utils.fadan_utils import check_fadan_qiangdan_eligible
from utils.invitationcode_utils import CreateInvitationCode, VerifyInvitationCode
from users.fadan_fenhong_utils import process_fadan_fenhong
from orders.utils import (
update_daily_payout,
settle_shangjia_order_guanshi_fenhong
)
from backend.utils import (
update_dashou_daily_by_action, update_guanshi_daily_by_action,
update_shangjia_daily, update_zuzhang_daily_by_action,
verify_kefu_permission
)
from rank.utils import get_tag_fee, create_shenhe_jilu, validate_shenheguan
from ..models import (
UserBoss, UserDashou, UserShangjia, UserGuanshi,
UserZuzhang, UserKefu, AdminProfile, OfficialAccountUser,
TixianAutoRecord, TixianShenheJilu, Tixianjilu, Xiugaijilu,
RankingRecord
)
from users.business_models import User
from ..tixian_shenhe_services import (
load_audit_meta_map, refund_balance, sync_audit_and_jilu_status,
mark_transfer_success, release_collect_quota_for_record,
close_audit_wechat_failed, query_wechat_transfer_bill,
check_shijidaozhang_within_limit,
check_dashou_has_formal_huiyuan_for_withdraw,
check_dashou_trial_blocks_commission_withdraw,
WX_STATE_FAIL, WX_STATE_SUCCESS, WX_STATE_WAIT, WX_STATE_CANCELING,
)
from ..tixian_shenhe_views import process_audit_collect
from orders.models import (
Order, PlatformOrderExt, MerchantOrderExt, CommissionRate,
PenaltyRecord, PenaltyEvidenceImage, RefundRecord, PlayerDeliveryImage,
Penalty, PenaltyAppealImage
)
from products.models import (
ShangpinLeixing, Huiyuan, Huiyuangoumai, Gsfenhong, Czjilu
)
from config.models import Qunpeizhi
from backend.models import MerchantDailyStats
from rank.models import (
KaohePayTemp, Chenghao, ShenheJilu,
KaoheCishuFeiyong, YonghuChenghao
)
from users.models import UserShenheguan
from products.utils import update_shangpin_daily_stat
from shop.utils import update_dianpu_daily_stat
from rank.utils import create_shenhe_jilu_from_temp
import logging
logger = logging.getLogger(__name__)
class TixianXinxiHuoquView(APIView):
"""
获取用户提现收款信息接口
返回用户已设置的手机号、收款码、收款账号
"""
permission_classes = [permissions.IsAuthenticated]
def post(self, request):
"""
处理获取收款信息的POST请求
直接返回用户当前的收款设置
"""
try:
# 从request.user获取当前用户
user_main = request.user
from jituan.services.club_user import get_user_club_id
from jituan.services.withdraw_config import get_tixian_feilv
club_id = get_user_club_id(user_main)
dashou_rate = get_tixian_feilv(club_id, 1)
guanshi_rate = get_tixian_feilv(club_id, 2)
# 构建返回数据
response_data = {
'txdianhua': user_main.Phone if user_main.Phone else '',
'txtupian': user_main.PaymentQRCode if user_main.PaymentQRCode else '',
'txzh': user_main.PaymentAccount if user_main.PaymentAccount else '',
'dashou_rate': dashou_rate,
'guanshi_rate': guanshi_rate,
}
# 返回成功响应
return Response({
'code': 0,
'msg': '获取成功',
'data': response_data
})
except Exception as e:
return Response({
'code': 99,
'msg': f'系统错误: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class ShoukuanXinxiShangchuanView(APIView):
"""
上传用户收款信息接口
支持同时上传手机号、收款账号、收款码图片
使用multipart/form-data格式
"""
permission_classes = [permissions.IsAuthenticated]
parser_classes = (MultiPartParser,FormParser,JSONParser)
def post(self, request):
"""
处理上传收款信息的POST请求
验证并保存用户的收款设置
"""
try:
# 获取当前用户
user_main = request.user
yonghuid = user_main.UserUID
# 准备响应数据
response_data = {}
# 1. 验证必填字段:手机号必须要有
txdianhua = request.data.get('txdianhua', '').strip()
if not txdianhua:
return Response({
'code': 1,
'msg': '收款人电话不能为空',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 验证手机号格式11位数字
if not txdianhua.isdigit() or len(txdianhua) != 11:
return Response({
'code': 2,
'msg': '请输入11位有效的手机号码',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 2. 获取收款账号(可能为空)
txzh = request.data.get('txzh', '').strip()
# 3. 处理收款码图片上传(如果有)
txtupian_file = request.FILES.get('file') # 注意前端upload.js使用'file'作为字段名
txtupian_url = ''
if txtupian_file:
# 验证图片
is_valid, error_msg = validate_image(txtupian_file)
if not is_valid:
return Response({
'code': 3,
'msg': f'图片验证失败: {error_msg}',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 上传收款码图片
txtupian_url = self.handle_shoukuanma_upload(user_main, txtupian_file)
if txtupian_url:
response_data['txtupian'] = txtupian_url
else:
return Response({
'code': 4,
'msg': '收款码上传失败',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
else:
response_data['txtupian'] = ''
# 4. 验证收款账号和收款码至少有一个
if not txzh and not txtupian_url:
return Response({
'code': 5,
'msg': '请填写收款账号或上传收款码',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 5. 保存所有信息到数据库
with transaction.atomic():
user_main.Phone = txdianhua
user_main.PaymentAccount = txzh if txzh else ''
# 只有上传了图片才更新zhifu字段
if txtupian_url:
user_main.PaymentQRCode = txtupian_url
user_main.save()
# 6. 构建完整的返回数据
response_data['txdianhua'] = txdianhua
response_data['txzh'] = txzh if txzh else ''
# 7. 返回成功响应
return Response({
'code': 0,
'msg': '收款信息设置成功',
'data': response_data
})
except Exception as e:
traceback.print_exc() # 打印详细错误信息到控制台
return Response({
'code': 99,
'msg': f'系统错误: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def handle_shoukuanma_upload(self, user_main, image_file):
"""
处理收款码图片上传到腾讯云COS
存储路径: tixian/{yonghuid}/{yonghuid}.扩展名
"""
try:
yonghuid = user_main.UserUID
if not yonghuid:
return None
# 获取旧收款码URL
old_shoukuanma_url = user_main.PaymentQRCode
# 生成文件扩展名
file_name = image_file.name
file_extension = os.path.splitext(file_name)[1].lower()
if not file_extension:
file_extension = '.jpg'
# 构建OSS文件路径: tixian/{yonghuid}/{yonghuid}{扩展名}
# 使用用户ID作为文件名确保同一用户多次上传会覆盖
oss_file_name = f"{yonghuid}{file_extension}"
oss_file_path = f"tixian/{yonghuid}/{oss_file_name}"
# 删除旧收款码(如果存在)
if old_shoukuanma_url and old_shoukuanma_url.strip():
delete_from_oss(old_shoukuanma_url)
# 上传新收款码到OSS
new_shoukuanma_url = upload_to_oss(image_file, oss_file_path)
if new_shoukuanma_url:
# 返回相对路径从tixian文件夹开始
return oss_file_path
else:
return None
except Exception as e:
print(f"处理收款码上传失败: {e}")
return None
# ========== 订单状态常量(请根据您的实际业务修改)==========
# 假设状态值:已完成=9已退款=10示例实际以您的项目为准
ORDER_STATUS_COMPLETED = 3 # 订单已完成
ORDER_STATUS_REFUNDED = 5 # 订单已退款
ORDER_STATUS_FAIL = 6
# 进行中状态(不可提现)例如:待接单、已接单、进行中等,您需要定义排除列表
FORBIDDEN_STATUS_FOR_WITHDRAW = [1, 2, 3, 4, 5, 6, 7, 8] # 除已完成和已退款外的所有状态
class TixianShenqingView(APIView):
"""
用户提现申请接口(完整版)
支持类型:
1-打手佣金提现
2-管事分红提现
3-组长分红提现
4-审核官分佣提现
5-打手押金提现(退出平台)
6-商家余额提现(退出平台)
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
user_main = request.user
yonghuid = user_main.UserUID
# 1. 获取并验证参数
leixing = request.data.get('leixing')
jine = request.data.get('jine')
fangshi = request.data.get('fangshi')
txskfs = request.data.get('txskfs', 0)
# 罚单检查(共用)
fadan_ok, fadan_msg = check_fadan_qiangdan_eligible(yonghuid, 2)
if not fadan_ok:
return Response({'code': 400, 'msg': fadan_msg})
# 参数完整性
missing = []
if leixing is None: missing.append('leixing')
if jine is None: missing.append('jine')
if fangshi is None: missing.append('fangshi')
if missing:
return Response({'code': 1, 'msg': f'缺少参数: {", ".join(missing)}'},
status=status.HTTP_400_BAD_REQUEST)
try:
leixing = int(leixing)
jine = decimal.Decimal(str(jine))
fangshi = int(fangshi)
txskfs = int(txskfs)
except (ValueError, TypeError, decimal.InvalidOperation) as e:
return Response({'code': 2, 'msg': f'参数格式错误: {str(e)}'},
status=status.HTTP_400_BAD_REQUEST)
if jine <= decimal.Decimal('0'):
return Response({'code': 3, 'msg': '提现金额必须大于0'},
status=status.HTTP_400_BAD_REQUEST)
# 支持的提现类型1-6
if leixing not in [1, 2, 3, 4, 5, 6]:
return Response({'code': 4, 'msg': '无效的提现类型'},
status=status.HTTP_400_BAD_REQUEST)
if fangshi not in [1, 2]:
return Response({'code': 5, 'msg': '无效的收款方式只能是1(微信)或2(支付宝)'},
status=status.HTTP_400_BAD_REQUEST)
from jituan.services.club_user import get_user_club_id
from jituan.services.withdraw_config import get_tixian_feilv
club_id = get_user_club_id(user_main)
current_rate = decimal.Decimal('0.00')
rate_key = {
1: '5', # 打手佣金
2: '6', # 管事分红
3: '8', # 组长分红
4: '9', # 审核官分佣
5: '11', # 打手押金提现
6: '10', # 商家余额提现
}.get(leixing)
if rate_key:
try:
current_rate = get_tixian_feilv(club_id, leixing)
except ValueError:
return Response({'code': 41, 'msg': f'提现费率未配置(类型{leixing}'},
status=status.HTTP_400_BAD_REQUEST)
shouxufei = jine * current_rate
shijidaozhang = jine - shouxufei
if shijidaozhang <= decimal.Decimal('0'):
return Response({'code': 6, 'msg': '提现金额过低,扣除手续费后无实际到账'},
status=status.HTTP_400_BAD_REQUEST)
# 3. 根据提现类型执行校验和扣款
nicheng = ''
if leixing == 1:
result = self._check_dashou_commission_withdraw(user_main, jine)
if result['code'] != 0:
return Response(result, status=status.HTTP_400_BAD_REQUEST)
nicheng = result['data']['nicheng']
# 扣款(佣金)
with transaction.atomic():
profile = UserDashou.objects.select_for_update().get(user=user_main)
if profile.yue < jine:
return Response({'code': 13, 'msg': '余额不足'})
profile.yue = F('yue') - jine
profile.save()
elif leixing == 2:
result = self._check_guanshi_dividend_withdraw(user_main, jine)
if result['code'] != 0:
return Response(result, status=status.HTTP_400_BAD_REQUEST)
with transaction.atomic():
profile = UserGuanshi.objects.select_for_update().get(user=user_main)
if profile.yue < jine:
return Response({'code': 22, 'msg': '余额不足'})
profile.yue = F('yue') - jine
profile.save()
nicheng = "管事用户"
elif leixing == 3:
result = self._check_zuzhang_withdraw(user_main, jine)
if result['code'] != 0:
return Response(result, status=status.HTTP_400_BAD_REQUEST)
with transaction.atomic():
profile = UserZuzhang.objects.select_for_update().get(user=user_main)
if profile.ketixian_jine < jine:
return Response({'code': 32, 'msg': '可提现金额不足'})
profile.ketixian_jine = F('ketixian_jine') - jine
profile.save()
nicheng = user_main.nicheng if hasattr(user_main, 'nicheng') else ''
elif leixing == 4:
result = self._check_shenheguan_withdraw(user_main, jine)
if result['code'] != 0:
return Response(result, status=status.HTTP_400_BAD_REQUEST)
with transaction.atomic():
profile = UserShenheguan.objects.select_for_update().get(user=user_main)
if profile.yue < jine:
return Response({'code': 42, 'msg': '可提现金额不足'})
profile.yue = F('yue') - jine
profile.save()
nicheng = "审核官"
elif leixing == 5:
# 打手押金提现(退出)
result = self._check_dashou_yajin_withdraw(user_main, jine)
if result['code'] != 0:
return Response(result, status=status.HTTP_400_BAD_REQUEST)
with transaction.atomic():
profile = UserDashou.objects.select_for_update().get(user=user_main)
if profile.yajin < jine:
return Response({'code': 53, 'msg': '押金余额不足'})
profile.yajin = F('yajin') - jine
# 押金提现后封禁打手账号
profile.zhanghaozhuangtai = 0 # 0表示禁用
profile.save()
nicheng = profile.nicheng or ''
elif leixing == 6:
# 商家余额提现(退出)
result = self._check_shangjia_withdraw(user_main, jine)
if result['code'] != 0:
return Response(result, status=status.HTTP_400_BAD_REQUEST)
with transaction.atomic():
profile = UserShangjia.objects.select_for_update().get(user=user_main)
if profile.yue < jine:
return Response({'code': 63, 'msg': '商家余额不足'})
profile.yue = F('yue') - jine
# 注意:是否封禁商家账号?根据需求,用户未明确要求封禁,暂时只扣款,可后续扩展。
profile.save()
nicheng = profile.nicheng or ''
# 4. 创建提现记录(先扣款后创建,确保资金安全)
try:
avatar = user_main.Avatar or ''
phone = user_main.Phone or ''
zhifu = user_main.PaymentQRCode or ''
skzhanghao = user_main.PaymentAccount or ''
from jituan.services.club_user import get_user_club_id
tixian_record = Tixianjilu.query.create(
club_id=get_user_club_id(user_main),
yonghuid=yonghuid,
avatar=avatar,
phone=phone,
nicheng=nicheng,
leixing=leixing,
zhifu=zhifu,
skzhanghao=skzhanghao,
jine=shijidaozhang, # 记录实际到账金额
zhuangtai=1, # 审核中
fangshi=fangshi,
shenheid=None,
bhliyou='',
)
except Exception as e:
return Response({'code': 99, 'msg': f'创建提现记录失败: {str(e)}'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# 5. 返回成功响应
return Response({
'code': 0,
'msg': '提现申请提交成功',
'data': {
'tixian_id': tixian_record.id,
'leixing': leixing,
'jine': str(jine.quantize(decimal.Decimal('0.01'))),
'fangshi': fangshi,
'zhuangtai': 1,
'CreateTime': tixian_record.CreateTime.strftime('%Y-%m-%d %H:%M:%S'),
'current_rate': str(current_rate.quantize(decimal.Decimal('0.0000'))),
'shouxufei': str(shouxufei.quantize(decimal.Decimal('0.01'))),
'shijidaozhang': str(shijidaozhang.quantize(decimal.Decimal('0.01'))),
'tip': f'本次提现手续费率{current_rate * 100}%,手续费{shouxufei}元,实际到账{shijidaozhang}'
}
})
except Exception as e:
traceback.print_exc()
return Response({'code': 99, 'msg': f'系统错误: {str(e)}'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# ==================== 校验函数 ====================
def _check_dashou_commission_withdraw(self, user_main, jine):
"""打手佣金提现校验"""
try:
dashou = UserDashou.query.get(user=user_main)
except ObjectDoesNotExist:
return {'code': 10, 'msg': '您不是打手身份,无法提现佣金'}
if dashou.zhanghaozhuangtai != 1:
return {'code': 11, 'msg': '打手账号已被封禁,无法提现'}
if dashou.zhuangtai != 1:
return {'code': 12, 'msg': '您有订单进行中,请完成后提现'}
if dashou.jifen != 10:
return {'code': 15, 'msg': '积分不足10分请补充积分后提现'}
if dashou.yue < jine:
return {'code': 13, 'msg': f'余额不足,当前余额: {dashou.yue}'}
formal_ok, formal_msg = check_dashou_has_formal_huiyuan_for_withdraw(user_main.UserUID)
if not formal_ok:
return {'code': 14, 'msg': formal_msg}
trial_ok, trial_msg = check_dashou_trial_blocks_commission_withdraw(user_main.UserUID)
if not trial_ok:
return {'code': 16, 'msg': trial_msg}
return {'code': 0, 'msg': 'ok', 'data': {'nicheng': dashou.nicheng or ''}}
def _check_guanshi_dividend_withdraw(self, user_main, jine):
"""管事分红提现校验"""
try:
guanshi = UserGuanshi.query.get(user=user_main)
except ObjectDoesNotExist:
return {'code': 20, 'msg': '您不是管事身份,无法提现分红'}
if guanshi.zhuangtai != 1:
return {'code': 21, 'msg': '管事账号已被封禁,无法提现'}
if guanshi.yue < jine:
return {'code': 22, 'msg': f'余额不足,当前余额: {guanshi.yue}'}
return {'code': 0, 'msg': 'ok', 'data': {}}
def _check_zuzhang_withdraw(self, user_main, jine):
"""组长分红提现校验"""
try:
zuzhang = UserZuzhang.query.get(user=user_main)
except ObjectDoesNotExist:
return {'code': 30, 'msg': '您不是组长身份,无法提现组长分红'}
if zuzhang.zhuangtai != 1:
return {'code': 31, 'msg': '组长账号已被封禁,无法提现'}
if zuzhang.ketixian_jine < jine:
return {'code': 32, 'msg': f'可提现金额不足,当前可提: {zuzhang.ketixian_jine}'}
return {'code': 0, 'msg': 'ok', 'data': {}}
def _check_shenheguan_withdraw(self, user_main, jine):
"""审核官分佣提现校验"""
try:
shenheguan = UserShenheguan.query.get(user=user_main)
except ObjectDoesNotExist:
return {'code': 40, 'msg': '您不是审核官身份,无法提现分佣'}
if shenheguan.zhuangtai != 1:
return {'code': 41, 'msg': '审核官账号已被封禁,无法提现'}
if shenheguan.yue < jine:
return {'code': 42, 'msg': f'可提现金额不足,当前可提: {shenheguan.yue}'}
return {'code': 0, 'msg': 'ok', 'data': {}}
def _check_shangjia_withdraw(self, user_main, jine):
"""
商家余额提现校验(退出平台)
包含:订单、自身罚单、申请的罚单检查
"""
try:
shangjia = UserShangjia.query.get(user=user_main)
except ObjectDoesNotExist:
return {'code': 60, 'msg': '您不是商家身份,无法提现商家余额'}
if shangjia.zhuangtai != 1:
return {'code': 61, 'msg': '商家账号已被禁用,无法提现'}
if shangjia.yue < jine:
return {'code': 62, 'msg': f'商家余额不足,当前余额: {shangjia.yue}'}
# 订单检查(使用 Q 取反)
unfinished_orders = MerchantOrderExt.query.filter(
MerchantID=user_main.UserUID,
).exclude(
Order__Status__in=[ORDER_STATUS_COMPLETED, ORDER_STATUS_REFUNDED,ORDER_STATUS_FAIL]
).exists()
if unfinished_orders:
return {'code': 63, 'msg': '您还有未完成或未退款的订单,请先处理完毕再提现余额'}
# 自身被处罚的罚单检查(状态不是已缴纳(2)和已驳回(4)
self_fadan = Penalty.query.filter(
PenalizedUserID=user_main.UserUID,
).exclude(
Status__in=[2, 4]
).exists()
if self_fadan:
return {'code': 64, 'msg': '您有未处理完毕的自身罚单(待缴纳/申诉中),请先处理'}
# 申请的对他人处罚罚单检查(状态不是已缴纳(2)和已驳回(4)
applied_fadan = Penalty.query.filter(
ApplicantID=user_main.UserUID,
).exclude(
Status__in=[2, 4]
).exists()
if applied_fadan:
return {'code': 65, 'msg': '您发起的对其他用户的处罚尚未处理完毕(待缴纳/申诉中),请等待完结'}
return {'code': 0, 'msg': 'ok', 'data': {'nicheng': shangjia.nicheng or ''}}
def _check_dashou_yajin_withdraw(self, user_main, jine):
"""
打手押金提现校验(退出平台)
要求:无进行中的订单,无未结算订单,自身罚单需已处理
"""
try:
dashou = UserDashou.query.get(user=user_main)
except ObjectDoesNotExist:
return {'code': 50, 'msg': '您不是打手身份,无法提取押金'}
if dashou.zhanghaozhuangtai != 1:
return {'code': 51, 'msg': '打手账号已禁用,无法提取押金'}
if dashou.zhuangtai != 1:
return {'code': 52, 'msg': '您有订单进行中,请完成后提取押金'}
if dashou.yajin < jine:
return {'code': 53, 'msg': f'押金余额不足,当前押金: {dashou.yajin}'}
# 订单检查(使用 exclude + __in
unfinished_orders = Order.query.filter(
PlayerID=user_main.UserUID,
).exclude(
Status__in=[ORDER_STATUS_COMPLETED, ORDER_STATUS_REFUNDED]
).exists()
if unfinished_orders:
return {'code': 54, 'msg': '您还有未完成或未退款的订单,请先处理完毕再提取押金'}
# 自身罚单检查
self_fadan = Penalty.query.filter(
PenalizedUserID=user_main.UserUID,
).exclude(
Status__in=[2, 4]
).exists()
if self_fadan:
return {'code': 55, 'msg': '您有未处理完毕的自身罚单(待缴纳/申诉中),请先处理'}
return {'code': 0, 'msg': 'ok', 'data': {'nicheng': dashou.nicheng or ''}}
class TixianJiluHuoquViewV2(APIView):
"""
提现记录获取接口
mode=1手动提现记录默认兼容旧版
mode=2自动提现审核记录含 shenhe_jilu_id / shenhe_danhao支持 zhuangtai 筛选)
"""
permission_classes = [permissions.IsAuthenticated]
def post(self, request):
try:
user_main = request.user
yonghuid = user_main.UserUID
page = request.data.get('page', 1)
limit = request.data.get('limit', 50)
mode = int(request.data.get('mode', 1) or 1)
zhuangtai_filter = int(request.data.get('zhuangtai', 0) or 0)
try:
page = int(page)
limit = int(limit)
if page < 1 or limit < 1:
return Response({'code': 1, 'msg': '参数错误', 'data': None},
status=status.HTTP_400_BAD_REQUEST)
except (TypeError, ValueError):
return Response({'code': 1, 'msg': '参数格式错误', 'data': None},
status=status.HTTP_400_BAD_REQUEST)
if limit > 100:
limit = 100
offset = (page - 1) * limit
qs = Tixianjilu.query.filter(yonghuid=yonghuid)
if mode == 2:
# 自动提现含新审核关联记录zhuangtai>0 时按状态筛选
if zhuangtai_filter > 0:
qs = qs.filter(zhuangtai=zhuangtai_filter)
qs = qs.order_by('-CreateTime')
records_queryset = qs.values(
'id', 'leixing', 'jine', 'zhuangtai', 'CreateTime', 'fangshi',
'phone', 'skzhanghao', 'zhifu', 'bhliyou',
'shenhe_jilu_id', 'shenhe_danhao',
)[offset:offset + limit + 1]
records_list = list(records_queryset)
has_more = len(records_list) > limit
records_to_return = records_list[:limit] if has_more else records_list
# 批量取审核表 fail_reason
audit_ids = [r['shenhe_jilu_id'] for r in records_to_return if r.get('shenhe_jilu_id')]
audit_map = {}
if audit_ids:
for a in TixianShenheJilu.query.filter(id__in=audit_ids).values(
'id', 'fail_reason', 'shenqing_jine', 'shenhe_danhao'
):
audit_map[a['id']] = a
# 批量取自动打款失败原因
danhao_list = [r['shenhe_danhao'] for r in records_to_return if r.get('shenhe_danhao')]
auto_fail_map = {}
if danhao_list:
for ar in TixianAutoRecord.query.filter(
shenhe_danhao__in=danhao_list, zhuangtai=2
).order_by('-CreateTime').values('shenhe_danhao', 'fail_reason'):
if ar['shenhe_danhao'] not in auto_fail_map:
auto_fail_map[ar['shenhe_danhao']] = ar['fail_reason']
formatted_records = []
for record in records_to_return:
audit = audit_map.get(record.get('shenhe_jilu_id')) if record.get('shenhe_jilu_id') else None
fail_reason = ''
if audit and audit.get('fail_reason'):
fail_reason = audit['fail_reason']
elif record.get('shenhe_danhao'):
fail_reason = auto_fail_map.get(record['shenhe_danhao'], '') or ''
jine_val = float(record['jine']) if record['jine'] else 0.00
if mode == 2 and audit and audit.get('shenqing_jine') is not None:
jine_val = float(audit['shenqing_jine'])
shenhe_danhao_val = record.get('shenhe_danhao') or (audit.get('shenhe_danhao') if audit else '')
formatted_records.append({
'id': record['id'],
'tixianjilu_id': record['id'],
'tixian_id': record['id'],
'leixing': record['leixing'],
'jine': jine_val,
'zhuangtai': record['zhuangtai'],
'create': record['CreateTime'].strftime('%Y-%m-%d %H:%M:%S'),
'fangshi': record['fangshi'],
'txdianhua': record['phone'] or '',
'txzh': record['skzhanghao'] or '',
'txtupian': record['zhifu'] or '',
'shibaiyuanyin': record['bhliyou'] or '',
'bhliyou': record['bhliyou'] or '',
'fail_reason': fail_reason,
'shenhe_jilu_id': record.get('shenhe_jilu_id'),
'shenhe_danhao': shenhe_danhao_val,
})
return Response({
'code': 0,
'msg': '获取成功',
'data': {
'list': formatted_records,
'has_more': has_more,
'page': page,
'limit': limit,
'current_count': len(formatted_records),
},
})
except Exception as e:
return Response({
'code': 99,
'msg': f'系统错误: {str(e)}',
'data': None,
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class YonghuTixianShenheXiugaiView(APIView):
"""
用户修改审核中提现申请接口
访问路径: /api/yonghu/yonghutxshxg
请求方法: POST
权限要求: JWT Token认证通过的用户
请求体: multipart/form-data 格式
支持字段: tixian_id, fangshi, txdianhua, txzh, file (图片文件)
功能: 允许用户修改审核中提现申请的收款方式、电话、账号、收款码等
"""
permission_classes = [IsAuthenticated]
parser_classes = (MultiPartParser, FormParser, JSONParser)
def post(self, request):
"""
处理用户修改审核中提现申请的POST请求
"""
try:
# 1. 获取当前用户
user_main = request.user
current_yonghuid = user_main.UserUID
# 2. 验证必填字段提现记录ID
tixian_id = request.data.get('tixian_id')
if not tixian_id:
return Response({
'code': 1,
'msg': '提现记录ID不能为空',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 3. 查询提现记录使用select_for_update确保事务安全
with transaction.atomic():
try:
tixian_record = Tixianjilu.objects.select_for_update().get(
id=tixian_id,
yonghuid=current_yonghuid # 确保只能修改自己的记录
)
except Tixianjilu.DoesNotExist:
return Response({
'code': 2,
'msg': '提现记录不存在或无权修改',
'data': None
}, status=status.HTTP_404_NOT_FOUND)
# 4. 验证提现记录状态(必须是审核中)
if tixian_record.zhuangtai != 1:
return Response({
'code': 3,
'msg': '只有审核中的提现申请可以修改',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 5. 获取并验证提现方式
fangshi = request.data.get('fangshi')
if fangshi:
fangshi = int(fangshi)
if fangshi not in [1, 2]:
return Response({
'code': 4,
'msg': '提现方式无效请选择1(微信)或2(支付宝)',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 检查提现类型与方式的匹配如果前端提供了leixing
leixing = request.data.get('leixing')
if leixing:
leixing = int(leixing)
# 这里可以根据业务逻辑添加类型与方式的验证
# 例如:某些类型只能使用某些方式
# 6. 验证手机号(必填)
txdianhua = request.data.get('txdianhua', '').strip()
if not txdianhua:
return Response({
'code': 5,
'msg': '收款人电话不能为空',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 验证手机号格式
if not txdianhua.isdigit() or len(txdianhua) != 11:
return Response({
'code': 6,
'msg': '请输入11位有效的手机号码',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 7. 获取收款账号(可选)
txzh = request.data.get('txzh', '').strip()
# 8. 处理收款码图片上传(如果有)
txtupian_file = request.FILES.get('file')
new_zhifu_url = None
old_zhifu_url = tixian_record.zhifu
if txtupian_file:
# 验证图片
is_valid, error_msg = validate_image(txtupian_file)
if not is_valid:
return Response({
'code': 7,
'msg': f'图片验证失败: {error_msg}',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 上传收款码图片
new_zhifu_url = self.handle_shoukuanma_upload(
user_main,
txtupian_file,
old_zhifu_url,
tixian_record.leixing
)
if not new_zhifu_url:
return Response({
'code': 8,
'msg': '收款码上传失败',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# 9. 验证收款信息(账号和图片至少有一个)
if not txzh and not (new_zhifu_url or old_zhifu_url):
return Response({
'code': 9,
'msg': '请填写收款账号或上传收款码',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 10. 更新提现记录
update_fields = []
# 更新提现方式(如果有)
if fangshi:
tixian_record.fangshi = fangshi
update_fields.append('fangshi')
# 更新手机号(必填)
tixian_record.phone = txdianhua
update_fields.append('phone')
# 更新收款账号(如果有)
if txzh is not None: # 注意:空字符串也要更新
tixian_record.skzhanghao = txzh
update_fields.append('skzhanghao')
# 更新收款码图片(如果有新图片)
if new_zhifu_url:
tixian_record.zhifu = new_zhifu_url
update_fields.append('zhifu')
# 保存更新
if update_fields:
tixian_record.save(update_fields=update_fields)
# 11. 构建返回数据
response_data = {
'tixian_id': tixian_record.id,
'fangshi': tixian_record.fangshi,
'txdianhua': tixian_record.phone,
'txzh': tixian_record.skzhanghao or '',
'txtupian': tixian_record.zhifu or '' # 返回相对URL
}
# 12. 返回成功响应
return Response({
'code': 0,
'msg': '提现申请修改成功',
'data': response_data
})
except Exception as e:
# 记录详细错误日志
logger.error(
f"修改提现申请异常 - 用户ID: {getattr(user_main, 'yonghuid', 'N/A')}, "
f"提现ID: {tixian_id}, "
f"错误类型: {type(e).__name__}, "
f"错误详情: {str(e)}",
exc_info=True
)
# 返回友好错误信息
return Response({
'code': 99,
'msg': '系统繁忙,请稍后重试',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def handle_shoukuanma_upload(self, user_main, image_file, old_zhifu_url, leixing=None):
"""
处理收款码图片上传到腾讯云COS
存储路径: tixian/{yonghuid}/{timestamp_uuid}.扩展名
参数:
- user_main: 当前用户对象
- image_file: 图片文件对象
- old_zhifu_url: 旧收款码URL用于删除
- leixing: 提现类型(可选,用于文件夹分类)
返回: 新的图片相对路径
"""
try:
yonghuid = user_main.UserUID
if not yonghuid:
return None
# 删除旧收款码(如果存在且不是空字符串)
if old_zhifu_url and old_zhifu_url.strip():
delete_from_oss(old_zhifu_url)
# 获取文件扩展名
file_name = image_file.name
file_extension = os.path.splitext(file_name)[1].lower()
if not file_extension:
file_extension = '.jpg'
# 生成唯一文件名(使用时间戳+UUID确保不重复
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
unique_id = str(uuid.uuid4())[:8] # 取UUID前8位
unique_file_name = f"{timestamp}_{unique_id}{file_extension}"
# 根据提现类型构建文件夹路径如果提供了leixing
if leixing:
# leixing=1:打手佣金, leixing=2:管事分红
type_folder = 'dashou' if leixing == 1 else 'guanshi'
oss_file_path = f"tixian/{type_folder}/{yonghuid}/{unique_file_name}"
else:
# 默认路径
oss_file_path = f"tixian/{yonghuid}/{unique_file_name}"
# 上传新收款码到OSS
new_shoukuanma_url = upload_to_oss(image_file, oss_file_path)
if new_shoukuanma_url:
# 返回相对路径从tixian文件夹开始
return oss_file_path
else:
return None
except Exception as e:
print(f"处理收款码上传失败: {e}")
return None
class TixianAssetView(APIView):
"""
获取用户所有可提现资产及费率
请求方式POST
认证JWT Token
返回:
{
"code": 200,
"msg": "success",
"data": {
"dashou_yue": "123.45",
"dashou_yajin": "500.00",
"shangjia_yue": "0.00",
"guanshi_fenyong": "67.89",
"zuzhang_fenyong": "0.00",
"kaoheguan_fenyong": "0.00",
"dashou_rate": 0.08,
"dashou_yajin_rate": 0.05,
"shangjia_rate": 0.06,
"guanshi_rate": 0.08,
"zuzhang_rate": 0.05,
"kaoheguan_rate": 0.07
}
}
"""
permission_classes = [IsAuthenticated]
def post(self, request):
user = User.query.select_related(
'DashouProfile', 'ShopProfile', 'GuanshiProfile', 'ZuzhangProfile', 'ShenheguanProfile'
).get(UserUID=request.user.UserUID)
# 初始化返回数据默认全部为0
data = {
'dashou_yue': '0.00',
'dashou_yajin': '0.00',
'shangjia_yue': '0.00',
'guanshi_fenyong': '0.00',
'zuzhang_fenyong': '0.00',
'kaoheguan_fenyong': '0.00',
'dashou_rate': 0.00,
'dashou_yajin_rate': 0.00,
'shangjia_rate': 0.00,
'guanshi_rate': 0.00,
'zuzhang_rate': 0.00,
'kaoheguan_rate': 0.00,
}
# ------------------- 打手资产 -------------------
try:
dashou = user.DashouProfile
data['dashou_yue'] = str(dashou.yue)
data['dashou_yajin'] = str(dashou.yajin)
except ObjectDoesNotExist:
pass
# ------------------- 商家资产 -------------------
try:
shangjia = user.ShopProfile
data['shangjia_yue'] = str(shangjia.yue)
except ObjectDoesNotExist:
pass
# ------------------- 管事资产 -------------------
try:
guanshi = user.GuanshiProfile
data['guanshi_fenyong'] = str(guanshi.yue)
except ObjectDoesNotExist:
pass
# ------------------- 组长资产 -------------------
try:
zuzhang = user.ZuzhangProfile
data['zuzhang_fenyong'] = str(zuzhang.ketixian_jine)
except ObjectDoesNotExist:
pass
# ------------------- 审核官(考核官)资产 -------------------
try:
kaoheguan = user.ShenheguanProfile
data['kaoheguan_fenyong'] = str(kaoheguan.yue)
except ObjectDoesNotExist:
pass
from jituan.services.club_user import get_user_club_id
from jituan.services.withdraw_config import build_tixian_asset_rates
club_id = get_user_club_id(user)
data.update(build_tixian_asset_rates(club_id))
return Response({
'code': 200,
'msg': '获取成功',
'data': data
}, status=status.HTTP_200_OK)
class GuanshiContactView(APIView):
"""
管事获取自己的邀请人(组长)信息
路径: /api/yonghu/guanshi_contact
方法: POST
权限: JWT认证
请求体: 无仅需携带合法token
成功返回:
code: 200,
data: {
uid: "1234567", // 组长用户ID
nicheng: "张三", // 组长昵称
touxiang: "avatar/xxx.jpg" // 组长头像相对路径前端拼接完整URL
}
"""
permission_classes = [IsAuthenticated]
def post(self, request):
# 1. 获取当前登录用户必须已通过JWT认证
current_user = request.user # 类型: User
# 2. 校验是否为管事身份(存在管事扩展表记录)
try:
guanshi_profile = current_user.GuanshiProfile
except ObjectDoesNotExist:
return Response({
'code': 403,
'msg': '您还不是管事,无法获取组长信息',
'data': None
})
# 3. 获取邀请人ID管事扩展表中的 yaoqingren 字段存储的是邀请人的 yonghuid
inviter_yonghuid = guanshi_profile.yaoqingren
if not inviter_yonghuid:
return Response({
'code': 404,
'msg': '未找到邀请人信息',
'data': None
})
# 4. 根据邀请人用户ID查询用户主表
try:
inviter_user = User.query.get(UserUID=inviter_yonghuid)
except User.DoesNotExist:
logger.error(f"邀请人用户不存在: yonghuid={inviter_yonghuid}")
return Response({
'code': 404,
'msg': '邀请人账号异常',
'data': None
})
# 5. 获取邀请人昵称(从老板扩展表取,若不存在则给默认值)
try:
inviter_nicheng = inviter_user.BossProfile.nickname
except ObjectDoesNotExist:
inviter_nicheng = '组长' # 兜底昵称
# 6. 构造返回数据
response_data = {
'uid': inviter_user.UserUID,
'nicheng': inviter_nicheng,
'touxiang': inviter_user.Avatar or '' # 头像相对路径
}
return Response({
'code': 200,
'msg': '获取成功',
'data': response_data
})
def generate_tixian_id():
"""生成唯一提现订单号TX + 时间戳(13位) + 4位随机数"""
timestamp = str(int(time.time() * 1000))
rand = ''.join(random.choices(string.digits, k=4))
return f'TX{timestamp}{rand}'
class TixianShenqingV3View(APIView):
"""
自动提现收款接口(新流程第二步)
POST /yonghu/tixiansq
必须传 shenhe_danhao审核状态须为 6=待收款;申请时已扣款,本接口不再扣款
"""
permission_classes = [permissions.IsAuthenticated]
def post(self, request):
shenhe_danhao = (request.data.get('shenhe_danhao') or '').strip()
if shenhe_danhao:
return process_audit_collect(request)
return Response({
'code': 7,
'msg': '缺少审核单号,请先提交提现申请(/yonghu/zddksh)并等待审核通过后再收款',
})
class TixianCallbackV3View(APIView):
"""
微信转账结果回调接口(最终状态通知)
处理微信异步发送的转账成功/失败结果
"""
permission_classes = [] # 无需认证,签名验证保证安全
def post(self, request):
headers = request.headers
body = request.body.decode('utf-8')
# 1. 验证签名(多商户依次验签)
from utils.wechat_v3 import resolve_callback_club_id, decrypt_callback_ciphertext
callback_club_id = resolve_callback_club_id(headers, body)
if not callback_club_id:
return Response({'code': 'FAIL', 'message': '签名验证失败'}, status=status.HTTP_401_UNAUTHORIZED)
# 2. 解密数据
try:
data = json.loads(body)
resource = data.get('resource')
if not resource:
return Response({'code': 'FAIL', 'message': 'resource不存在'}, status=status.HTTP_400_BAD_REQUEST)
associated_data = resource.get('associated_data', '')
nonce = resource.get('nonce')
ciphertext = resource.get('ciphertext')
if not nonce or not ciphertext:
return Response({'code': 'FAIL', 'message': '解密字段缺失'}, status=status.HTTP_400_BAD_REQUEST)
plaintext = decrypt_callback_ciphertext(associated_data, nonce, ciphertext, club_id=callback_club_id)
callback_data = json.loads(plaintext)
except Exception as e:
logger.error(f"回调解密失败: {str(e)}", exc_info=True)
return Response({'code': 'FAIL', 'message': f'解密失败: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST)
# 3. 获取关键信息
out_bill_no = callback_data.get('out_bill_no') # 商户提现单号(即 tixian_id
transfer_status = callback_data.get('transfer_status') # SUCCESS / FAIL
wechat_transfer_no = callback_data.get('transfer_no') # 微信转账单号
fail_reason = callback_data.get('fail_reason', '')
if not out_bill_no:
return Response({'code': 'FAIL', 'message': 'out_bill_no缺失'}, status=status.HTTP_400_BAD_REQUEST)
# 4. 幂等处理只更新状态为0处理中的记录已最终状态不再处理
with transaction.atomic():
try:
auto_record = TixianAutoRecord.objects.select_for_update().get(tixian_id=out_bill_no)
except TixianAutoRecord.DoesNotExist:
# 记录不存在,视为已处理,返回成功
return Response({'code': 'SUCCESS', 'message': 'ok'})
# 如果已经是最新状态,直接返回成功(幂等)
if auto_record.zhuangtai != 0:
return Response({'code': 'SUCCESS', 'message': 'already processed'})
if transfer_status == 'SUCCESS':
mark_transfer_success(
auto_record,
wechat_transfer_no=wechat_transfer_no,
with_accounting=True,
)
else: # transfer_status == 'FAIL'
auto_record.zhuangtai = 2
auto_record.fail_reason = fail_reason or '微信转账失败'
auto_record.save(update_fields=['zhuangtai', 'fail_reason'])
release_collect_quota_for_record(auto_record)
if not auto_record.shenhe_danhao:
logger.error('回调失败但打款记录无审核单号 bill=%s', out_bill_no)
else:
try:
audit = TixianShenheJilu.objects.select_for_update().get(
shenhe_danhao=auto_record.shenhe_danhao
)
close_audit_wechat_failed(
audit, None, auto_record.fail_reason,
)
except TixianShenheJilu.DoesNotExist:
logger.warning(f'回调失败但审核记录不存在: {auto_record.shenhe_danhao}')
return Response({'code': 'SUCCESS', 'message': 'ok'})
class TixianQueRenAutoView(APIView):
"""
用户确认提现结果
POST /yonghu/tixianqr
以微信官方查单状态为准SUCCESS 落库成功FAIL/CANCELLED 关闭审核单+退款;进行中则提示等待
"""
permission_classes = [permissions.IsAuthenticated]
def post(self, request):
try:
tixian_id = request.data.get('tixian_id')
result = request.data.get('result') # 1成功 0失败/取消
if not tixian_id or result is None:
return Response({'code': 1, 'msg': '缺少参数'}, status=status.HTTP_400_BAD_REQUEST)
try:
result = int(result)
except ValueError:
return Response({'code': 2, 'msg': '参数格式错误'}, status=status.HTTP_400_BAD_REQUEST)
if result not in [0, 1]:
return Response({'code': 3, 'msg': 'result值无效'}, status=status.HTTP_400_BAD_REQUEST)
user_main = request.user
with transaction.atomic():
try:
auto_record = TixianAutoRecord.objects.select_for_update().get(
tixian_id=tixian_id,
yonghuid=user_main.UserUID,
)
except TixianAutoRecord.DoesNotExist:
return Response({'code': 4, 'msg': '提现记录不存在'}, status=status.HTTP_404_NOT_FOUND)
if result == 1:
if auto_record.zhuangtai == 1:
return Response({'code': 0, 'msg': '提现成功', 'data': None})
wx_data = query_wechat_transfer_bill(tixian_id)
if wx_data is None:
return Response({'code': 12, 'msg': '系统正在核对微信状态,请稍后再试'})
if wx_data.get('_not_found'):
return Response({'code': 12, 'msg': '收款处理中,请稍后再试'})
state = (wx_data.get('state') or '').upper()
if state in WX_STATE_SUCCESS:
mark_transfer_success(
auto_record,
wechat_transfer_no=wx_data.get('transfer_bill_no') or wx_data.get('transfer_no'),
with_accounting=True,
)
return Response({'code': 0, 'msg': '提现成功', 'data': None})
if state in WX_STATE_FAIL:
fail_reason = wx_data.get('fail_reason') or wx_data.get('close_reason') or state
auto_record.zhuangtai = 2
auto_record.fail_reason = str(fail_reason)[:500]
auto_record.save(update_fields=['zhuangtai', 'fail_reason'])
release_collect_quota_for_record(auto_record)
if auto_record.shenhe_danhao:
audit = TixianShenheJilu.objects.select_for_update().get(
shenhe_danhao=auto_record.shenhe_danhao,
)
close_audit_wechat_failed(audit, None, auto_record.fail_reason)
return Response({'code': 400, 'msg': '微信已确认转账失败,该笔提现已关闭,请联系客服处理'})
if state in WX_STATE_WAIT or state in WX_STATE_CANCELING:
return Response({'code': 12, 'msg': '收款处理中,请在微信中完成或稍后再试'})
return Response({'code': 12, 'msg': '系统正在核对微信状态,请稍后再试'})
# 用户报告取消/失败:以微信查单为准,不本地盲目标记
if auto_record.zhuangtai == 2:
return Response({'code': 0, 'msg': '提现已取消', 'data': None})
if auto_record.zhuangtai == 1:
return Response({'code': 0, 'msg': '提现成功', 'data': None})
wx_data = query_wechat_transfer_bill(tixian_id)
if wx_data is None:
return Response({'code': 12, 'msg': '系统正在核对微信状态,请稍后再试,切勿重复操作'})
if wx_data.get('_not_found'):
return Response({'code': 12, 'msg': '收款处理中,请稍后再试'})
state = (wx_data.get('state') or '').upper()
if state in WX_STATE_SUCCESS:
mark_transfer_success(
auto_record,
wechat_transfer_no=wx_data.get('transfer_bill_no') or wx_data.get('transfer_no'),
with_accounting=True,
)
return Response({'code': 0, 'msg': '提现成功', 'data': None})
if state in WX_STATE_FAIL:
fail_reason = wx_data.get('fail_reason') or wx_data.get('close_reason') or '用户取消收款'
auto_record.zhuangtai = 2
auto_record.fail_reason = str(fail_reason)[:500]
auto_record.save(update_fields=['zhuangtai', 'fail_reason'])
release_collect_quota_for_record(auto_record)
if auto_record.shenhe_danhao:
audit = TixianShenheJilu.objects.select_for_update().get(
shenhe_danhao=auto_record.shenhe_danhao,
)
close_audit_wechat_failed(audit, None, auto_record.fail_reason)
return Response({'code': 0, 'msg': '提现已关闭,请联系客服处理'})
if state in WX_STATE_WAIT or state in WX_STATE_CANCELING:
return Response({'code': 12, 'msg': '收款仍在进行中,请在微信中操作或稍后再试'})
return Response({'code': 12, 'msg': '系统正在核对微信状态,请稍后再试'})
except Exception as e:
logger.error(f'提现确认异常: {str(e)}', exc_info=True)
return Response({'code': 99, 'msg': '系统错误'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)