"""充值订单 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 products.czjilu_types import is_czjilu_fulfillable_status 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): """押金充值幂等履约:仅待履约状态(9/用户关单2)时入账;已付(3)直接跳过防多加。""" 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 not is_czjilu_fulfillable_status(order.zhuangtai): try: from jituan.services.szjilu_accounting import repair_wechat_income_if_missing repair_wechat_income_if_missing( Decimal(str(order.jine)), getattr(order, 'club_id', None) or CLUB_ID_DEFAULT, biz_ref=f'cz:{dingdan_id}', ) except Exception as exc: logger.error('押金补记收支失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True) 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 repair_wechat_income_if_missing repair_wechat_income_if_missing(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): """积分补充幂等履约:仅待履约状态(9/2)时入账。""" 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 not is_czjilu_fulfillable_status(order.zhuangtai): try: from jituan.services.szjilu_accounting import repair_wechat_income_if_missing repair_wechat_income_if_missing( Decimal(str(order.jine)), getattr(order, 'club_id', None) or CLUB_ID_DEFAULT, biz_ref=f'cz:{dingdan_id}', ) except Exception as exc: logger.error('积分补记收支失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True) 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 repair_wechat_income_if_missing repair_wechat_income_if_missing(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} @transaction.atomic def fulfill_fakuan(dingdan_id, paid_amount_yuan=None): """罚款支付幂等履约:仅 zhuangtai==9 时入账。""" import re from orders.models import Penalty from users.fadan_fenhong_utils import process_fadan_fenhong order = Czjilu.query.filter(dingdan_id=dingdan_id).select_for_update().first() if not order: raise CzjiluFulfillError('订单不存在', 'not_found') if order.leixing != 6: raise CzjiluFulfillError('订单类型不是罚款', 'type_error') if not is_czjilu_fulfillable_status(order.zhuangtai): try: from jituan.services.szjilu_accounting import repair_wechat_income_if_missing repair_wechat_income_if_missing( Decimal(str(order.jine)), getattr(order, 'club_id', None) or CLUB_ID_DEFAULT, biz_ref=f'cz:{dingdan_id}', ) except Exception as exc: logger.error('罚款补记收支失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True) 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') order.zhuangtai = 3 order.save(update_fields=['zhuangtai']) # 更新罚单状态为已缴纳(2)并触发分红 try: fadan = None m = re.search(r'#fadan:(\d+)', order.shuoming or '') if m: fadan_id_str = m.group(1) fadan = Penalty.query.filter( id=int(fadan_id_str), PenalizedUserID=order.yonghuid, Status__in=[1, 3], ).first() if not fadan: logger.warning( '罚款订单 %s shuoming 标记罚单 %s 未找到(被处罚者=%s, 状态需为1/3)', dingdan_id, fadan_id_str, order.yonghuid, ) else: logger.warning( '罚款订单 %s shuoming 无 #fadan:xxx 标记,回退金额匹配: %s', dingdan_id, order.shuoming, ) if not fadan: fadan = Penalty.query.filter( PenalizedUserID=order.yonghuid, FineAmount=order.jine, Status__in=[1, 3], ).order_by('-CreateTime').first() if fadan: logger.warning( '罚款订单 %s 回退金额匹配到罚单 %s(金额=%s)', dingdan_id, fadan.id, order.jine, ) if fadan: fadan.Status = 2 fadan.save(update_fields=['Status']) success, msg = process_fadan_fenhong(fadan.id) if not success: logger.error('罚单 %s 分红处理失败: %s', fadan.id, msg) else: logger.info('罚单 %s 分红处理完成: %s', fadan.id, msg) else: logger.error( '罚款订单 %s 未找到对应罚单,罚单状态未更新(被处罚者=%s, 金额=%s)', dingdan_id, order.yonghuid, order.jine, ) except Exception as exc: logger.error('罚款罚单状态更新失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True) try: from jituan.services.szjilu_accounting import repair_wechat_income_if_missing repair_wechat_income_if_missing(amount, getattr(order, 'club_id', None) or CLUB_ID_DEFAULT, 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 amount=%s', dingdan_id, amount) return {'already_done': False, 'dingdan_id': dingdan_id, 'zhuangtai': 3} @transaction.atomic def fulfill_kaohe(dingdan_id, paid_amount_yuan=None): """考核支付幂等履约:仅待履约状态(9/2)时入账。""" from rank.models import KaohePayTemp from rank.utils import create_shenhe_jilu_from_temp from users.business_models import User order = Czjilu.query.filter(dingdan_id=dingdan_id).select_for_update().first() if not order: raise CzjiluFulfillError('订单不存在', 'not_found') if order.leixing != 5: raise CzjiluFulfillError('订单类型不是考核', 'type_error') if not is_czjilu_fulfillable_status(order.zhuangtai): try: from jituan.services.szjilu_accounting import repair_wechat_income_if_missing repair_wechat_income_if_missing( Decimal(str(order.jine)), getattr(order, 'club_id', None) or CLUB_ID_DEFAULT, biz_ref=f'cz:{dingdan_id}', ) except Exception as exc: logger.error('考核补记收支失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True) 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') order.zhuangtai = 3 order.save(update_fields=['zhuangtai']) # 从临时表取业务参数并创建审核记录 try: temp = KaohePayTemp.query.get(dingdan_id=dingdan_id, yonghuid=order.yonghuid) user = User.query.get(UserUID=order.yonghuid) success, msg, record = create_shenhe_jilu_from_temp(user, temp) if not success: logger.error('创建审核记录失败 dingdan=%s: %s', dingdan_id, msg) order.zhuangtai = 10 # 支付成功但业务处理失败 order.save(update_fields=['zhuangtai']) else: logger.info('考核审核记录创建成功 dingdan=%s jilu_id=%s', dingdan_id, getattr(record, 'jilu_id', None)) except KaohePayTemp.DoesNotExist: logger.error('考核临时表无记录 dingdan=%s', dingdan_id) order.zhuangtai = 10 order.save(update_fields=['zhuangtai']) except Exception as exc: logger.error('考核审核记录创建异常 dingdan=%s: %s', dingdan_id, exc, exc_info=True) order.zhuangtai = 10 order.save(update_fields=['zhuangtai']) try: from jituan.services.szjilu_accounting import repair_wechat_income_if_missing repair_wechat_income_if_missing(amount, getattr(order, 'club_id', None) or CLUB_ID_DEFAULT, 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 amount=%s', dingdan_id, amount) return {'already_done': False, 'dingdan_id': dingdan_id, 'zhuangtai': order.zhuangtai} @transaction.atomic def fulfill_shangjia(dingdan_id, paid_amount_yuan=None): """商家充值幂等履约:仅待履约状态(9/2)时入账;已付(3)跳过防多加。""" from users.models import UserShangjia order = Czjilu.query.filter(dingdan_id=dingdan_id).select_for_update().first() if not order: raise CzjiluFulfillError('订单不存在', 'not_found') if order.leixing != 4: raise CzjiluFulfillError('订单类型不是商家充值', 'type_error') if not is_czjilu_fulfillable_status(order.zhuangtai): try: from jituan.services.szjilu_accounting import repair_wechat_income_if_missing repair_wechat_income_if_missing( Decimal(str(order.jine)), getattr(order, 'club_id', None) or CLUB_ID_DEFAULT, biz_ref=f'cz:{dingdan_id}', ) except Exception as exc: logger.error('商家充值补记收支失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True) 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: shangjia = UserShangjia.objects.select_for_update().get(user__UserUID=order.yonghuid) except UserShangjia.DoesNotExist: raise CzjiluFulfillError('商家不存在,无法充值', 'shangjia_not_found') shangjia.yue += amount shangjia.save(update_fields=['yue']) order.zhuangtai = 3 order.save(update_fields=['zhuangtai']) try: from jituan.services.szjilu_accounting import repair_wechat_income_if_missing repair_wechat_income_if_missing(amount, getattr(order, 'club_id', None) or CLUB_ID_DEFAULT, 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 amount=%s yue=%s', dingdan_id, amount, shangjia.yue) return {'already_done': False, 'dingdan_id': dingdan_id, 'zhuangtai': 3, 'yue': shangjia.yue} def confirm_czjilu_paid(dingdan_id, yonghuid, expected_leixing): """轮询确认:支付宝订单直接看本地状态,微信订单查微信 SUCCESS 后幂等履约。""" from django.core.cache import cache 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: try: from jituan.services.szjilu_accounting import repair_wechat_income_if_missing repair_wechat_income_if_missing( Decimal(str(order.jine)), getattr(order, 'club_id', None) or CLUB_ID_DEFAULT, biz_ref=f'cz:{dingdan_id}', ) except Exception as exc: logger.error('充值单补记收支失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True) return {'already_done': True, 'zhuangtai': 3} if not is_czjilu_fulfillable_status(order.zhuangtai): raise CzjiluFulfillError(f'订单状态异常: {order.zhuangtai}', 'status_error') # 主动查单确认支付状态 is_alipay = cache.get(f'alipay_pay_url_{dingdan_id}') is not None paid = Decimal(str(order.jine)) club_id = getattr(order, 'club_id', None) if is_alipay: # 支付宝订单:主动查单(alipay.trade.query),TRADE_SUCCESS/FINISHED 则触发履约 from jituan.services.alipay_pay import query_alipay_trade trade_status = query_alipay_trade(dingdan_id, club_id) if trade_status not in ('TRADE_SUCCESS', 'TRADE_FINISHED'): raise CzjiluFulfillError('支付宝支付结果尚未同步,请稍后再试', 'not_paid') else: # 微信订单:主动查单 from jituan.services.member_recharge import query_wechat_v2_trade_state trade_state, _ = query_wechat_v2_trade_state(dingdan_id, club_id) if trade_state != 'SUCCESS': # 兜底:cache 标记可能已过期(30分钟),尝试查支付宝确认 from jituan.services.alipay_pay import query_alipay_trade alipay_status = query_alipay_trade(dingdan_id, club_id) if alipay_status in ('TRADE_SUCCESS', 'TRADE_FINISHED'): logger.info('cache 过期但支付宝查单成功 dingdan=%s', dingdan_id) else: raise CzjiluFulfillError('微信侧尚未支付成功,请稍后再试', 'not_paid') # 查单确认支付成功,执行履约(fulfill_* 内行锁+仅 9/2→3,防多加) if expected_leixing == 1: from jituan.services.member_recharge import fulfill_member_recharge return fulfill_member_recharge(dingdan_id, paid_amount_yuan=paid, source='confirm') 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) if expected_leixing == 4: return fulfill_shangjia(dingdan_id, paid_amount_yuan=paid) if expected_leixing == 5: return fulfill_kaohe(dingdan_id, paid_amount_yuan=paid) if expected_leixing == 6: return fulfill_fakuan(dingdan_id, paid_amount_yuan=paid) raise CzjiluFulfillError('不支持的充值类型', 'type_error') def repair_czjilu_payment(dingdan_id): """客服补单:官方查单确认已付后,走同一套幂等履约(绝不多加)。""" try: order = Czjilu.query.get(dingdan_id=dingdan_id) except Czjilu.DoesNotExist: raise CzjiluFulfillError('订单不存在', 'not_found') return confirm_czjilu_paid(dingdan_id, order.yonghuid, expected_leixing=order.leixing)