fix: 充值履约幂等加固(会员/押金/积分),后台加会员club_id

This commit is contained in:
XingQue
2026-06-24 18:16:11 +08:00
parent 9cd7b6920a
commit 45123d5b6f
3 changed files with 219 additions and 157 deletions

View File

@@ -1256,7 +1256,7 @@ class KefuUpdateDashouView(APIView):
# ---------- 会员操作 ----------
elif caozuo == 'jia_huiyuan':
if '001ff' not in permissions:
if '001ff' not in permissions and '000001' not in permissions:
return Response({'code': 403, 'msg': '无权限给打手加会员'})
if not huiyuan_id:
return Response({'code': 400, 'msg': '缺少会员ID'})
@@ -1265,6 +1265,8 @@ class KefuUpdateDashouView(APIView):
huiyuan = Huiyuan.query.get(huiyuan_id=huiyuan_id)
except Huiyuan.DoesNotExist:
return Response({'code': 404, 'msg': '会员类型不存在'})
from jituan.services.club_user import get_user_club_id
player_club = get_user_club_id(dashou_user)
record, created = Huiyuangoumai.objects.select_for_update().get_or_create(
yonghu_id=dashou_id,
huiyuan_id=huiyuan_id,
@@ -1272,6 +1274,7 @@ class KefuUpdateDashouView(APIView):
'huiyuan_zhuangtai': 1,
'jieshao': huiyuan.jieshao,
'daoqi_time': timezone.now() + timedelta(days=days),
'club_id': player_club,
}
)
if not created:

View File

