fix: 会员分红按购买次序与duoci_fenhong计,严格俱乐部配置

This commit is contained in:
XingQue committed 2026-06-24 16:47:28 +08:00
1 parent 00d2939457
commit 1c48779ff0
3 files changed
+500 -331

No files matched your search

+15 -17
View File
@@ -1,34 +1,32 @@
"""各俱乐部价格 / 利率覆盖(无覆盖时回落全局 huiyuan / lilubiao)。"""
"""各俱乐部价格 / 利率(严格按 club_id,不读全局 huiyuan 会员表)。"""
from decimal import Decimal
from jituan.constants import CLUB_ID_DEFAULT
from jituan.models import ClubHuiyuanPrice, ClubLilubiao
from orders.models import CommissionRate
from products.models import Huiyuan
def get_club_huiyuan_price_row(club_id, huiyuan_id, require_enabled=True):
club_id = club_id or CLUB_ID_DEFAULT
qs = ClubHuiyuanPrice.query.filter(club_id=club_id, huiyuan_id=huiyuan_id)
if require_enabled:
qs = qs.filter(is_enabled=True)
return qs.first()
def get_huiyuan_price(club_id, huiyuan_id, huiyuan_obj=None):
club_id = club_id or CLUB_ID_DEFAULT
row = ClubHuiyuanPrice.query.filter(
club_id=club_id, huiyuan_id=huiyuan_id, is_enabled=True,
).first()
if row:
"""会员售价:仅 club_huiyuan_price。"""
row = get_club_huiyuan_price_row(club_id, huiyuan_id, require_enabled=True)
if row and row.jiage is not None:
return Decimal(str(row.jiage))
obj = huiyuan_obj or Huiyuan.query.filter(huiyuan_id=huiyuan_id).first()
if obj:
return Decimal(str(obj.jiage))
return Decimal('0')
def get_huiyuan_fenchong(club_id, huiyuan_id, huiyuan_obj=None):
"""返回 (guanshifc, zuzhangfc)。"""
club_id = club_id or CLUB_ID_DEFAULT
row = ClubHuiyuanPrice.query.filter(club_id=club_id, huiyuan_id=huiyuan_id).first()
"""首次购买默认分成:仅 club_huiyuan_price(管事/组长各公司独立配置)。"""
row = get_club_huiyuan_price_row(club_id, huiyuan_id, require_enabled=False)
if row:
return Decimal(str(row.guanshifc)), Decimal(str(row.zuzhangfc))
obj = huiyuan_obj or Huiyuan.query.filter(huiyuan_id=huiyuan_id).first()
if obj:
return Decimal(str(obj.guanshifc)), Decimal(str(getattr(obj, 'zuzhangfc', 0) or 0))
return Decimal(str(row.guanshifc or 0)), Decimal(str(row.zuzhangfc or 0))
return Decimal('0'), Decimal('0')
+448
View File
@@ -0,0 +1,448 @@
"""
会员微信充值履约(回调 / 前端确认共用,幂等)。
分红策略(定稿):
- 第几次购买 cishu:本俱乐部该打手该会员,已支付成功 czjilu 笔数(不含本单)+ 1。
与换管事无关,不看 gsfenhong,不看管事被分了几次。
- cishu=1:先 duoci_fenhong(club_id+受益人+会员+cishu);无则 club_huiyuan_price 默认分成。
- cishu>=2:仅 duoci_fenhong;无配置则不分(不读管事/组长用户表二次或永久分红字段)。
- 价格与默认分成:仅 club_huiyuan_price + order.club_id,不读全局 huiyuan 表。
"""
from __future__ import annotations
import hashlib
import logging
import random
import string
from datetime import timedelta
from decimal import Decimal
import defusedxml.ElementTree as ET
import requests
from django.db import transaction
from django.utils import timezone
from backend.utils import (
update_guanshi_daily_by_action,
update_guanshi_xufei_daily,
update_zuzhang_daily_by_action,
)
from jituan.constants import CLUB_ID_DEFAULT
from jituan.models import ClubHuiyuanPrice
from jituan.services.club_config import get_huiyuan_fenchong
from jituan.services.club_penalty import resolve_gsfenhong_club_id
from jituan.services.club_user import get_user_club_id
from jituan.services.wechat_pay import club_id_from_czjilu, get_wechat_v2_config
from orders.utils import update_daily_income
from products.models import Czjilu, DuociFenhong, Gsfenhong, Huiyuan, Huiyuangoumai
from users.business_models import User
from users.models import UserDashou, UserGuanshi, UserZuzhang
logger = logging.getLogger(__name__)
MEMBER_FENHONG_LEIXING = 1
class MemberRechargeError(Exception):
def __init__(self, message, code='error'):
super().__init__(message)
self.code = code
def club_huiyuan_sellable(club_id, huiyuan_id):
"""仅 club_huiyuan_price 可售;未配置则不可购买。"""
club_id = club_id or CLUB_ID_DEFAULT
row = ClubHuiyuanPrice.query.filter(
club_id=club_id, huiyuan_id=huiyuan_id, is_enabled=True,
).first()
if row and row.jiage and Decimal(str(row.jiage)) > 0:
return True, Decimal(str(row.jiage))
return False, Decimal('0')
def resolve_member_purchase_cishu(yonghuid, huiyuan_id, current_dingdan_id, club_id):
"""
打手第几次购买该会员(= duoci_fenhong.cishu)。
同俱乐部 czjilu 已支付笔数(不含本单)+ 1。
"""
club_id = club_id or CLUB_ID_DEFAULT
prior_paid = Czjilu.query.filter(
yonghuid=yonghuid,
huiyuan_id=huiyuan_id,
leixing=1,
zhuangtai=3,
club_id=club_id,
).exclude(dingdan_id=current_dingdan_id).count()
return prior_paid + 1
def _lookup_duoci_fenhong(club_id, huiyuan_id, beneficiary_id, cishu, field):
"""有行则返回金额(含 0);无行返回 None 表示未配置。"""
try:
row = DuociFenhong.query.get(
club_id=club_id or CLUB_ID_DEFAULT,
huiyuan=huiyuan_id,
yonghuid=beneficiary_id,
cishu=cishu,
)
return Decimal(str(getattr(row, field) or 0))
except DuociFenhong.DoesNotExist:
return None
def calc_guanshi_member_fenhong(
huiyuan_id, guanshi_id, cishu, club_id, default_guanshi_fc,
):
"""cishu>=2 只认 duoci_fenhong;cishu=1 可回落 club 默认分成。"""
club_id = club_id or CLUB_ID_DEFAULT
custom = _lookup_duoci_fenhong(club_id, huiyuan_id, guanshi_id, cishu, 'guanshi_fenhong')
if custom is not None:
return custom if custom > 0 else None
if cishu == 1:
base = default_guanshi_fc if default_guanshi_fc is not None else Decimal('0')
return base if base > 0 else None
return None
def calc_zuzhang_member_fenhong(
huiyuan_id, zuzhang_id, cishu, club_id, default_zuzhang_fc,
):
if not zuzhang_id:
return None
club_id = club_id or CLUB_ID_DEFAULT
custom = _lookup_duoci_fenhong(club_id, huiyuan_id, zuzhang_id, cishu, 'zuzhang_fenhong')
if custom is not None:
return custom if custom > 0 else None
if cishu == 1:
base = default_zuzhang_fc if default_zuzhang_fc is not None else Decimal('0')
return base if base > 0 else None
return None
def _apply_member_fenhong(order, huiyuan, purchase_cishu):
if Gsfenhong.query.filter(dingdan_id=order.dingdan_id).exists():
logger.info('会员分红已处理,跳过: %s', order.dingdan_id)
return
dashou = UserDashou.query.get(user__UserUID=order.yonghuid)
guanshi_id = dashou.yaoqingren
if not guanshi_id:
logger.info('打手%s无邀请管事,跳过会员分红', order.yonghuid)
return
try:
guanshi = UserGuanshi.query.get(user__UserUID=guanshi_id)
except UserGuanshi.DoesNotExist:
logger.warning('管事不存在: %s', guanshi_id)
return
zuzhang_id = guanshi.yaoqingren
zuzhang = None
if zuzhang_id:
try:
zuzhang = UserZuzhang.query.get(user__UserUID=zuzhang_id)
except UserZuzhang.DoesNotExist:
logger.warning('组长不存在: %s', zuzhang_id)
club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
huiyuan_id = huiyuan.huiyuan_id
cishu = purchase_cishu
guanshi_fc, zuzhang_fc = get_huiyuan_fenchong(club_id, huiyuan_id)
order_amount = Decimal(str(order.jine))
guanshi_fenhong = calc_guanshi_member_fenhong(
huiyuan_id, guanshi_id, cishu, club_id, guanshi_fc,
)
guanshi_fenhong = guanshi_fenhong if guanshi_fenhong is not None else Decimal('0')
zuzhang_fenhong = calc_zuzhang_member_fenhong(
huiyuan_id, zuzhang_id, cishu, club_id, zuzhang_fc,
)
zuzhang_fenhong = zuzhang_fenhong if zuzhang_fenhong is not None else Decimal('0')
total = guanshi_fenhong + zuzhang_fenhong
if total > order_amount:
logger.warning(
'分红总额%s超订单%s,组长置0', total, order_amount,
)
zuzhang_fenhong = Decimal('0')
total = guanshi_fenhong
if total > order_amount:
logger.warning('管事分红%s超订单,置0', guanshi_fenhong)
guanshi_fenhong = Decimal('0')
if guanshi_fenhong <= 0 and zuzhang_fenhong <= 0:
logger.info(
'本次无会员分红 dingdan=%s club=%s cishu=%s',
order.dingdan_id, club_id, cishu,
)
return
user_main = dashou.user
if guanshi_fenhong > 0:
guanshi.chongzhifenrun += guanshi_fenhong
guanshi.yue += guanshi_fenhong
guanshi.jinrichongzhi += 1
guanshi.jinyuechongzhi += 1
guanshi.save(update_fields=[
'chongzhifenrun', 'yue', 'jinrichongzhi', 'jinyuechongzhi',
])
if zuzhang_fenhong > 0 and zuzhang:
zuzhang.fenyong_zonge += zuzhang_fenhong
zuzhang.ketixian_jine += zuzhang_fenhong
zuzhang.jinri_fenyong += zuzhang_fenhong
zuzhang.jinyue_fenyong += zuzhang_fenhong
zuzhang.save(update_fields=[
'fenyong_zonge', 'ketixian_jine', 'jinri_fenyong', 'jinyue_fenyong',
])
Gsfenhong.query.create(
dingdan_id=order.dingdan_id,
guanshi=guanshi_id,
dashouid=order.yonghuid,
shuoming=order.shuoming,
fenhong=guanshi_fenhong,
avatar=user_main.Avatar,
nicheng=dashou.nicheng or '未知打手',
zuzhang_id=zuzhang_id if zuzhang_fenhong > 0 else None,
zuzhang_fenhong=zuzhang_fenhong if zuzhang_fenhong > 0 else None,
huiyuan_id=huiyuan_id,
fenhong_leixing=MEMBER_FENHONG_LEIXING,
club_id=resolve_gsfenhong_club_id(dingdan_id=order.dingdan_id, czjilu=order),
)
if guanshi_fenhong > 0:
try:
if cishu == 1:
update_guanshi_daily_by_action(
yonghuid=guanshi_id, action=2, amount=guanshi_fenhong,
)
else:
update_guanshi_xufei_daily(
yonghuid=guanshi_id, xufei_jine=guanshi_fenhong,
)
except Exception as exc:
logger.error('管事日统计更新失败: %s', exc)
if zuzhang_fenhong > 0 and zuzhang_id:
try:
update_zuzhang_daily_by_action(
yonghuid=zuzhang_id, action=2, amount=zuzhang_fenhong,
)
except Exception as exc:
logger.error('组长日统计更新失败: %s', exc)
logger.info(
'会员分红完成 dingdan=%s club=%s cishu=%s 管事=%s 组长=%s',
order.dingdan_id, club_id, cishu, guanshi_fenhong, zuzhang_fenhong,
)
def _apply_huiyuan_goumai(yonghuid, huiyuan_id, huiyuan, club_id):
"""返回 (record, is_first_purchase_of_member, is_first_buy_any_member)。"""
existing = Huiyuangoumai.query.filter(
yonghu_id=yonghuid, huiyuan_id=huiyuan_id,
).first()
is_first_purchase = existing is None
is_first_buy_any = False
if existing:
existing.daoqi_time += timedelta(days=30)
existing.huiyuan_zhuangtai = 1
existing.save(update_fields=['daoqi_time', 'huiyuan_zhuangtai', 'UpdateTime'])
return existing, False, False
other = Huiyuangoumai.query.filter(yonghu_id=yonghuid).exclude(
huiyuan_id=huiyuan_id,
).count()
if other == 0:
is_first_buy_any = True
record = Huiyuangoumai.query.create(
yonghu_id=yonghuid,
huiyuan_id=huiyuan_id,
jieshao=huiyuan.jieshao,
huiyuan_zhuangtai=1,
daoqi_time=timezone.now() + timedelta(days=30),
club_id=club_id or get_user_club_id(User.query.filter(UserUID=yonghuid).first()),
)
return record, is_first_purchase, is_first_buy_any
def _add_first_buy_jifen(yonghuid):
try:
dashou = UserDashou.query.get(user__UserUID=yonghuid)
dashou.jifen += 10
dashou.save(update_fields=['jifen'])
except UserDashou.DoesNotExist:
logger.warning('首次购会员加积分失败,打手不存在: %s', yonghuid)
@transaction.atomic
def fulfill_member_recharge(dingdan_id, paid_amount_yuan=None, source='callback'):
"""
幂等履约:仅当 czjilu.zhuangtai==9 时执行。
已处理 (zhuangtai!=9) 直接返回 already_done=True。
"""
locked = Czjilu.query.filter(dingdan_id=dingdan_id).select_for_update()
order = locked.first()
if not order:
raise MemberRechargeError('订单不存在', 'not_found')
if order.leixing != 1:
raise MemberRechargeError('订单类型不是会员充值', 'type_error')
if order.zhuangtai != 9:
logger.info(
'会员订单已处理,跳过履约 dingdan=%s zhuangtai=%s source=%s',
dingdan_id, order.zhuangtai, source,
)
return {
'already_done': True,
'dingdan_id': dingdan_id,
'zhuangtai': order.zhuangtai,
}
if paid_amount_yuan is not None:
paid = Decimal(str(paid_amount_yuan))
order_amt = Decimal(str(order.jine))
if abs(paid - order_amt) > Decimal('0.01'):
raise MemberRechargeError(
f'支付金额{paid}与订单{order_amt}不一致', 'amount_mismatch',
)
huiyuan = Huiyuan.query.get(huiyuan_id=order.huiyuan_id)
club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
is_first_purchase_of_member = not Huiyuangoumai.query.filter(
yonghu_id=order.yonghuid,
huiyuan_id=order.huiyuan_id,
).exists()
# 必须在改 zhuangtai 之前算清第几次购买
purchase_cishu = resolve_member_purchase_cishu(
order.yonghuid, order.huiyuan_id, order.dingdan_id, club_id,
)
jine_decimal = Decimal(str(order.jine))
try:
update_daily_income(jine_decimal, club_id)
from jituan.services.szjilu_accounting import apply_szjilu_income
apply_szjilu_income(jine_decimal, club_id)
except Exception as exc:
logger.error('会员收支统计更新失败: %s', exc, exc_info=True)
order.zhuangtai = 3
order.save(update_fields=['zhuangtai'])
huiyuan.goumai_cishu = (huiyuan.goumai_cishu or 0) + 1
huiyuan.save(update_fields=['goumai_cishu'])
goumai_record, _, is_first_buy_any = _apply_huiyuan_goumai(
order.yonghuid, order.huiyuan_id, huiyuan, club_id,
)
if is_first_buy_any:
_add_first_buy_jifen(order.yonghuid)
_apply_member_fenhong(order, huiyuan, purchase_cishu)
logger.info(
'会员充值履约完成 dingdan=%s source=%s club=%s purchase_cishu=%s first_member=%s',
dingdan_id, source, club_id, purchase_cishu, is_first_purchase_of_member,
)
return {
'already_done': False,
'dingdan_id': dingdan_id,
'zhuangtai': 3,
'purchase_cishu': purchase_cishu,
'is_first_purchase_of_member': is_first_purchase_of_member,
'daoqi_time': goumai_record.daoqi_time,
'huiyuan_id': order.huiyuan_id,
}
def query_wechat_v2_trade_state(out_trade_no, club_id=None):
"""微信 V2 查单,返回 (trade_state, transaction_id) 或 (None, None)。"""
club_id = club_id or club_id_from_czjilu(out_trade_no)
cfg = get_wechat_v2_config(club_id)
nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32))
params = {
'appid': cfg['appid'],
'mch_id': cfg['mch_id'],
'out_trade_no': out_trade_no,
'nonce_str': nonce_str,
}
string_a = '&'.join(f'{k}={params[k]}' for k in sorted(params.keys()))
sign = hashlib.md5(f'{string_a}&key={cfg["key"]}'.encode()).hexdigest().upper()
params['sign'] = sign
xml_body = ''.join(f'<{k}>{params[k]}</{k}>' for k in params)
xml_data = f'<xml>{xml_body}</xml>'
try:
resp = requests.post(
'https://api.mch.weixin.qq.com/pay/orderquery',
data=xml_data.encode('utf-8'),
headers={'Content-Type': 'application/xml'},
timeout=10,
)
root = ET.fromstring(resp.content)
if root.find('return_code').text != 'SUCCESS':
return None, None
if root.find('result_code').text != 'SUCCESS':
return None, None
trade_state = root.find('trade_state').text
txn = root.find('transaction_id')
transaction_id = txn.text if txn is not None else None
return trade_state, transaction_id
except Exception as exc:
logger.error('微信查单失败 %s: %s', out_trade_no, exc)
return None, None
def confirm_member_recharge_paid(dingdan_id, yonghuid):
"""
前端支付成功确认:先查微信 SUCCESS 再幂等履约。
已履约则直接返回成功,不与回调重复入账。
"""
try:
order = Czjilu.query.get(dingdan_id=dingdan_id, yonghuid=yonghuid)
except Czjilu.DoesNotExist:
raise MemberRechargeError('订单不存在或不属于当前用户', 'not_found')
if order.leixing != 1:
raise MemberRechargeError('不是会员订单', 'type_error')
if order.zhuangtai == 3:
goumai = Huiyuangoumai.query.filter(
yonghu_id=yonghuid, huiyuan_id=order.huiyuan_id,
).first()
if not goumai:
raise MemberRechargeError('订单已付但会员记录缺失,请联系客服', 'data_error')
return {
'fulfilled': False,
'already_done': True,
'zhuangtai': 3,
'daoqi_time': goumai.daoqi_time,
'huiyuan_id': order.huiyuan_id,
}
if order.zhuangtai != 9:
raise MemberRechargeError(f'订单状态异常: {order.zhuangtai}', 'status_error')
trade_state, _ = query_wechat_v2_trade_state(
dingdan_id, getattr(order, 'club_id', None),
)
if trade_state != 'SUCCESS':
raise MemberRechargeError('微信侧尚未支付成功,请稍后再试', 'not_paid')
total_fee_yuan = Decimal(str(order.jine))
result = fulfill_member_recharge(
dingdan_id, paid_amount_yuan=total_fee_yuan, source='confirm',
)
result['fulfilled'] = not result.get('already_done')
return result
+37 -314
View File
@@ -1503,6 +1503,15 @@ class HuiyuanGoumai(APIView):
'message': '会员信息不存在'
}, status=status.HTTP_400_BAD_REQUEST)
club_id = resolve_club_id_from_request(request)
from jituan.services.member_recharge import club_huiyuan_sellable
sellable, club_price = club_huiyuan_sellable(club_id, huiyuanid)
if not sellable:
return Response({
'code': 400,
'message': '该俱乐部未配置此会员价格,请联系管理员'
}, status=status.HTTP_400_BAD_REQUEST)
# 5. 生成订单ID(前缀CzHY表示会员充值)
timestamp_ms = int(time.time() * 1000)
timestamp_ns = int(time.time_ns() // 1000) % 1000000
@@ -1510,9 +1519,8 @@ class HuiyuanGoumai(APIView):
dingdanid = f"CzHY{timestamp_ms:011d}{timestamp_ns:06d}{random_num:02d}"
# 6. 创建充值记录订单
club_id = resolve_club_id_from_request(request)
jine = float(get_huiyuan_price(club_id, huiyuanid, huiyuan))
# 6. 创建充值记录订单(金额仅用本俱乐部配置)
jine = float(club_price)
# 组合订单说明:会员介绍 + 配置描述
shuoming = f"{huiyuan.jieshao} - {settings.HUIYUAN_PAY_DESCRIPTION}"
@@ -1760,51 +1768,22 @@ class HuiyuanHuitiao(View):
logger.warning(f"会员订单状态不是未支付: {order.dingdan_id}, 当前状态: {order.zhuangtai}")
return self.wechat_response('SUCCESS', 'OK')
# 验证金额
wechat_amount_yuan = total_fee / 100
order_amount_yuan = float(order.jine)
if abs(wechat_amount_yuan - order_amount_yuan) > 0.01:
logger.error(f"会员金额不一致: 订单金额{order_amount_yuan}元, 支付金额{wechat_amount_yuan}元")
return self.wechat_response('FAIL', '金额不一致')
# 查询会员信息
from jituan.services.member_recharge import fulfill_member_recharge, MemberRechargeError
try:
huiyuan = Huiyuan.query.get(huiyuan_id=order.huiyuan_id)
except Huiyuan.DoesNotExist:
logger.error(f"会员信息不存在: {order.huiyuan_id}")
return self.wechat_response('FAIL', '会员信息不存在')
# ===== 更新收支记录 =====
try:
jine_decimal = Decimal(str(order.jine)) if not isinstance(order.jine, Decimal) else order.jine
update_daily_income(jine_decimal, getattr(order, 'club_id', None))
from jituan.services.szjilu_accounting import apply_szjilu_income
apply_szjilu_income(jine_decimal, getattr(order, 'club_id', None))
logger.info(f"收支记录更新成功: 订单{order.dingdan_id}, 金额{jine_decimal}元")
except Exception as e:
logger.error(f"更新收支记录失败: {str(e)}", exc_info=True)
# 更新订单状态
order.zhuangtai = 3
order.save(update_fields=['zhuangtai'])
logger.info(f"会员订单状态更新成功: {order.dingdan_id} -> 3")
# 更新会员购买次数
huiyuan.goumai_cishu += 1
huiyuan.save(update_fields=['goumai_cishu'])
# 处理会员购买记录
goumai_record, is_new, is_first_buy = self.process_huiyuan_goumai(
order.yonghuid, order.huiyuan_id, huiyuan
)
if is_first_buy:
self.add_first_buy_jifen(order.yonghuid)
# 🆕 新增:处理分红(包含管事和组长)
with transaction.atomic():
self.process_fenhong(order, huiyuan)
fulfill_member_recharge(
out_trade_no,
paid_amount_yuan=wechat_amount_yuan,
source='callback',
)
except MemberRechargeError as exc:
logger.error('会员回调履约失败 %s: %s', out_trade_no, exc)
return self.wechat_response('FAIL', str(exc))
logger.info(f"会员支付回调处理完成: {out_trade_no}")
return self.wechat_response('SUCCESS', 'OK')
@@ -1816,278 +1795,8 @@ class HuiyuanHuitiao(View):
logger.error(f"会员支付回调处理异常: {str(e)}", exc_info=True)
return self.wechat_response('FAIL', '处理异常')
# ---------- 以下为原方法,基本不变,但需要导入新增模型 ----------
# 履约逻辑见 jituan.services.member_recharge(回调与 /huiyuanlx 确认共用)
def process_huiyuan_goumai(self, yonghuid, huiyuan_id, huiyuan):
"""处理会员购买记录(与原代码一致)"""
try:
existing_record = Huiyuangoumai.query.filter(
yonghu_id=yonghuid,
huiyuan_id=huiyuan_id
).first()
is_first_buy_any = False
if existing_record:
existing_record.daoqi_time += timedelta(days=30)
existing_record.huiyuan_zhuangtai = 1
existing_record.save()
logger.info(f"会员续费: 用户{yonghuid}, 会员{huiyuan_id}")
return existing_record, False, False
else:
other_huiyuan_count = Huiyuangoumai.query.filter(
yonghu_id=yonghuid
).exclude(huiyuan_id=huiyuan_id).count()
if other_huiyuan_count == 0:
is_first_buy_any = True
logger.info(f"用户{yonghuid}首次购买任何会员,将获得10积分")
goumai_record = Huiyuangoumai.query.create(
yonghu_id=yonghuid,
huiyuan_id=huiyuan_id,
jieshao=huiyuan.jieshao,
huiyuan_zhuangtai=1,
daoqi_time=timezone.now() + timedelta(days=30),
club_id=get_user_club_id(User.query.filter(UserUID=yonghuid).first()),
)
logger.info(f"新购会员: 用户{yonghuid}, 会员{huiyuan_id}")
return goumai_record, True, is_first_buy_any
except Exception as e:
logger.error(f"处理会员购买记录失败: {str(e)}", exc_info=True)
if "unique" in str(e).lower() or "duplicate" in str(e).lower():
logger.warning(f"检测到重复创建尝试,已由数据库拦截: 用户{yonghuid}, 会员{huiyuan_id}")
return self.process_huiyuan_goumai(yonghuid, huiyuan_id, huiyuan)
raise
def add_first_buy_jifen(self, yonghuid):
"""首次购买积分奖励(与原代码一致)"""
try:
dashou = UserDashou.query.get(user__UserUID=yonghuid)
dashou.jifen += 10
dashou.save(update_fields=['jifen'])
logger.info(f"用户{yonghuid}首次购买会员,积分+10,当前积分: {dashou.jifen}")
except UserDashou.DoesNotExist:
logger.error(f"找不到打手记录,无法增加积分: {yonghuid}")
except Exception as e:
logger.error(f"增加积分失败: {str(e)}")
# 🆕 新增:统一分红处理函数
def process_fenhong(self, order, huiyuan):
"""
处理管事和组长分红(支持多次分红配置)
"""
try:
# 获取打手信息
dashou = UserDashou.query.get(user__UserUID=order.yonghuid)
guanshi_id = dashou.yaoqingren
if not guanshi_id:
logger.info(f"打手{order.yonghuid}没有邀请人,无分红")
return
# 获取管事信息
try:
guanshi = UserGuanshi.query.get(user__UserUID=guanshi_id)
except UserGuanshi.DoesNotExist:
logger.warning(f"管事信息不存在: {guanshi_id}")
return
# 获取组长ID(从管事扩展表的邀请人)
zuzhang_id = guanshi.yaoqingren
# 获取打手头像和昵称
user_main = dashou.user
avatar = user_main.Avatar
nicheng = dashou.nicheng or "未知打手"
# 查询该打手针对该管事的已分红次数
existing_count = Gsfenhong.query.filter(
dashouid=order.yonghuid,
guanshi=guanshi_id
).count()
cishu = existing_count + 1 # 本次是第几次
# 获取订单金额(用于限制分红总额)
order_amount = Decimal(str(order.jine)) if not isinstance(order.jine, Decimal) else order.jine
club_id = getattr(order, 'club_id', None) or resolve_club_id_for_write(user=dashou.user)
guanshi_fc, zuzhang_fc = get_huiyuan_fenchong(club_id, huiyuan.huiyuan_id, huiyuan)
# 计算管事分红金额
guanshi_fenhong = self._calc_guanshi_fenhong(
guanshi, huiyuan, guanshi_id, cishu, default_fc=guanshi_fc,
)
if guanshi_fenhong is None:
logger.info(f"管事{guanshi_id}本次无分红")
guanshi_fenhong = Decimal('0.00')
# 计算组长分红金额(如果存在组长)
zuzhang_fenhong = Decimal('0.00')
if zuzhang_id:
# 获取组长信息
try:
zuzhang = UserZuzhang.query.get(user__UserUID=zuzhang_id)
except UserZuzhang.DoesNotExist:
logger.warning(f"组长信息不存在: {zuzhang_id}")
zuzhang = None
zuzhang_fenhong = self._calc_zuzhang_fenhong(
zuzhang, huiyuan, zuzhang_id, cishu, default_fc=zuzhang_fc,
)
if zuzhang_fenhong is None:
logger.info(f"组长{zuzhang_id}本次无分红")
zuzhang_fenhong = Decimal('0.00')
else:
logger.info(f"管事{guanshi_id}没有组长,无组长分红")
# 检查分红总额是否超过订单金额(优先保证管事分红)
total_fenhong = guanshi_fenhong + zuzhang_fenhong
if total_fenhong > order_amount:
logger.warning(f"分红总额{total_fenhong}超过订单金额{order_amount},组长分红设为0")
zuzhang_fenhong = Decimal('0.00')
total_fenhong = guanshi_fenhong
if total_fenhong > order_amount:
logger.warning(f"管事分红{guanshi_fenhong}也超过订单金额,强制设为0")
guanshi_fenhong = Decimal('0.00')
total_fenhong = Decimal('0.00')
# 创建分红记录(先创建主记录,即使金额为0也记录?但为0无意义,所以只有大于0才创建)
if guanshi_fenhong > 0 or zuzhang_fenhong > 0:
# 更新管事扩展表
if guanshi_fenhong > 0:
guanshi.chongzhifenrun += guanshi_fenhong
guanshi.yue += guanshi_fenhong
guanshi.jinrichongzhi += 1
guanshi.jinyuechongzhi += 1
guanshi.save(update_fields=['chongzhifenrun', 'yue', 'jinrichongzhi', 'jinyuechongzhi'])
logger.info(f"管事分红成功: 管事{guanshi_id}, 金额{guanshi_fenhong}")
# 更新组长扩展表
if zuzhang_fenhong > 0 and zuzhang:
zuzhang.fenyong_zonge += zuzhang_fenhong
zuzhang.ketixian_jine += zuzhang_fenhong
zuzhang.jinri_fenyong += zuzhang_fenhong
zuzhang.jinyue_fenyong += zuzhang_fenhong
zuzhang.save(update_fields=['fenyong_zonge', 'ketixian_jine', 'jinri_fenyong', 'jinyue_fenyong'])
logger.info(f"组长分红成功: 组长{zuzhang_id}, 金额{zuzhang_fenhong}")
# 创建分红记录
Gsfenhong.query.create(
dingdan_id=order.dingdan_id,
guanshi=guanshi_id,
dashouid=order.yonghuid,
shuoming=order.shuoming,
fenhong=guanshi_fenhong,
avatar=avatar,
nicheng=nicheng,
zuzhang_id=zuzhang_id if zuzhang_fenhong > 0 else None,
zuzhang_fenhong=zuzhang_fenhong if zuzhang_fenhong > 0 else None,
huiyuan_id=huiyuan.huiyuan_id,
fenhong_leixing=1,
club_id=resolve_gsfenhong_club_id(
dingdan_id=order.dingdan_id,
dashouid=order.yonghuid,
czjilu=order,
),
)
# ========== 管事每日统计(新购) ==========
if guanshi_fenhong > 0 and cishu == 1:
try:
update_guanshi_daily_by_action(
yonghuid=guanshi_id,
action=2,
amount=guanshi_fenhong
)
logger.info(f"管事每日统计更新成功(新购):管事{guanshi_id},分红{guanshi_fenhong}")
except Exception as e:
logger.error(f"管事每日统计更新失败(新购):{str(e)}")
# ========== 管事每日统计(续费) ==========
if guanshi_fenhong > 0 and cishu > 1:
try:
update_guanshi_xufei_daily(
yonghuid=guanshi_id,
xufei_jine=guanshi_fenhong
)
logger.info(f"管事续费统计更新成功:管事{guanshi_id},续费分红{guanshi_fenhong}")
except Exception as e:
logger.error(f"管事续费统计更新失败:{str(e)}")
# ========== 组长每日统计(分佣收入) ==========
if zuzhang_fenhong > 0 and zuzhang_id:
try:
update_zuzhang_daily_by_action(
yonghuid=zuzhang_id,
action=2,
amount=zuzhang_fenhong
)
logger.info(f"组长每日统计更新成功:组长{zuzhang_id},分佣{zuzhang_fenhong}")
except Exception as e:
logger.error(f"组长每日统计更新失败:{str(e)}")
logger.info(f"分红记录创建成功: 订单{order.dingdan_id}")
else:
logger.info(f"分红金额均为0,不创建记录")
except Exception as e:
logger.error(f"处理分红失败: {str(e)}", exc_info=True)
# 不抛出异常,避免影响主流程
def _calc_guanshi_fenhong(self, guanshi, huiyuan, guanshi_id, cishu, default_fc=None):
"""计算管事分红金额"""
# 先查多次分红配置表
try:
cid = resolve_gsfenhong_club_id(dashouid=guanshi_id)
config = DuociFenhong.query.get(
club_id=cid,
huiyuan=huiyuan,
yonghuid=guanshi_id,
cishu=cishu,
)
return Decimal(str(config.guanshi_fenhong))
except DuociFenhong.DoesNotExist:
pass
if cishu == 1:
base = default_fc if default_fc is not None else huiyuan.guanshifc
return Decimal(str(base))
else:
# 第二次及以上,检查是否开启二次分红
if guanshi.fenghong_erci_enabled:
return Decimal(str(guanshi.fenghong_erci))
else:
return None # 无分红
def _calc_zuzhang_fenhong(self, zuzhang, huiyuan, zuzhang_id, cishu, default_fc=None):
"""计算组长分红金额"""
if zuzhang is None:
return None
# 查多次分红配置表
try:
cid = resolve_gsfenhong_club_id(dashouid=zuzhang_id)
config = DuociFenhong.query.get(
club_id=cid,
huiyuan=huiyuan,
yonghuid=zuzhang_id,
cishu=cishu,
)
return Decimal(str(config.zuzhang_fenhong))
except DuociFenhong.DoesNotExist:
pass
if cishu == 1:
base = default_fc if default_fc is not None else huiyuan.zuzhangfc
return Decimal(str(base))
else:
# 第二次及以上,检查组长是否开启额外分红
if zuzhang.kaioi_ewai_fenhong:
return Decimal(str(zuzhang.ewai_fenhong_jine))
else:
return None
# 以下为原辅助方法,保持原样
def verify_signature(self, xml_data):
"""验证微信支付签名(与原代码一致)"""
try:
@@ -2236,9 +1945,23 @@ class HuiyuanLunxun(APIView):
'message': '订单类型错误,不是会员订单'
}, status=status.HTTP_400_BAD_REQUEST)
# 4. 检查订单状态是否为已支付(3)
# 4. 未支付:查微信并幂等履约(与回调互斥,zhuangtai!=9 则跳过)
if order.zhuangtai == 9:
from jituan.services.member_recharge import (
confirm_member_recharge_paid,
MemberRechargeError,
)
try:
confirm_member_recharge_paid(dingdanid, request.user.UserUID)
order = Czjilu.query.get(dingdan_id=dingdanid, yonghuid=request.user.UserUID)
except MemberRechargeError as exc:
logger.info(f"会员确认未就绪 {dingdanid}: {exc}")
return Response({
'code': 400,
'message': str(exc),
}, status=status.HTTP_400_BAD_REQUEST)
if order.zhuangtai != 3:
logger.info(f"会员订单{dingdanid}尚未支付完成,当前状态: {order.zhuangtai}")
return Response({
'code': 400,
'message': '会员订单尚未支付完成'