fix: 会员充值链路加固(体验资格、积分分红、czjilu审计、余额抵扣)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
52
jituan/management/commands/backfill_member_trial_flags.py
Normal file
52
jituan/management/commands/backfill_member_trial_flags.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
"""回填会员体验标记:czjilu.is_trial + huiyuangoumai.has_used_trial。"""
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from jituan.services.member_recharge import (
|
||||||
|
_order_is_trial_like,
|
||||||
|
has_paid_trial_purchase,
|
||||||
|
)
|
||||||
|
from products.models import Czjilu, Huiyuangoumai
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = '回填体验会员订单/权益标记(修复重复购买体验、正式计次错误)'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'--dry-run',
|
||||||
|
action='store_true',
|
||||||
|
help='仅统计,不写库',
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
dry_run = bool(options.get('dry_run'))
|
||||||
|
czjilu_fixed = 0
|
||||||
|
goumai_fixed = 0
|
||||||
|
|
||||||
|
member_orders = Czjilu.query.filter(leixing=1, zhuangtai=3)
|
||||||
|
with transaction.atomic():
|
||||||
|
for order in member_orders.iterator():
|
||||||
|
club_id = getattr(order, 'club_id', None)
|
||||||
|
if _order_is_trial_like(order, club_id) and not order.is_trial:
|
||||||
|
czjilu_fixed += 1
|
||||||
|
if not dry_run:
|
||||||
|
Czjilu.query.filter(dingdan_id=order.dingdan_id).update(is_trial=True)
|
||||||
|
|
||||||
|
goumai_rows = Huiyuangoumai.query.all()
|
||||||
|
for row in goumai_rows.iterator():
|
||||||
|
if row.has_used_trial:
|
||||||
|
continue
|
||||||
|
if has_paid_trial_purchase(row.yonghu_id, row.huiyuan_id, row.club_id):
|
||||||
|
goumai_fixed += 1
|
||||||
|
if not dry_run:
|
||||||
|
Huiyuangoumai.query.filter(id=row.id).update(has_used_trial=True)
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
transaction.set_rollback(True)
|
||||||
|
|
||||||
|
mode = 'DRY-RUN' if dry_run else 'APPLIED'
|
||||||
|
self.stdout.write(self.style.SUCCESS(
|
||||||
|
f'[{mode}] czjilu.is_trial 修复 {czjilu_fixed} 条,'
|
||||||
|
f'huiyuangoumai.has_used_trial 修复 {goumai_fixed} 条',
|
||||||
|
))
|
||||||
@@ -2,11 +2,12 @@
|
|||||||
会员微信充值履约(回调 / 前端确认共用,幂等)。
|
会员微信充值履约(回调 / 前端确认共用,幂等)。
|
||||||
|
|
||||||
分红策略(定稿 v2):
|
分红策略(定稿 v2):
|
||||||
- formal_cishu:本俱乐部 + 打手 + 会员,czjilu 已支付正式单(is_trial=false)笔数 + 1。
|
- formal_cishu:本俱乐部 + 打手 + 会员,已支付正式单笔数 + 1(体验单及历史脏数据不计入)。
|
||||||
与换管事无关;体验单不计入。
|
|
||||||
- formal_cishu=1:duoci_fenhong 或 club 默认正式分成。
|
- formal_cishu=1:duoci_fenhong 或 club 默认正式分成。
|
||||||
- formal_cishu>=2:仅 duoci_fenhong;无配置则不分。
|
- formal_cishu>=2:仅 duoci_fenhong;无配置则不分。
|
||||||
- 体验单:固定 trial_guanshifc / trial_zuzhangfc;不计入 formal_cishu。
|
- 体验单:固定 trial_guanshifc / trial_zuzhangfc;终身仅可购 1 次。
|
||||||
|
- 积分:体验 +5,正式 +5(体验后)或直接至 10(未买过体验),封顶 10。
|
||||||
|
- 履约(回调/轮询/余额)均二次校验体验资格;分红失败回滚事务。
|
||||||
|
|
||||||
财务:每笔成功履约必须 czjilu.zhuangtai=3 + 收支入账(record_wechat_income_once)。
|
财务:每笔成功履约必须 czjilu.zhuangtai=3 + 收支入账(record_wechat_income_once)。
|
||||||
"""
|
"""
|
||||||
@@ -45,6 +46,11 @@ MEMBER_FENHONG_LEIXING = 1
|
|||||||
MEMBER_LEIXING = 1
|
MEMBER_LEIXING = 1
|
||||||
MEMBER_PAID_STATUS = 3
|
MEMBER_PAID_STATUS = 3
|
||||||
MEMBER_PENDING_STATUS = 9
|
MEMBER_PENDING_STATUS = 9
|
||||||
|
MEMBER_FAILED_STATUS = 8
|
||||||
|
MEMBER_CANCELLED_STATUS = 2
|
||||||
|
MEMBER_JIFEN_CAP = 10
|
||||||
|
MEMBER_JIFEN_TRIAL_GRANT = 5
|
||||||
|
MEMBER_JIFEN_FORMAL_GRANT = 5
|
||||||
|
|
||||||
|
|
||||||
class MemberRechargeError(Exception):
|
class MemberRechargeError(Exception):
|
||||||
@@ -84,17 +90,70 @@ def club_huiyuan_sellable(club_id, huiyuan_id, is_trial=False):
|
|||||||
return True, price, days
|
return True, price, days
|
||||||
|
|
||||||
|
|
||||||
def has_formal_member_purchase(yonghuid, huiyuan_id, club_id):
|
def _order_is_trial_like(order, club_id=None):
|
||||||
"""是否已有成功支付的正式会员单(用于禁止再买体验)。"""
|
"""识别体验单(含历史 is_trial 未回填的脏数据)。"""
|
||||||
|
if bool(getattr(order, 'is_trial', False)):
|
||||||
|
return True
|
||||||
|
club_id = club_id or getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
|
||||||
|
huiyuan_id = getattr(order, 'huiyuan_id', None)
|
||||||
|
if not huiyuan_id:
|
||||||
|
return False
|
||||||
|
row = get_club_huiyuan_row(club_id, huiyuan_id, require_enabled=False)
|
||||||
|
if not row or not row.trial_enabled:
|
||||||
|
return False
|
||||||
|
trial_price = Decimal(str(row.trial_price or 0))
|
||||||
|
trial_days = int(row.trial_days or 0)
|
||||||
|
if trial_price <= 0 or trial_days <= 0:
|
||||||
|
return False
|
||||||
|
order_jine = Decimal(str(order.jine or 0))
|
||||||
|
if abs(order_jine - trial_price) > Decimal('0.01'):
|
||||||
|
return False
|
||||||
|
order_days = int(getattr(order, 'purchase_days', None) or 0)
|
||||||
|
return order_days in (0, trial_days)
|
||||||
|
|
||||||
|
|
||||||
|
def _iter_paid_member_orders(yonghuid, huiyuan_id, club_id, exclude_dingdan_id=None):
|
||||||
club_id = club_id or CLUB_ID_DEFAULT
|
club_id = club_id or CLUB_ID_DEFAULT
|
||||||
return Czjilu.query.filter(
|
qs = Czjilu.query.filter(
|
||||||
yonghuid=yonghuid,
|
yonghuid=yonghuid,
|
||||||
huiyuan_id=huiyuan_id,
|
huiyuan_id=huiyuan_id,
|
||||||
leixing=MEMBER_LEIXING,
|
leixing=MEMBER_LEIXING,
|
||||||
zhuangtai=MEMBER_PAID_STATUS,
|
zhuangtai=MEMBER_PAID_STATUS,
|
||||||
club_id=club_id,
|
club_id=club_id,
|
||||||
is_trial=False,
|
)
|
||||||
).exists()
|
if exclude_dingdan_id:
|
||||||
|
qs = qs.exclude(dingdan_id=exclude_dingdan_id)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
def has_paid_trial_purchase(yonghuid, huiyuan_id, club_id, exclude_dingdan_id=None):
|
||||||
|
"""是否已有成功支付体验单(含历史脏数据)。"""
|
||||||
|
club_id = club_id or CLUB_ID_DEFAULT
|
||||||
|
for order in _iter_paid_member_orders(yonghuid, huiyuan_id, club_id, exclude_dingdan_id):
|
||||||
|
if _order_is_trial_like(order, club_id):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def has_formal_member_purchase(yonghuid, huiyuan_id, club_id, exclude_dingdan_id=None):
|
||||||
|
"""是否已有成功支付的正式会员单(用于禁止再买体验)。"""
|
||||||
|
club_id = club_id or CLUB_ID_DEFAULT
|
||||||
|
for order in _iter_paid_member_orders(yonghuid, huiyuan_id, club_id, exclude_dingdan_id):
|
||||||
|
if not _order_is_trial_like(order, club_id):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _sync_trial_usage_flags(yonghuid, huiyuan_id, club_id):
|
||||||
|
"""根据 czjilu 历史回填 huiyuangoumai.has_used_trial。"""
|
||||||
|
goumai = Huiyuangoumai.query.filter(
|
||||||
|
yonghu_id=yonghuid, huiyuan_id=huiyuan_id,
|
||||||
|
).first()
|
||||||
|
if not goumai:
|
||||||
|
return
|
||||||
|
if has_paid_trial_purchase(yonghuid, huiyuan_id, club_id) and not goumai.has_used_trial:
|
||||||
|
goumai.has_used_trial = True
|
||||||
|
goumai.save(update_fields=['has_used_trial', 'UpdateTime'])
|
||||||
|
|
||||||
|
|
||||||
def can_purchase_trial(yonghuid, huiyuan_id, club_id):
|
def can_purchase_trial(yonghuid, huiyuan_id, club_id):
|
||||||
@@ -104,6 +163,11 @@ def can_purchase_trial(yonghuid, huiyuan_id, club_id):
|
|||||||
if not sellable:
|
if not sellable:
|
||||||
return False, '本俱乐部未开启体验会员'
|
return False, '本俱乐部未开启体验会员'
|
||||||
|
|
||||||
|
_sync_trial_usage_flags(yonghuid, huiyuan_id, club_id)
|
||||||
|
|
||||||
|
if has_paid_trial_purchase(yonghuid, huiyuan_id, club_id):
|
||||||
|
return False, '体验会员仅可购买一次'
|
||||||
|
|
||||||
goumai = Huiyuangoumai.query.filter(
|
goumai = Huiyuangoumai.query.filter(
|
||||||
yonghu_id=yonghuid, huiyuan_id=huiyuan_id,
|
yonghu_id=yonghuid, huiyuan_id=huiyuan_id,
|
||||||
).first()
|
).first()
|
||||||
@@ -137,20 +201,61 @@ def validate_member_purchase(yonghuid, huiyuan_id, club_id, is_trial):
|
|||||||
def resolve_formal_purchase_cishu(yonghuid, huiyuan_id, current_dingdan_id, club_id):
|
def resolve_formal_purchase_cishu(yonghuid, huiyuan_id, current_dingdan_id, club_id):
|
||||||
"""
|
"""
|
||||||
打手第几次正式购买该会员(duoci_fenhong.cishu)。
|
打手第几次正式购买该会员(duoci_fenhong.cishu)。
|
||||||
仅统计 is_trial=false 的已支付 czjilu。
|
体验单(含历史脏数据)不计入。
|
||||||
"""
|
"""
|
||||||
club_id = club_id or CLUB_ID_DEFAULT
|
club_id = club_id or CLUB_ID_DEFAULT
|
||||||
prior_paid = Czjilu.query.filter(
|
prior_paid = 0
|
||||||
yonghuid=yonghuid,
|
for order in _iter_paid_member_orders(yonghuid, huiyuan_id, club_id, current_dingdan_id):
|
||||||
huiyuan_id=huiyuan_id,
|
if not _order_is_trial_like(order, club_id):
|
||||||
leixing=MEMBER_LEIXING,
|
prior_paid += 1
|
||||||
zhuangtai=MEMBER_PAID_STATUS,
|
|
||||||
club_id=club_id,
|
|
||||||
is_trial=False,
|
|
||||||
).exclude(dingdan_id=current_dingdan_id).count()
|
|
||||||
return prior_paid + 1
|
return prior_paid + 1
|
||||||
|
|
||||||
|
|
||||||
|
def _truncate_shuoming(text, max_len=100):
|
||||||
|
text = (text or '').strip()
|
||||||
|
return text[:max_len] if text else ''
|
||||||
|
|
||||||
|
|
||||||
|
def _mark_member_order_failed(order, reason, *, record_income=False, club_id=None):
|
||||||
|
"""已收款或已扣款但履约失败:写失败状态,不发会员/积分/分红。"""
|
||||||
|
club_id = club_id or getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
|
||||||
|
base = (order.shuoming or '').strip()
|
||||||
|
order.zhuangtai = MEMBER_FAILED_STATUS
|
||||||
|
order.shuoming = _truncate_shuoming(f'{base} | 履约失败:{reason}')
|
||||||
|
order.save(update_fields=['zhuangtai', 'shuoming', 'UpdateTime'])
|
||||||
|
if record_income:
|
||||||
|
try:
|
||||||
|
_record_member_income(order, club_id)
|
||||||
|
except MemberRechargeError:
|
||||||
|
logger.error('失败单记收入异常 dingdan=%s', order.dingdan_id, exc_info=True)
|
||||||
|
logger.error('会员订单履约失败 dingdan=%s reason=%s', order.dingdan_id, reason)
|
||||||
|
|
||||||
|
|
||||||
|
def _require_paid_order_for_benefits(order):
|
||||||
|
"""仅已支付(3)订单可发会员权益/积分/分红。"""
|
||||||
|
if int(getattr(order, 'zhuangtai', 0) or 0) != MEMBER_PAID_STATUS:
|
||||||
|
raise MemberRechargeError(
|
||||||
|
f'订单未支付成功(zhuangtai={order.zhuangtai}),禁止发放权益',
|
||||||
|
'not_paid',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_member_fulfillment(order, club_id):
|
||||||
|
"""履约前二次校验(防止待支付旧单绕过下单校验)。"""
|
||||||
|
club_id = club_id or CLUB_ID_DEFAULT
|
||||||
|
is_trial = bool(getattr(order, 'is_trial', False))
|
||||||
|
if is_trial or _order_is_trial_like(order, club_id):
|
||||||
|
ok, msg = can_purchase_trial(order.yonghuid, order.huiyuan_id, club_id)
|
||||||
|
if not ok:
|
||||||
|
raise MemberRechargeError(msg or '体验会员不可购买', 'trial_not_allowed')
|
||||||
|
return
|
||||||
|
ok, msg, _, _ = validate_member_purchase(
|
||||||
|
order.yonghuid, order.huiyuan_id, club_id, is_trial=False,
|
||||||
|
)
|
||||||
|
if not ok:
|
||||||
|
raise MemberRechargeError(msg or '会员不可购买', 'purchase_not_allowed')
|
||||||
|
|
||||||
|
|
||||||
# 兼容旧名
|
# 兼容旧名
|
||||||
def resolve_member_purchase_cishu(yonghuid, huiyuan_id, current_dingdan_id, club_id):
|
def resolve_member_purchase_cishu(yonghuid, huiyuan_id, current_dingdan_id, club_id):
|
||||||
return resolve_formal_purchase_cishu(yonghuid, huiyuan_id, current_dingdan_id, club_id)
|
return resolve_formal_purchase_cishu(yonghuid, huiyuan_id, current_dingdan_id, club_id)
|
||||||
@@ -160,50 +265,54 @@ def fulfill_member_balance_purchase(czjilu, huiyuan, club_id=None):
|
|||||||
"""余额抵扣购买会员:按俱乐部配置天数履约,并计入购买次数/分红。"""
|
"""余额抵扣购买会员:按俱乐部配置天数履约,并计入购买次数/分红。"""
|
||||||
club_id = club_id or getattr(czjilu, 'club_id', None) or CLUB_ID_DEFAULT
|
club_id = club_id or getattr(czjilu, 'club_id', None) or CLUB_ID_DEFAULT
|
||||||
is_trial = bool(getattr(czjilu, 'is_trial', False))
|
is_trial = bool(getattr(czjilu, 'is_trial', False))
|
||||||
|
if is_trial:
|
||||||
|
raise MemberRechargeError(
|
||||||
|
'体验会员仅支持微信支付,不可用佣金、分红或押金抵扣',
|
||||||
|
'trial_balance_forbidden',
|
||||||
|
)
|
||||||
|
|
||||||
|
ok, err_msg, _, _ = validate_member_purchase(
|
||||||
|
czjilu.yonghuid, czjilu.huiyuan_id, club_id, is_trial=False,
|
||||||
|
)
|
||||||
|
if not ok:
|
||||||
|
raise MemberRechargeError(err_msg or '会员不可购买', 'purchase_not_allowed')
|
||||||
|
|
||||||
purchase_days = int(getattr(czjilu, 'purchase_days', None) or 0)
|
purchase_days = int(getattr(czjilu, 'purchase_days', None) or 0)
|
||||||
if purchase_days <= 0:
|
if purchase_days <= 0:
|
||||||
_, _, purchase_days = club_huiyuan_sellable(club_id, czjilu.huiyuan_id, is_trial=is_trial)
|
_, _, purchase_days = club_huiyuan_sellable(club_id, czjilu.huiyuan_id, is_trial=False)
|
||||||
|
|
||||||
formal_cishu = None
|
|
||||||
trial_guanshi_fc = Decimal('0')
|
|
||||||
trial_zuzhangfc = Decimal('0')
|
|
||||||
if is_trial:
|
|
||||||
row = get_club_huiyuan_row(club_id, czjilu.huiyuan_id)
|
|
||||||
if row:
|
|
||||||
trial_guanshi_fc = Decimal(str(row.trial_guanshifc or 0))
|
|
||||||
trial_zuzhangfc = Decimal(str(row.trial_zuzhangfc or 0))
|
|
||||||
else:
|
|
||||||
formal_cishu = resolve_formal_purchase_cishu(
|
formal_cishu = resolve_formal_purchase_cishu(
|
||||||
czjilu.yonghuid, czjilu.huiyuan_id, czjilu.dingdan_id, club_id,
|
czjilu.yonghuid, czjilu.huiyuan_id, czjilu.dingdan_id, club_id,
|
||||||
)
|
)
|
||||||
|
trial_guanshi_fc = Decimal('0')
|
||||||
goumai_record, is_first_buy_any = _apply_huiyuan_entitlement(
|
trial_zuzhangfc = Decimal('0')
|
||||||
czjilu.yonghuid, czjilu.huiyuan_id, huiyuan, club_id, is_trial, purchase_days,
|
|
||||||
)
|
|
||||||
if is_first_buy_any:
|
|
||||||
_add_first_buy_jifen(czjilu.yonghuid)
|
|
||||||
|
|
||||||
czjilu.zhuangtai = MEMBER_PAID_STATUS
|
czjilu.zhuangtai = MEMBER_PAID_STATUS
|
||||||
if not czjilu.purchase_days:
|
if not czjilu.purchase_days:
|
||||||
czjilu.purchase_days = purchase_days
|
czjilu.purchase_days = purchase_days
|
||||||
czjilu.save(update_fields=['zhuangtai', 'purchase_days'])
|
czjilu.is_trial = False
|
||||||
|
czjilu.save(update_fields=['zhuangtai', 'purchase_days', 'is_trial'])
|
||||||
|
_require_paid_order_for_benefits(czjilu)
|
||||||
|
|
||||||
|
goumai_record, _ = _apply_huiyuan_entitlement(
|
||||||
|
czjilu.yonghuid, czjilu.huiyuan_id, huiyuan, club_id, False, purchase_days,
|
||||||
|
)
|
||||||
|
new_jifen = _apply_member_purchase_jifen(
|
||||||
|
czjilu.yonghuid, is_trial=False, huiyuan_id=czjilu.huiyuan_id,
|
||||||
|
club_id=club_id, current_dingdan_id=czjilu.dingdan_id,
|
||||||
|
)
|
||||||
|
|
||||||
huiyuan.goumai_cishu = (huiyuan.goumai_cishu or 0) + 1
|
huiyuan.goumai_cishu = (huiyuan.goumai_cishu or 0) + 1
|
||||||
huiyuan.save(update_fields=['goumai_cishu'])
|
huiyuan.save(update_fields=['goumai_cishu'])
|
||||||
|
|
||||||
try:
|
|
||||||
_apply_member_fenhong(
|
_apply_member_fenhong(
|
||||||
czjilu, huiyuan,
|
czjilu, huiyuan,
|
||||||
formal_cishu=formal_cishu,
|
formal_cishu=formal_cishu,
|
||||||
trial_guanshi_fc=trial_guanshi_fc,
|
trial_guanshi_fc=trial_guanshi_fc,
|
||||||
trial_zuzhangfc=trial_zuzhangfc,
|
trial_zuzhangfc=trial_zuzhangfc,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
|
||||||
logger.error(
|
|
||||||
'余额购会员分红失败 dingdan=%s: %s', czjilu.dingdan_id, exc, exc_info=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
return goumai_record, is_first_buy_any
|
return goumai_record, new_jifen
|
||||||
|
|
||||||
|
|
||||||
def _lookup_duoci_fenhong(club_id, huiyuan_id, beneficiary_id, cishu, field):
|
def _lookup_duoci_fenhong(club_id, huiyuan_id, beneficiary_id, cishu, field):
|
||||||
@@ -313,6 +422,8 @@ def _apply_huiyuan_entitlement(yonghuid, huiyuan_id, huiyuan, club_id, is_trial,
|
|||||||
existing.has_used_trial = True
|
existing.has_used_trial = True
|
||||||
else:
|
else:
|
||||||
existing.is_trial = False
|
existing.is_trial = False
|
||||||
|
if has_paid_trial_purchase(yonghuid, huiyuan_id, club_id):
|
||||||
|
existing.has_used_trial = True
|
||||||
existing.save(update_fields=[
|
existing.save(update_fields=[
|
||||||
'daoqi_time', 'huiyuan_zhuangtai', 'jieshao', 'is_trial', 'has_used_trial', 'UpdateTime',
|
'daoqi_time', 'huiyuan_zhuangtai', 'jieshao', 'is_trial', 'has_used_trial', 'UpdateTime',
|
||||||
])
|
])
|
||||||
@@ -321,6 +432,7 @@ def _apply_huiyuan_entitlement(yonghuid, huiyuan_id, huiyuan, club_id, is_trial,
|
|||||||
other = Huiyuangoumai.query.filter(yonghu_id=yonghuid).exclude(huiyuan_id=huiyuan_id).count()
|
other = Huiyuangoumai.query.filter(yonghu_id=yonghuid).exclude(huiyuan_id=huiyuan_id).count()
|
||||||
if other == 0:
|
if other == 0:
|
||||||
is_first_buy_any = True
|
is_first_buy_any = True
|
||||||
|
used_trial_before = has_paid_trial_purchase(yonghuid, huiyuan_id, club_id)
|
||||||
resolved_club = club_id or get_user_club_id(User.query.filter(UserUID=yonghuid).first())
|
resolved_club = club_id or get_user_club_id(User.query.filter(UserUID=yonghuid).first())
|
||||||
record = Huiyuangoumai.query.create(
|
record = Huiyuangoumai.query.create(
|
||||||
yonghu_id=yonghuid,
|
yonghu_id=yonghuid,
|
||||||
@@ -330,12 +442,18 @@ def _apply_huiyuan_entitlement(yonghuid, huiyuan_id, huiyuan, club_id, is_trial,
|
|||||||
daoqi_time=new_daoqi,
|
daoqi_time=new_daoqi,
|
||||||
club_id=resolved_club,
|
club_id=resolved_club,
|
||||||
is_trial=is_trial,
|
is_trial=is_trial,
|
||||||
has_used_trial=is_trial,
|
has_used_trial=is_trial or used_trial_before,
|
||||||
)
|
)
|
||||||
return record, is_first_buy_any
|
return record, is_first_buy_any
|
||||||
|
|
||||||
|
|
||||||
def _apply_member_fenhong(order, huiyuan, formal_cishu=None, trial_guanshi_fc=None, trial_zuzhang_fc=None):
|
def _apply_member_fenhong(order, huiyuan, formal_cishu=None, trial_guanshi_fc=None, trial_zuzhang_fc=None):
|
||||||
|
if int(getattr(order, 'zhuangtai', 0) or 0) != MEMBER_PAID_STATUS:
|
||||||
|
logger.warning(
|
||||||
|
'订单未支付成功,禁止分红 dingdan=%s zhuangtai=%s',
|
||||||
|
order.dingdan_id, order.zhuangtai,
|
||||||
|
)
|
||||||
|
return
|
||||||
if Gsfenhong.query.filter(dingdan_id=order.dingdan_id).exists():
|
if Gsfenhong.query.filter(dingdan_id=order.dingdan_id).exists():
|
||||||
logger.info('会员分红已处理,跳过: %s', order.dingdan_id)
|
logger.info('会员分红已处理,跳过: %s', order.dingdan_id)
|
||||||
return
|
return
|
||||||
@@ -368,7 +486,7 @@ def _apply_member_fenhong(order, huiyuan, formal_cishu=None, trial_guanshi_fc=No
|
|||||||
club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
|
club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
|
||||||
huiyuan_id = huiyuan.huiyuan_id
|
huiyuan_id = huiyuan.huiyuan_id
|
||||||
order_amount = Decimal(str(order.jine))
|
order_amount = Decimal(str(order.jine))
|
||||||
is_trial_order = bool(getattr(order, 'is_trial', False))
|
is_trial_order = _order_is_trial_like(order, club_id)
|
||||||
|
|
||||||
if is_trial_order:
|
if is_trial_order:
|
||||||
guanshi_fenhong = Decimal(str(trial_guanshi_fc or 0))
|
guanshi_fenhong = Decimal(str(trial_guanshi_fc or 0))
|
||||||
@@ -460,13 +578,55 @@ def _apply_member_fenhong(order, huiyuan, formal_cishu=None, trial_guanshi_fc=No
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _add_first_buy_jifen(yonghuid):
|
def _apply_member_purchase_jifen(yonghuid, is_trial, huiyuan_id, club_id, current_dingdan_id=None):
|
||||||
|
"""
|
||||||
|
会员购积分:体验 +5,正式 +5(体验后)或直接到 10(未买过体验),封顶 10。
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
dashou = UserDashou.query.get(user__UserUID=yonghuid)
|
dashou = UserDashou.query.get(user__UserUID=yonghuid)
|
||||||
dashou.jifen += 10
|
|
||||||
dashou.save(update_fields=['jifen'])
|
|
||||||
except UserDashou.DoesNotExist:
|
except UserDashou.DoesNotExist:
|
||||||
logger.warning('首次购会员加积分失败,打手不存在: %s', yonghuid)
|
logger.warning('会员购积分失败,打手不存在: %s', yonghuid)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
current = int(dashou.jifen or 0)
|
||||||
|
if current >= MEMBER_JIFEN_CAP:
|
||||||
|
return current
|
||||||
|
|
||||||
|
if is_trial:
|
||||||
|
delta = min(MEMBER_JIFEN_TRIAL_GRANT, MEMBER_JIFEN_CAP - current)
|
||||||
|
else:
|
||||||
|
had_trial = has_paid_trial_purchase(
|
||||||
|
yonghuid, huiyuan_id, club_id, exclude_dingdan_id=current_dingdan_id,
|
||||||
|
)
|
||||||
|
if had_trial:
|
||||||
|
delta = min(MEMBER_JIFEN_FORMAL_GRANT, MEMBER_JIFEN_CAP - current)
|
||||||
|
else:
|
||||||
|
delta = MEMBER_JIFEN_CAP - current
|
||||||
|
|
||||||
|
if delta <= 0:
|
||||||
|
return current
|
||||||
|
|
||||||
|
dashou.jifen = min(current + delta, MEMBER_JIFEN_CAP)
|
||||||
|
dashou.save(update_fields=['jifen'])
|
||||||
|
logger.info(
|
||||||
|
'会员购积分 yonghu=%s is_trial=%s huiyuan=%s +%s -> %s',
|
||||||
|
yonghuid, is_trial, huiyuan_id, delta, dashou.jifen,
|
||||||
|
)
|
||||||
|
return dashou.jifen
|
||||||
|
|
||||||
|
|
||||||
|
def _add_first_buy_jifen(yonghuid):
|
||||||
|
"""兼容旧调用:等价于直接买正式且从未购会员积分。"""
|
||||||
|
logger.warning('_add_first_buy_jifen 已废弃,请改用 _apply_member_purchase_jifen')
|
||||||
|
try:
|
||||||
|
dashou = UserDashou.query.get(user__UserUID=yonghuid)
|
||||||
|
except UserDashou.DoesNotExist:
|
||||||
|
return
|
||||||
|
current = int(dashou.jifen or 0)
|
||||||
|
if current >= MEMBER_JIFEN_CAP:
|
||||||
|
return
|
||||||
|
dashou.jifen = MEMBER_JIFEN_CAP
|
||||||
|
dashou.save(update_fields=['jifen'])
|
||||||
|
|
||||||
|
|
||||||
def repair_member_entitlement_if_paid(dingdan_id):
|
def repair_member_entitlement_if_paid(dingdan_id):
|
||||||
@@ -488,10 +648,33 @@ def repair_member_entitlement_if_paid(dingdan_id):
|
|||||||
|
|
||||||
club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
|
club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
|
||||||
days = order.purchase_days or 30
|
days = order.purchase_days or 30
|
||||||
|
is_trial = _order_is_trial_like(order, club_id)
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
_apply_huiyuan_entitlement(
|
_apply_huiyuan_entitlement(
|
||||||
order.yonghuid, order.huiyuan_id, huiyuan, club_id,
|
order.yonghuid, order.huiyuan_id, huiyuan, club_id, is_trial, days,
|
||||||
bool(getattr(order, 'is_trial', False)), days,
|
)
|
||||||
|
_apply_member_purchase_jifen(
|
||||||
|
order.yonghuid, is_trial=is_trial, huiyuan_id=order.huiyuan_id,
|
||||||
|
club_id=club_id, current_dingdan_id=order.dingdan_id,
|
||||||
|
)
|
||||||
|
if not Gsfenhong.query.filter(dingdan_id=dingdan_id, fenhong_leixing=MEMBER_FENHONG_LEIXING).exists():
|
||||||
|
trial_guanshi_fc = Decimal('0')
|
||||||
|
trial_zuzhang_fc = Decimal('0')
|
||||||
|
formal_cishu = None
|
||||||
|
if is_trial:
|
||||||
|
row = get_club_huiyuan_row(club_id, order.huiyuan_id)
|
||||||
|
if row:
|
||||||
|
trial_guanshi_fc = Decimal(str(row.trial_guanshifc or 0))
|
||||||
|
trial_zuzhang_fc = Decimal(str(row.trial_zuzhangfc or 0))
|
||||||
|
else:
|
||||||
|
formal_cishu = resolve_formal_purchase_cishu(
|
||||||
|
order.yonghuid, order.huiyuan_id, order.dingdan_id, club_id,
|
||||||
|
)
|
||||||
|
_apply_member_fenhong(
|
||||||
|
order, huiyuan,
|
||||||
|
formal_cishu=formal_cishu,
|
||||||
|
trial_guanshi_fc=trial_guanshi_fc,
|
||||||
|
trial_zuzhang_fc=trial_zuzhang_fc,
|
||||||
)
|
)
|
||||||
logger.warning('已补开通会员 dingdan=%s', dingdan_id)
|
logger.warning('已补开通会员 dingdan=%s', dingdan_id)
|
||||||
return True
|
return True
|
||||||
@@ -536,7 +719,21 @@ def fulfill_member_recharge(dingdan_id, paid_amount_yuan=None, source='callback'
|
|||||||
raise MemberRechargeError(f'会员不存在: {order.huiyuan_id}', 'huiyuan_not_found')
|
raise MemberRechargeError(f'会员不存在: {order.huiyuan_id}', 'huiyuan_not_found')
|
||||||
|
|
||||||
club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
|
club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
|
||||||
is_trial = bool(getattr(order, 'is_trial', False))
|
try:
|
||||||
|
_validate_member_fulfillment(order, club_id)
|
||||||
|
except MemberRechargeError as exc:
|
||||||
|
_mark_member_order_failed(
|
||||||
|
order, str(exc),
|
||||||
|
record_income=paid_amount_yuan is not None,
|
||||||
|
club_id=club_id,
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
is_trial = _order_is_trial_like(order, club_id)
|
||||||
|
if is_trial and not order.is_trial:
|
||||||
|
order.is_trial = True
|
||||||
|
order.save(update_fields=['is_trial'])
|
||||||
|
|
||||||
purchase_days = order.purchase_days or (30 if not is_trial else 0)
|
purchase_days = order.purchase_days or (30 if not is_trial else 0)
|
||||||
if purchase_days <= 0:
|
if purchase_days <= 0:
|
||||||
_, _, purchase_days = club_huiyuan_sellable(club_id, order.huiyuan_id, is_trial=is_trial)
|
_, _, purchase_days = club_huiyuan_sellable(club_id, order.huiyuan_id, is_trial=is_trial)
|
||||||
@@ -554,31 +751,33 @@ def fulfill_member_recharge(dingdan_id, paid_amount_yuan=None, source='callback'
|
|||||||
order.yonghuid, order.huiyuan_id, order.dingdan_id, club_id,
|
order.yonghuid, order.huiyuan_id, order.dingdan_id, club_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
goumai_record, is_first_buy_any = _apply_huiyuan_entitlement(
|
order.zhuangtai = MEMBER_PAID_STATUS
|
||||||
|
if not order.purchase_days:
|
||||||
|
order.purchase_days = purchase_days
|
||||||
|
if is_trial:
|
||||||
|
order.is_trial = True
|
||||||
|
order.save(update_fields=['zhuangtai', 'purchase_days', 'is_trial'])
|
||||||
|
_require_paid_order_for_benefits(order)
|
||||||
|
|
||||||
|
goumai_record, _ = _apply_huiyuan_entitlement(
|
||||||
order.yonghuid, order.huiyuan_id, huiyuan, club_id, is_trial, purchase_days,
|
order.yonghuid, order.huiyuan_id, huiyuan, club_id, is_trial, purchase_days,
|
||||||
)
|
)
|
||||||
if is_first_buy_any:
|
_apply_member_purchase_jifen(
|
||||||
_add_first_buy_jifen(order.yonghuid)
|
order.yonghuid, is_trial=is_trial, huiyuan_id=order.huiyuan_id,
|
||||||
|
club_id=club_id, current_dingdan_id=order.dingdan_id,
|
||||||
order.zhuangtai = MEMBER_PAID_STATUS
|
)
|
||||||
order.save(update_fields=['zhuangtai'])
|
|
||||||
|
|
||||||
huiyuan.goumai_cishu = (huiyuan.goumai_cishu or 0) + 1
|
huiyuan.goumai_cishu = (huiyuan.goumai_cishu or 0) + 1
|
||||||
huiyuan.save(update_fields=['goumai_cishu'])
|
huiyuan.save(update_fields=['goumai_cishu'])
|
||||||
|
|
||||||
_record_member_income(order, club_id)
|
_record_member_income(order, club_id)
|
||||||
|
|
||||||
try:
|
|
||||||
_apply_member_fenhong(
|
_apply_member_fenhong(
|
||||||
order, huiyuan,
|
order, huiyuan,
|
||||||
formal_cishu=formal_cishu,
|
formal_cishu=formal_cishu,
|
||||||
trial_guanshi_fc=trial_guanshi_fc,
|
trial_guanshi_fc=trial_guanshi_fc,
|
||||||
trial_zuzhang_fc=trial_zuzhang_fc,
|
trial_zuzhang_fc=trial_zuzhang_fc,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
|
||||||
logger.error(
|
|
||||||
'会员分红失败但会员已开通 dingdan=%s err=%s', dingdan_id, exc, exc_info=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
'会员充值履约完成 dingdan=%s source=%s club=%s is_trial=%s formal_cishu=%s jine=%s',
|
'会员充值履约完成 dingdan=%s source=%s club=%s is_trial=%s formal_cishu=%s jine=%s',
|
||||||
@@ -689,14 +888,37 @@ def dashou_in_active_trial_period(yonghuid):
|
|||||||
).exists()
|
).exists()
|
||||||
|
|
||||||
|
|
||||||
def extend_member_entitlement_admin(yonghuid, huiyuan_id, days, club_id=None):
|
def extend_member_entitlement_admin(yonghuid, huiyuan_id, days, club_id=None, operator_note=''):
|
||||||
"""后台赠送正式会员(无 czjilu、无分红)。"""
|
"""后台赠送正式会员:写 czjilu 审计记录,无支付、无分红、无积分。"""
|
||||||
try:
|
try:
|
||||||
huiyuan = Huiyuan.query.get(huiyuan_id=huiyuan_id)
|
huiyuan = Huiyuan.query.get(huiyuan_id=huiyuan_id)
|
||||||
except Huiyuan.DoesNotExist:
|
except Huiyuan.DoesNotExist:
|
||||||
return None, '会员类型不存在'
|
return None, '会员类型不存在'
|
||||||
days = max(1, int(days))
|
days = max(1, int(days))
|
||||||
|
club_id = club_id or CLUB_ID_DEFAULT
|
||||||
record, _ = _apply_huiyuan_entitlement(
|
record, _ = _apply_huiyuan_entitlement(
|
||||||
yonghuid, huiyuan_id, huiyuan, club_id, is_trial=False, purchase_days=days,
|
yonghuid, huiyuan_id, huiyuan, club_id, is_trial=False, purchase_days=days,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
timestamp = str(int(timezone.now().timestamp() * 1000))[-12:]
|
||||||
|
rand_str = ''.join(random.choices(string.ascii_uppercase + string.digits, k=4))
|
||||||
|
dingdan_id = f'ADM{timestamp}{rand_str}'
|
||||||
|
note = (operator_note or '').strip()
|
||||||
|
shuoming = f'后台赠送会员 {huiyuan.jieshao} {days}天'
|
||||||
|
if note:
|
||||||
|
shuoming = _truncate_shuoming(f'{shuoming} | {note}')
|
||||||
|
|
||||||
|
Czjilu.query.create(
|
||||||
|
dingdan_id=dingdan_id,
|
||||||
|
zhuangtai=MEMBER_PAID_STATUS,
|
||||||
|
yonghuid=yonghuid,
|
||||||
|
jine=Decimal('0'),
|
||||||
|
leixing=MEMBER_LEIXING,
|
||||||
|
shuoming=shuoming,
|
||||||
|
huiyuan_id=huiyuan_id,
|
||||||
|
club_id=club_id,
|
||||||
|
is_trial=False,
|
||||||
|
purchase_days=days,
|
||||||
|
)
|
||||||
|
logger.info('后台赠送会员 yonghu=%s huiyuan=%s days=%s dingdan=%s', yonghuid, huiyuan_id, days, dingdan_id)
|
||||||
return record, None
|
return record, None
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ from jituan.services.member_recharge import (
|
|||||||
MEMBER_LEIXING,
|
MEMBER_LEIXING,
|
||||||
MEMBER_PAID_STATUS,
|
MEMBER_PAID_STATUS,
|
||||||
MEMBER_PENDING_STATUS,
|
MEMBER_PENDING_STATUS,
|
||||||
|
MEMBER_FAILED_STATUS,
|
||||||
|
MEMBER_CANCELLED_STATUS,
|
||||||
resolve_formal_purchase_cishu,
|
resolve_formal_purchase_cishu,
|
||||||
)
|
)
|
||||||
from jituan.constants import DATA_SCOPE_ALL
|
from jituan.constants import DATA_SCOPE_ALL
|
||||||
@@ -22,6 +24,8 @@ from users.models import UserDashou, UserBoss
|
|||||||
MEMBER_STATUS_LABELS = {
|
MEMBER_STATUS_LABELS = {
|
||||||
MEMBER_PAID_STATUS: '已支付',
|
MEMBER_PAID_STATUS: '已支付',
|
||||||
MEMBER_PENDING_STATUS: '待支付',
|
MEMBER_PENDING_STATUS: '待支付',
|
||||||
|
MEMBER_FAILED_STATUS: '履约失败',
|
||||||
|
MEMBER_CANCELLED_STATUS: '已取消',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -300,6 +304,8 @@ def build_member_recharge_records_payload(request, filters=None, page=1, page_si
|
|||||||
'zhuangtai_options': [
|
'zhuangtai_options': [
|
||||||
{'value': MEMBER_PAID_STATUS, 'label': '已支付'},
|
{'value': MEMBER_PAID_STATUS, 'label': '已支付'},
|
||||||
{'value': MEMBER_PENDING_STATUS, 'label': '待支付'},
|
{'value': MEMBER_PENDING_STATUS, 'label': '待支付'},
|
||||||
|
{'value': MEMBER_FAILED_STATUS, 'label': '履约失败'},
|
||||||
|
{'value': MEMBER_CANCELLED_STATUS, 'label': '已取消'},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'scope': resolve_club_scope(request),
|
'scope': resolve_club_scope(request),
|
||||||
|
|||||||
@@ -879,6 +879,7 @@ class DsqrgmdhView(APIView):
|
|||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
|
from jituan.services.member_recharge import MemberRechargeError
|
||||||
try:
|
try:
|
||||||
leixing = request.data.get('leixing')
|
leixing = request.data.get('leixing')
|
||||||
shenfen_id = request.data.get('shenfen_id')
|
shenfen_id = request.data.get('shenfen_id')
|
||||||
@@ -969,6 +970,7 @@ class DsqrgmdhView(APIView):
|
|||||||
return Response({'code': 400, 'msg': f'{identity["name"]}余额不足,需要{required}元,当前{balance}元'}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({'code': 400, 'msg': f'{identity["name"]}余额不足,需要{required}元,当前{balance}元'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
fulfill_error = None
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
locked_profile = identity['model'].objects.select_for_update().get(id=profile.id)
|
locked_profile = identity['model'].objects.select_for_update().get(id=profile.id)
|
||||||
current_balance = getattr(locked_profile, identity['balance_field'])
|
current_balance = getattr(locked_profile, identity['balance_field'])
|
||||||
@@ -995,7 +997,8 @@ class DsqrgmdhView(APIView):
|
|||||||
shuoming = f"{identity['name']}抵扣 {action_desc},抵扣金额{required}元"
|
shuoming = f"{identity['name']}抵扣 {action_desc},抵扣金额{required}元"
|
||||||
cz_leixing = leixing
|
cz_leixing = leixing
|
||||||
cz_club_id = club_id
|
cz_club_id = club_id
|
||||||
cz_zhuangtai = 1
|
from jituan.services.member_recharge import MEMBER_PENDING_STATUS, MEMBER_FAILED_STATUS, MEMBER_PAID_STATUS
|
||||||
|
cz_zhuangtai = MEMBER_PENDING_STATUS
|
||||||
if leixing == 4:
|
if leixing == 4:
|
||||||
from jituan.services.club_penalty import penalty_cz_club_id
|
from jituan.services.club_penalty import penalty_cz_club_id
|
||||||
from products.czjilu_types import (
|
from products.czjilu_types import (
|
||||||
@@ -1031,11 +1034,21 @@ class DsqrgmdhView(APIView):
|
|||||||
|
|
||||||
czjilu = Czjilu.query.create(**czjilu_kwargs)
|
czjilu = Czjilu.query.create(**czjilu_kwargs)
|
||||||
|
|
||||||
response_data = {}
|
response_data = {'dingdan_id': dingdan_id}
|
||||||
|
fulfill_error = None
|
||||||
|
|
||||||
if leixing == 1:
|
if leixing == 1:
|
||||||
from jituan.services.member_recharge import fulfill_member_balance_purchase
|
from jituan.services.member_recharge import (
|
||||||
huiyuan_record, is_first_buy = fulfill_member_balance_purchase(
|
fulfill_member_balance_purchase,
|
||||||
|
validate_member_purchase,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
ok, err_msg, _, _ = validate_member_purchase(
|
||||||
|
yonghuid, huiyuan_id, club_id, is_trial=False,
|
||||||
|
)
|
||||||
|
if not ok:
|
||||||
|
raise MemberRechargeError(err_msg or '会员不可购买', 'purchase_not_allowed')
|
||||||
|
huiyuan_record, new_jifen = fulfill_member_balance_purchase(
|
||||||
czjilu, huiyuan, club_id=club_id,
|
czjilu, huiyuan, club_id=club_id,
|
||||||
)
|
)
|
||||||
response_data['huiyuan'] = {
|
response_data['huiyuan'] = {
|
||||||
@@ -1043,23 +1056,30 @@ class DsqrgmdhView(APIView):
|
|||||||
'huiyuanming': huiyuan.jieshao,
|
'huiyuanming': huiyuan.jieshao,
|
||||||
'daoqi': huiyuan_record.daoqi_time.strftime('%Y-%m-%d %H:%M:%S') if hasattr(huiyuan_record, 'daoqi_time') else '',
|
'daoqi': huiyuan_record.daoqi_time.strftime('%Y-%m-%d %H:%M:%S') if hasattr(huiyuan_record, 'daoqi_time') else '',
|
||||||
}
|
}
|
||||||
if is_first_buy:
|
response_data['jifen'] = new_jifen
|
||||||
try:
|
except MemberRechargeError as exc:
|
||||||
dashou = UserDashou.query.get(user=user)
|
setattr(locked_profile, identity['balance_field'], current_balance)
|
||||||
dashou.jifen += 10
|
locked_profile.save(update_fields=[identity['balance_field']])
|
||||||
dashou.save(update_fields=['jifen'])
|
czjilu.zhuangtai = MEMBER_FAILED_STATUS
|
||||||
response_data['jifen'] = dashou.jifen
|
czjilu.shuoming = f"{shuoming} | 履约失败:{str(exc)[:40]}"[:100]
|
||||||
except UserDashou.DoesNotExist:
|
czjilu.save(update_fields=['zhuangtai', 'shuoming'])
|
||||||
pass
|
fulfill_error = exc
|
||||||
|
|
||||||
elif leixing == 2:
|
elif leixing == 2:
|
||||||
try:
|
try:
|
||||||
dashou = UserDashou.objects.select_for_update().get(user=user)
|
dashou = UserDashou.objects.select_for_update().get(user=user)
|
||||||
dashou.yajin += yajin_jine
|
dashou.yajin += yajin_jine
|
||||||
dashou.save(update_fields=['yajin'])
|
dashou.save(update_fields=['yajin'])
|
||||||
|
czjilu.zhuangtai = MEMBER_PAID_STATUS
|
||||||
|
czjilu.save(update_fields=['zhuangtai'])
|
||||||
response_data['yajin'] = dashou.yajin
|
response_data['yajin'] = dashou.yajin
|
||||||
except UserDashou.DoesNotExist:
|
except UserDashou.DoesNotExist:
|
||||||
return Response({'code': 500, 'msg': '打手信息不存在,无法充值押金'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
setattr(locked_profile, identity['balance_field'], current_balance)
|
||||||
|
locked_profile.save(update_fields=[identity['balance_field']])
|
||||||
|
czjilu.zhuangtai = MEMBER_FAILED_STATUS
|
||||||
|
czjilu.shuoming = f"{shuoming} | 履约失败:打手不存在"[:100]
|
||||||
|
czjilu.save(update_fields=['zhuangtai', 'shuoming'])
|
||||||
|
fulfill_error = MemberRechargeError('打手信息不存在,无法充值押金', 'dashou_not_found')
|
||||||
|
|
||||||
elif leixing == 4:
|
elif leixing == 4:
|
||||||
if fadan_obj.Status not in [1, 3]:
|
if fadan_obj.Status not in [1, 3]:
|
||||||
@@ -1082,14 +1102,29 @@ class DsqrgmdhView(APIView):
|
|||||||
new_jifen = dashou.jifen + INTEGRAL_INCREMENT
|
new_jifen = dashou.jifen + INTEGRAL_INCREMENT
|
||||||
dashou.jifen = min(new_jifen, MAX_INTEGRAL)
|
dashou.jifen = min(new_jifen, MAX_INTEGRAL)
|
||||||
dashou.save(update_fields=['jifen'])
|
dashou.save(update_fields=['jifen'])
|
||||||
|
czjilu.zhuangtai = MEMBER_PAID_STATUS
|
||||||
|
czjilu.save(update_fields=['zhuangtai'])
|
||||||
response_data['jifen'] = dashou.jifen
|
response_data['jifen'] = dashou.jifen
|
||||||
except UserDashou.DoesNotExist:
|
except UserDashou.DoesNotExist:
|
||||||
return Response({'code': 500, 'msg': '打手信息不存在,无法补充积分'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
setattr(locked_profile, identity['balance_field'], current_balance)
|
||||||
|
locked_profile.save(update_fields=[identity['balance_field']])
|
||||||
|
czjilu.zhuangtai = MEMBER_FAILED_STATUS
|
||||||
|
czjilu.shuoming = f"{shuoming} | 履约失败:打手不存在"[:100]
|
||||||
|
czjilu.save(update_fields=['zhuangtai', 'shuoming'])
|
||||||
|
fulfill_error = MemberRechargeError('打手信息不存在,无法补充积分', 'dashou_not_found')
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"事务处理异常: {str(e)}", exc_info=True)
|
logger.error(f"事务处理异常: {str(e)}", exc_info=True)
|
||||||
return Response({'code': 500, 'msg': f'支付处理失败: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
return Response({'code': 500, 'msg': f'支付处理失败: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
|
|
||||||
|
if fulfill_error:
|
||||||
|
logger.warning('余额抵扣履约失败 dingdan=%s err=%s', dingdan_id, fulfill_error)
|
||||||
|
return Response({
|
||||||
|
'code': 400,
|
||||||
|
'msg': str(fulfill_error),
|
||||||
|
'data': response_data,
|
||||||
|
}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
final_profile = identity['model'].query.get(user=user)
|
final_profile = identity['model'].query.get(user=user)
|
||||||
response_data['balance'] = str(getattr(final_profile, identity['balance_field']))
|
response_data['balance'] = str(getattr(final_profile, identity['balance_field']))
|
||||||
|
|
||||||
|
|||||||
@@ -168,8 +168,15 @@ class HuiyuanGoumai(APIView):
|
|||||||
try:
|
try:
|
||||||
pay_openid, _ = get_payment_openid(request)
|
pay_openid, _ = get_payment_openid(request)
|
||||||
if not pay_openid:
|
if not pay_openid:
|
||||||
chongzhi_jilu.delete()
|
from jituan.services.member_recharge import MEMBER_CANCELLED_STATUS
|
||||||
return Response({'code': 400, 'message': '用户openid不存在'}, status=status.HTTP_400_BAD_REQUEST)
|
chongzhi_jilu.zhuangtai = MEMBER_CANCELLED_STATUS
|
||||||
|
chongzhi_jilu.shuoming = f"{shuoming} | 下单失败:openid不存在"[:100]
|
||||||
|
chongzhi_jilu.save(update_fields=['zhuangtai', 'shuoming'])
|
||||||
|
return Response({
|
||||||
|
'code': 400,
|
||||||
|
'message': '用户openid不存在',
|
||||||
|
'dingdanid': dingdanid,
|
||||||
|
}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
pay_params = self.generate_wechat_pay_params(
|
pay_params = self.generate_wechat_pay_params(
|
||||||
dingdanid=dingdanid,
|
dingdanid=dingdanid,
|
||||||
jine=jine,
|
jine=jine,
|
||||||
@@ -177,8 +184,10 @@ class HuiyuanGoumai(APIView):
|
|||||||
pay_type='huiyuan'
|
pay_type='huiyuan'
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# 支付参数生成失败,删除订单记录
|
from jituan.services.member_recharge import MEMBER_CANCELLED_STATUS
|
||||||
chongzhi_jilu.delete()
|
chongzhi_jilu.zhuangtai = MEMBER_CANCELLED_STATUS
|
||||||
|
chongzhi_jilu.shuoming = f"{shuoming} | 下单失败:{str(e)[:40]}"[:100]
|
||||||
|
chongzhi_jilu.save(update_fields=['zhuangtai', 'shuoming'])
|
||||||
logger.error(f"生成会员支付参数失败: {str(e)}")
|
logger.error(f"生成会员支付参数失败: {str(e)}")
|
||||||
return Response({
|
return Response({
|
||||||
'code': 500,
|
'code': 500,
|
||||||
|
|||||||
@@ -290,26 +290,7 @@ class DashouHuiyuanList(APIView):
|
|||||||
)
|
)
|
||||||
} if _huiyuan_ids else {}
|
} if _huiyuan_ids else {}
|
||||||
|
|
||||||
# 批量预取 2:当前用户已购 Huiyuangoumai,按 huiyuan_id 索引
|
from jituan.services.member_recharge import can_purchase_trial
|
||||||
_goumai_map = {
|
|
||||||
gm.huiyuan_id: gm
|
|
||||||
for gm in Huiyuangoumai.query.filter(
|
|
||||||
yonghu_id=yonghuid,
|
|
||||||
huiyuan_id__in=_huiyuan_ids,
|
|
||||||
)
|
|
||||||
} if _huiyuan_ids else {}
|
|
||||||
|
|
||||||
# 批量预取 3:当前用户在本俱乐部已支付正式会员的 huiyuan_id 集合
|
|
||||||
_formal_paid_set = set(
|
|
||||||
Czjilu.query.filter(
|
|
||||||
yonghuid=yonghuid,
|
|
||||||
huiyuan_id__in=_huiyuan_ids,
|
|
||||||
leixing=1,
|
|
||||||
zhuangtai=3,
|
|
||||||
club_id=club_id,
|
|
||||||
is_trial=False,
|
|
||||||
).values_list('huiyuan_id', flat=True)
|
|
||||||
) if _huiyuan_ids else set()
|
|
||||||
|
|
||||||
def _local_sellable(cp_row, is_trial):
|
def _local_sellable(cp_row, is_trial):
|
||||||
"""单行 ClubHuiyuanPrice 计算 sellable/price/days(避免重复查询)。"""
|
"""单行 ClubHuiyuanPrice 计算 sellable/price/days(避免重复查询)。"""
|
||||||
@@ -330,17 +311,9 @@ class DashouHuiyuanList(APIView):
|
|||||||
return True, price, days
|
return True, price, days
|
||||||
|
|
||||||
def _local_can_trial(huiyuan_id):
|
def _local_can_trial(huiyuan_id):
|
||||||
"""基于已预取的数据判断体验会员是否可购。"""
|
"""与下单/履约共用 can_purchase_trial。"""
|
||||||
cp_row = _club_price_map.get(huiyuan_id)
|
ok, _ = can_purchase_trial(yonghuid, huiyuan_id, club_id)
|
||||||
trial_sellable, _, _ = _local_sellable(cp_row, is_trial=True)
|
return ok
|
||||||
if not trial_sellable:
|
|
||||||
return False
|
|
||||||
goumai = _goumai_map.get(huiyuan_id)
|
|
||||||
if goumai and goumai.has_used_trial:
|
|
||||||
return False
|
|
||||||
if huiyuan_id in _formal_paid_set:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
huiyuan_list = []
|
huiyuan_list = []
|
||||||
for huiyuan in huiyuan_queryset:
|
for huiyuan in huiyuan_queryset:
|
||||||
|
|||||||
Reference in New Issue
Block a user