@@ -0,0 +1,179 @@
"""充值订单 czjilu 履约(押金/积分),与 member_recharge 同一套幂等原则。"""
from __future__ import annotations
import logging
from decimal import Decimal
from django.db import transaction
from backend.utils import update_guanshi_daily_by_action
from jituan.constants import CLUB_ID_DEFAULT
from jituan.services.club_penalty import resolve_gsfenhong_club_id
from orders.models import CommissionRate
from products.models import Czjilu, Gsfenhong
from users.models import UserDashou, UserGuanshi
logger = logging.getLogger(__name__)
INTEGRAL_PRICE = Decimal('5.00')
MAX_INTEGRAL = 10
INTEGRAL_INCREMENT = 5
class CzjiluFulfillError(Exception):
def __init__(self, message, code='error'):
super().__init__(message)
self.code = code
@transaction.atomic
def fulfill_yajin_recharge(dingdan_id, paid_amount_yuan=None):
"""押金充值幂等履约:仅 zhuangtai==9 时入账。"""
order = Czjilu.query.filter(dingdan_id=dingdan_id).select_for_update().first()
if not order:
raise CzjiluFulfillError('订单不存在', 'not_found')
if order.leixing != 2:
raise CzjiluFulfillError('订单类型不是押金', 'type_error')
if order.zhuangtai != 9:
return {'already_done': True, 'dingdan_id': dingdan_id, 'zhuangtai': order.zhuangtai}
amount = Decimal(str(order.jine))
if paid_amount_yuan is not None:
paid = Decimal(str(paid_amount_yuan))
if abs(paid - amount) > Decimal('0.01'):
raise CzjiluFulfillError(f'支付金额{paid}与订单{amount}不一致', 'amount_mismatch')
try:
dashou = UserDashou.objects.select_for_update().get(user__UserUID=order.yonghuid)
except UserDashou.DoesNotExist:
raise CzjiluFulfillError('打手不存在,无法充值押金', 'dashou_not_found')
dashou.yajin += amount
dashou.save(update_fields=['yajin'])
order.zhuangtai = 3
order.save(update_fields=['zhuangtai'])
club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
try:
from jituan.services.szjilu_accounting import record_wechat_income_once
record_wechat_income_once(amount, club_id, biz_ref=f'cz:{dingdan_id}')
except Exception as exc:
logger.error('押金收支统计失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True)
try:
_apply_yajin_fenhong(order, dashou, amount)
except Exception as exc:
logger.error('押金分红失败但押金已到账 dingdan=%s: %s', dingdan_id, exc, exc_info=True)
logger.info('押金履约完成 dingdan=%s amount=%s', dingdan_id, amount)
return {'already_done': False, 'dingdan_id': dingdan_id, 'zhuangtai': 3, 'yajin': dashou.yajin}
def _apply_yajin_fenhong(order, dashou, amount):
if Gsfenhong.query.filter(dingdan_id=order.dingdan_id).exists():
return
guanshi_id = dashou.yaoqingren
if not guanshi_id:
return
lilu_obj = CommissionRate.query.filter(Platform='12').first()
rate = lilu_obj.Rate if lilu_obj and lilu_obj.Rate is not None else Decimal('0')
if rate <= 0:
return
fenhong_jine = (amount * rate).quantize(Decimal('0.01'))
if fenhong_jine <= 0:
return
guanshi = UserGuanshi.objects.select_for_update().get(user__UserUID=guanshi_id)
guanshi.yue += fenhong_jine
guanshi.chongzhifenrun += fenhong_jine
guanshi.save(update_fields=['yue', 'chongzhifenrun'])
user_main = dashou.user
Gsfenhong.query.create(
dingdan_id=order.dingdan_id,
guanshi=guanshi_id,
dashouid=order.yonghuid,
shuoming='押金分红',
fenhong=fenhong_jine,
avatar=user_main.Avatar,
nicheng=dashou.nicheng or '未知打手',
fenhong_leixing=2,
club_id=resolve_gsfenhong_club_id(
dingdan_id=order.dingdan_id, dashouid=order.yonghuid, czjilu=order,
),
)
try:
update_guanshi_daily_by_action(yonghuid=guanshi_id, action=4, amount=fenhong_jine)
except Exception as exc:
logger.error('押金分红日统计失败: %s', exc)
@transaction.atomic
def fulfill_jifen_recharge(dingdan_id, paid_amount_yuan=None):
"""积分补充幂等履约:仅 zhuangtai==9 时入账。"""
order = Czjilu.query.filter(dingdan_id=dingdan_id).select_for_update().first()
if not order:
raise CzjiluFulfillError('订单不存在', 'not_found')
if order.leixing != 3:
raise CzjiluFulfillError('订单类型不是积分', 'type_error')
if order.zhuangtai != 9:
return {'already_done': True, 'dingdan_id': dingdan_id, 'zhuangtai': order.zhuangtai}
amount = Decimal(str(order.jine))
if paid_amount_yuan is not None:
paid = Decimal(str(paid_amount_yuan))
if abs(paid - amount) > Decimal('0.01'):
raise CzjiluFulfillError(f'支付金额{paid}与订单{amount}不一致', 'amount_mismatch')
if abs(paid - INTEGRAL_PRICE) > Decimal('0.01'):
raise CzjiluFulfillError('积分订单金额必须为5元', 'amount_mismatch')
try:
dashou = UserDashou.objects.select_for_update().get(user__UserUID=order.yonghuid)
except UserDashou.DoesNotExist:
raise CzjiluFulfillError('打手不存在,无法补充积分', 'dashou_not_found')
if dashou.jifen < MAX_INTEGRAL:
dashou.jifen = min(dashou.jifen + INTEGRAL_INCREMENT, MAX_INTEGRAL)
dashou.save(update_fields=['jifen'])
order.zhuangtai = 3
order.save(update_fields=['zhuangtai'])
club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
try:
from jituan.services.szjilu_accounting import record_wechat_income_once
record_wechat_income_once(amount, club_id, biz_ref=f'cz:{dingdan_id}')
except Exception as exc:
logger.error('积分收支统计失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True)
logger.info('积分履约完成 dingdan=%s jifen=%s', dingdan_id, dashou.jifen)
return {'already_done': False, 'dingdan_id': dingdan_id, 'zhuangtai': 3, 'jifen': dashou.jifen}
def confirm_czjilu_paid(dingdan_id, yonghuid, expected_leixing):
"""轮询确认:查微信 SUCCESS 后幂等履约。"""
from jituan.services.member_recharge import query_wechat_v2_trade_state
try:
order = Czjilu.query.get(dingdan_id=dingdan_id, yonghuid=yonghuid)
except Czjilu.DoesNotExist:
raise CzjiluFulfillError('订单不存在或不属于当前用户', 'not_found')
if order.leixing != expected_leixing:
raise CzjiluFulfillError('订单类型不匹配', 'type_error')
if order.zhuangtai == 3:
return {'already_done': True, 'zhuangtai': 3}
if order.zhuangtai != 9:
raise CzjiluFulfillError(f'订单状态异常: {order.zhuangtai}', 'status_error')
trade_state, _ = query_wechat_v2_trade_state(dingdan_id, getattr(order, 'club_id', None))
if trade_state != 'SUCCESS':
raise CzjiluFulfillError('微信侧尚未支付成功,请稍后再试', 'not_paid')
paid = Decimal(str(order.jine))
if expected_leixing == 2:
return fulfill_yajin_recharge(dingdan_id, paid_amount_yuan=paid)
if expected_leixing == 3:
return fulfill_jifen_recharge(dingdan_id, paid_amount_yuan=paid)
raise CzjiluFulfillError('不支持的充值类型', 'type_error')

View File

@@ -615,111 +615,13 @@ class YajinHuitiao(View):
logger.error(f"金额不一致: 订单金额{order_amount_yuan}元, 支付金额{wechat_amount_yuan}")
return self.wechat_response('FAIL', '金额不一致')
# 7. 更新订单状态为已支付3
order.zhuangtai = 3
order.save(update_fields=['zhuangtai'])
logger.info(f"订单状态更新成功: {order.dingdan_id} -> 3")
# 8. 更新打手押金
from jituan.services.czjilu_fulfill import fulfill_yajin_recharge, CzjiluFulfillError
try:
# 通过用户ID查找打手高效查询
dashou = UserDashou.query.get(user__UserUID=order.yonghuid)
fulfill_yajin_recharge(out_trade_no, paid_amount_yuan=wechat_amount_yuan)
except CzjiluFulfillError as exc:
logger.error('押金回调履约失败 %s: %s', out_trade_no, exc)
return self.wechat_response('FAIL', str(exc))
# 增加押金使用Decimal确保精度
dashou.yajin += Decimal(str(order_amount_yuan))
dashou.save(update_fields=['yajin'])
logger.info(f"打手押金更新成功: 用户{order.yonghuid}, 增加{order_amount_yuan}元, 新押金{dashou.yajin}")
# 8b. 押金分红(仅微信确认到账后;费率 CommissionRate Platform=12
try:
guanshi_id = dashou.yaoqingren
if not guanshi_id:
logger.info(f"押金分红跳过: 打手{order.yonghuid}无邀请管事")
elif Gsfenhong.query.filter(dingdan_id=order.dingdan_id).exists():
logger.info(f"押金分红已处理: 订单{order.dingdan_id}")
else:
lilu_obj = CommissionRate.query.filter(Platform='12').first()
rate = lilu_obj.Rate if lilu_obj and lilu_obj.Rate is not None else Decimal('0')
if rate <= 0:
logger.info(f"押金分红跳过: 费率未配置或为0")
else:
recharge_amount = Decimal(str(order_amount_yuan))
fenhong_jine = (recharge_amount * rate).quantize(Decimal('0.01'))
if fenhong_jine > 0:
with transaction.atomic():
guanshi = UserGuanshi.objects.select_for_update().get(
user__UserUID=guanshi_id
)
guanshi.yue += fenhong_jine
guanshi.chongzhifenrun += fenhong_jine
guanshi.save(update_fields=['yue', 'chongzhifenrun'])
user_main = dashou.user
Gsfenhong.query.create(
dingdan_id=order.dingdan_id,
guanshi=guanshi_id,
dashouid=order.yonghuid,
shuoming='押金分红',
fenhong=fenhong_jine,
avatar=user_main.Avatar,
nicheng=dashou.nicheng or '未知打手',
fenhong_leixing=2,
club_id=resolve_gsfenhong_club_id(
dingdan_id=order.dingdan_id,
dashouid=order.yonghuid,
czjilu=order,
),
)
logger.info(
f"押金分红成功: 订单{order.dingdan_id}, 管事{guanshi_id}, "
f"打手{order.yonghuid}, 金额{fenhong_jine}"
)
try:
update_guanshi_daily_by_action(
yonghuid=guanshi_id,
action=4,
amount=fenhong_jine,
)
except Exception as e:
logger.error(f"押金分红管事日统计更新失败: {e}")
except UserGuanshi.DoesNotExist:
logger.warning(f"押金分红跳过: 管事{guanshi_id}不存在")
except Exception as e:
logger.error(f"押金分红处理失败: {str(e)}", exc_info=True)
except UserDashou.DoesNotExist:
# 如果找不到打手,记录错误,但依然返回成功(避免微信重复回调)
logger.error(f"打手信息不存在: 用户{order.yonghuid}")
return self.wechat_response('SUCCESS', 'OK')
except Exception as e:
logger.error(f"更新打手押金失败: {str(e)}", exc_info=True)
return self.wechat_response('FAIL', '更新押金失败')
# ==== 在返回成功响应之前添加这段代码 ====
# 更新收支记录表
try:
# 获取订单金额确保是Decimal类型
jine_decimal = Decimal(str(order.jine)) if not isinstance(order.jine, Decimal) else order.jine
from jituan.services.szjilu_accounting import record_wechat_income_once
record_wechat_income_once(
jine_decimal,
getattr(order, 'club_id', None),
biz_ref=f'cz:{order.dingdan_id}',
)
logger.info(f"收支记录更新成功: 订单{order.dingdan_id}, 金额{jine_decimal}")
except Exception as e:
# 收支记录更新失败不影响主流程,只记录日志
logger.error(f"更新收支记录失败: {str(e)}", exc_info=True)
# ==== 添加结束 ====
# 9. 返回成功响应给微信
logger.info(f"押金支付回调处理完成: {out_trade_no}")
return self.wechat_response('SUCCESS', 'OK')
@@ -872,7 +774,19 @@ class YajinLunxun(APIView):
'message': '订单不存在或不属于当前用户'
}, status=status.HTTP_400_BAD_REQUEST)
# 3. 检查订单状态是否为已支付3
# 3. 未支付则查微信并幂等履约
if order.zhuangtai == 9:
from jituan.services.czjilu_fulfill import confirm_czjilu_paid, CzjiluFulfillError
try:
confirm_czjilu_paid(dingdanid, request.user.UserUID, expected_leixing=2)
order = Czjilu.query.get(dingdan_id=dingdanid, yonghuid=request.user.UserUID)
except CzjiluFulfillError as exc:
logger.info(f"押金确认未就绪 {dingdanid}: {exc}")
return Response({
'code': 400,
'message': str(exc),
}, status=status.HTTP_400_BAD_REQUEST)
if order.zhuangtai != 3:
logger.info(f"订单{dingdanid}尚未支付完成,当前状态: {order.zhuangtai}")
return Response({
@@ -1205,59 +1119,13 @@ class JifenHuitiao(View):
logger.error(f"积分金额错误: 订单金额{order_amount_yuan}元, 支付金额{wechat_amount_yuan}")
return self.wechat_response('FAIL', '金额错误')
# ==== 在返回成功响应之前添加这段代码 ====
# 更新收支记录表
from jituan.services.czjilu_fulfill import fulfill_jifen_recharge, CzjiluFulfillError
try:
jine_decimal = Decimal(str(order.jine)) if not isinstance(order.jine, Decimal) else order.jine
from jituan.services.szjilu_accounting import record_wechat_income_once
record_wechat_income_once(
jine_decimal,
getattr(order, 'club_id', None),
biz_ref=f'cz:{order.dingdan_id}',
)
fulfill_jifen_recharge(out_trade_no, paid_amount_yuan=wechat_amount_yuan)
except CzjiluFulfillError as exc:
logger.error('积分回调履约失败 %s: %s', out_trade_no, exc)
return self.wechat_response('FAIL', str(exc))
logger.info(f"收支记录更新成功: 订单{order.dingdan_id}, 金额{jine_decimal}")
except Exception as e:
# 收支记录更新失败不影响主流程,只记录日志
logger.error(f"更新收支记录失败: {str(e)}", exc_info=True)
# ==== 添加结束 ====
# 7. 更新订单状态为已支付3
order.zhuangtai = 3
order.save(update_fields=['zhuangtai'])
logger.info(f"积分订单状态更新成功: {order.dingdan_id} -> 3")
# 8. 更新打手积分
try:
# 通过用户ID查找打手
dashou = UserDashou.query.get(user__UserUID=order.yonghuid)
# 检查积分是否小于10
if dashou.jifen < 10:
# 增加5积分但不超过10
new_jifen = dashou.jifen + 5
dashou.jifen = min(new_jifen, 10) # 确保不超过10
dashou.save(update_fields=['jifen'])
logger.info(f"打手积分更新成功: 用户{order.yonghuid}, 增加5积分, 新积分{dashou.jifen}")
else:
logger.warning(f"打手积分已满: 用户{order.yonghuid}, 当前积分{dashou.jifen}")
except UserDashou.DoesNotExist:
logger.error(f"打手信息不存在: 用户{order.yonghuid}")
return self.wechat_response('SUCCESS', 'OK')
except Exception as e:
logger.error(f"更新打手积分失败: {str(e)}", exc_info=True)
return self.wechat_response('FAIL', '更新积分失败')
# 9. 返回成功响应给微信
logger.info(f"积分支付回调处理完成: {out_trade_no}")
return self.wechat_response('SUCCESS', 'OK')
@@ -1421,7 +1289,19 @@ class JifenLunxun(APIView):
'message': '订单类型错误,不是积分订单'
}, status=status.HTTP_400_BAD_REQUEST)
# 4. 检查订单状态是否为已支付3
# 4. 未支付则查微信并幂等履约
if order.zhuangtai == 9:
from jituan.services.czjilu_fulfill import confirm_czjilu_paid, CzjiluFulfillError
try:
confirm_czjilu_paid(dingdanid, request.user.UserUID, expected_leixing=3)
order = Czjilu.query.get(dingdan_id=dingdanid, yonghuid=request.user.UserUID)
except CzjiluFulfillError as exc:
logger.info(f"积分确认未就绪 {dingdanid}: {exc}")
return Response({
'code': 400,
'message': str(exc),
}, status=status.HTTP_400_BAD_REQUEST)
if order.zhuangtai != 3:
logger.info(f"积分订单{dingdanid}尚未支付完成,当前状态: {order.zhuangtai}")
return Response({