This commit is contained in:
177
backend/shop/services.py
Normal file
177
backend/shop/services.py
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
import logging
|
||||||
|
from django.db import models
|
||||||
|
from .models import Order, CommissionLog, Distributor
|
||||||
|
# To avoid circular imports, import other models inside function if needed
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def handle_post_payment(order):
|
||||||
|
"""
|
||||||
|
处理订单支付成功后的业务逻辑
|
||||||
|
包括:
|
||||||
|
1. 更新活动报名状态
|
||||||
|
2. 发送活动报名短信
|
||||||
|
3. 计算分销佣金
|
||||||
|
4. 发送普通订单短信
|
||||||
|
"""
|
||||||
|
print(f"开始处理订单 {order.id} 支付后逻辑...")
|
||||||
|
|
||||||
|
# 1. Handle Activity Signup
|
||||||
|
if hasattr(order, 'activity') and order.activity:
|
||||||
|
try:
|
||||||
|
# Use apps.get_model to avoid circular dependency
|
||||||
|
from django.apps import apps
|
||||||
|
ActivitySignup = apps.get_model('community', 'ActivitySignup')
|
||||||
|
|
||||||
|
signup = ActivitySignup.objects.filter(order=order).first()
|
||||||
|
|
||||||
|
# Fallback: try to find by user and activity if not found by order
|
||||||
|
if not signup and order.wechat_user:
|
||||||
|
print(f"Warning: ActivitySignup not found by order {order.id}, trying by user/activity")
|
||||||
|
signup = ActivitySignup.objects.filter(
|
||||||
|
user=order.wechat_user,
|
||||||
|
activity=order.activity,
|
||||||
|
status='unpaid'
|
||||||
|
).first()
|
||||||
|
if signup:
|
||||||
|
print(f"Found signup {signup.id} by user/activity, linking order...")
|
||||||
|
signup.order = order
|
||||||
|
signup.save()
|
||||||
|
|
||||||
|
if signup:
|
||||||
|
# Determine status based on activity setting
|
||||||
|
# Use the model method if available, otherwise manual logic
|
||||||
|
if hasattr(signup, 'check_payment_status'):
|
||||||
|
signup.check_payment_status()
|
||||||
|
print(f"活动报名状态已更新(check_payment_status): {signup.id} -> {signup.status}")
|
||||||
|
else:
|
||||||
|
new_status = 'confirmed' if signup.activity.auto_confirm else 'pending'
|
||||||
|
signup.status = new_status
|
||||||
|
signup.save()
|
||||||
|
print(f"活动报名状态已更新: {signup.id} -> {new_status}")
|
||||||
|
|
||||||
|
# Send Activity SMS
|
||||||
|
try:
|
||||||
|
from .sms_utils import notify_user_activity_signup_success
|
||||||
|
notify_user_activity_signup_success(order, signup)
|
||||||
|
except Exception as sms_e:
|
||||||
|
print(f"发送活动报名短信失败: {str(sms_e)}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"Error: No ActivitySignup found for paid order {order.id}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"更新活动报名状态失败: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
# 2. 计算佣金 (旧版销售员系统 & 新版分销员系统)
|
||||||
|
try:
|
||||||
|
# 旧版销售员系统
|
||||||
|
salesperson = order.salesperson
|
||||||
|
if salesperson:
|
||||||
|
# 1. 计算直接佣金 (一级)
|
||||||
|
# 优先级: 产品独立分润比例 > 销售员个人分润比例
|
||||||
|
rate_1 = 0
|
||||||
|
if order.config:
|
||||||
|
rate_1 = order.config.commission_rate if order.config.commission_rate > 0 else salesperson.commission_rate
|
||||||
|
elif order.course:
|
||||||
|
# 课程暂时使用销售员默认比例
|
||||||
|
rate_1 = salesperson.commission_rate
|
||||||
|
|
||||||
|
amount_1 = order.total_price * rate_1
|
||||||
|
|
||||||
|
if amount_1 > 0:
|
||||||
|
CommissionLog.objects.create(
|
||||||
|
order=order,
|
||||||
|
salesperson=salesperson,
|
||||||
|
amount=amount_1,
|
||||||
|
level=1,
|
||||||
|
status='pending'
|
||||||
|
)
|
||||||
|
print(f"生成一级佣金(Salesperson): {salesperson.name} - {amount_1}")
|
||||||
|
|
||||||
|
# 2. 计算上级佣金 (二级)
|
||||||
|
parent = salesperson.parent
|
||||||
|
if parent:
|
||||||
|
rate_2 = parent.second_level_rate
|
||||||
|
amount_2 = order.total_price * rate_2
|
||||||
|
|
||||||
|
if amount_2 > 0:
|
||||||
|
CommissionLog.objects.create(
|
||||||
|
order=order,
|
||||||
|
salesperson=parent,
|
||||||
|
amount=amount_2,
|
||||||
|
level=2,
|
||||||
|
status='pending'
|
||||||
|
)
|
||||||
|
print(f"生成二级佣金(Salesperson): {parent.name} - {amount_2}")
|
||||||
|
|
||||||
|
# 新版分销员系统
|
||||||
|
distributor = order.distributor
|
||||||
|
if distributor:
|
||||||
|
# 1. 计算直接佣金 (一级)
|
||||||
|
# 优先级: 产品独立分润比例 > 分销员个人分润比例
|
||||||
|
rate_1 = 0
|
||||||
|
if order.config:
|
||||||
|
rate_1 = order.config.commission_rate if order.config.commission_rate > 0 else distributor.commission_rate
|
||||||
|
elif order.course:
|
||||||
|
# 课程暂时使用分销员默认比例
|
||||||
|
rate_1 = distributor.commission_rate
|
||||||
|
|
||||||
|
amount_1 = order.total_price * rate_1
|
||||||
|
|
||||||
|
if amount_1 > 0:
|
||||||
|
CommissionLog.objects.create(
|
||||||
|
order=order,
|
||||||
|
distributor=distributor,
|
||||||
|
amount=amount_1,
|
||||||
|
level=1,
|
||||||
|
status='settled' # 简化流程,直接结算到余额
|
||||||
|
)
|
||||||
|
# 更新余额
|
||||||
|
distributor.total_earnings += amount_1
|
||||||
|
distributor.withdrawable_balance += amount_1
|
||||||
|
distributor.save()
|
||||||
|
print(f"生成一级佣金(Distributor): {distributor.user.nickname} - {amount_1}")
|
||||||
|
|
||||||
|
# 2. 计算上级佣金 (二级)
|
||||||
|
parent = distributor.parent
|
||||||
|
if parent:
|
||||||
|
# 二级固定比例 2% (0.02)
|
||||||
|
rate_2 = 0.02
|
||||||
|
amount_2 = order.total_price * models.DecimalField(max_digits=5, decimal_places=4).to_python(rate_2)
|
||||||
|
|
||||||
|
if amount_2 > 0:
|
||||||
|
CommissionLog.objects.create(
|
||||||
|
order=order,
|
||||||
|
distributor=parent,
|
||||||
|
amount=amount_2,
|
||||||
|
level=2,
|
||||||
|
status='settled'
|
||||||
|
)
|
||||||
|
# 更新余额
|
||||||
|
parent.total_earnings += amount_2
|
||||||
|
parent.withdrawable_balance += amount_2
|
||||||
|
parent.save()
|
||||||
|
print(f"生成二级佣金(Distributor): {parent.user.nickname} - {amount_2}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"佣金计算失败: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
# 3. 发送普通商品/课程购买的短信通知(排除活动报名,避免重复发送)
|
||||||
|
# 活动报名的短信已经在上面发送过了
|
||||||
|
if not (hasattr(order, 'activity') and order.activity):
|
||||||
|
try:
|
||||||
|
from .sms_utils import notify_admins_order_paid, notify_user_order_paid
|
||||||
|
notify_admins_order_paid(order)
|
||||||
|
notify_user_order_paid(order)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"发送短信通知失败: {str(e)}")
|
||||||
|
else:
|
||||||
|
# 额外保险:如果是活动订单,手动标记不触发 signals 中的支付/发货通知
|
||||||
|
# 因为 signals 可能会在 save() 时触发
|
||||||
|
order._was_paid = False
|
||||||
|
order._was_shipped = False
|
||||||
@@ -17,7 +17,7 @@ def send_sms(phone_number, template_code, template_params):
|
|||||||
"phone_number": phone_number,
|
"phone_number": phone_number,
|
||||||
"template_code": template_code,
|
"template_code": template_code,
|
||||||
"sign_name": SIGN_NAME,
|
"sign_name": SIGN_NAME,
|
||||||
"additionalProp1": template_params
|
"template_params": template_params
|
||||||
}
|
}
|
||||||
headers = {
|
headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -113,7 +113,7 @@ def notify_user_activity_signup_success(order, signup):
|
|||||||
user_nick = order.wechat_user.nickname
|
user_nick = order.wechat_user.nickname
|
||||||
|
|
||||||
# 2. unit_name (Activity Title)
|
# 2. unit_name (Activity Title)
|
||||||
unit_name = signup.activity.title
|
unit_name = f"【{signup.activity.title}】"
|
||||||
|
|
||||||
# 3. time
|
# 3. time
|
||||||
start_time = signup.activity.start_time
|
start_time = signup.activity.start_time
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiPara
|
|||||||
from .models import ESP32Config, Order, WeChatPayConfig, Service, VCCourse, ServiceOrder, Salesperson, CommissionLog, WeChatUser, Distributor, Withdrawal, CourseEnrollment
|
from .models import ESP32Config, Order, WeChatPayConfig, Service, VCCourse, ServiceOrder, Salesperson, CommissionLog, WeChatUser, Distributor, Withdrawal, CourseEnrollment
|
||||||
from .serializers import ESP32ConfigSerializer, OrderSerializer, ServiceSerializer, VCCourseSerializer, ServiceOrderSerializer, WeChatUserSerializer, DistributorSerializer, WithdrawalSerializer, CommissionLogSerializer, CourseEnrollmentSerializer
|
from .serializers import ESP32ConfigSerializer, OrderSerializer, ServiceSerializer, VCCourseSerializer, ServiceOrderSerializer, WeChatUserSerializer, DistributorSerializer, WithdrawalSerializer, CommissionLogSerializer, CourseEnrollmentSerializer
|
||||||
from .utils import get_access_token
|
from .utils import get_access_token
|
||||||
|
from .services import handle_post_payment
|
||||||
from django.db import transaction, models
|
from django.db import transaction, models
|
||||||
from django.core.signing import TimestampSigner, BadSignature, SignatureExpired
|
from django.core.signing import TimestampSigner, BadSignature, SignatureExpired
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
@@ -507,166 +508,9 @@ def payment_finish(request):
|
|||||||
order.wechat_trade_no = transaction_id
|
order.wechat_trade_no = transaction_id
|
||||||
order.save()
|
order.save()
|
||||||
print(f"订单 {order.id} 状态已更新")
|
print(f"订单 {order.id} 状态已更新")
|
||||||
|
|
||||||
# Handle Activity Signup
|
# 6. 处理支付后业务逻辑 (活动报名、佣金、短信通知)
|
||||||
if hasattr(order, 'activity') and order.activity:
|
handle_post_payment(order)
|
||||||
try:
|
|
||||||
# Use apps.get_model to avoid circular dependency
|
|
||||||
from django.apps import apps
|
|
||||||
ActivitySignup = apps.get_model('community', 'ActivitySignup')
|
|
||||||
|
|
||||||
signup = ActivitySignup.objects.filter(order=order).first()
|
|
||||||
|
|
||||||
# Fallback: try to find by user and activity if not found by order
|
|
||||||
if not signup and order.wechat_user:
|
|
||||||
print(f"Warning: ActivitySignup not found by order {order.id}, trying by user/activity")
|
|
||||||
signup = ActivitySignup.objects.filter(
|
|
||||||
user=order.wechat_user,
|
|
||||||
activity=order.activity,
|
|
||||||
status='unpaid'
|
|
||||||
).first()
|
|
||||||
if signup:
|
|
||||||
print(f"Found signup {signup.id} by user/activity, linking order...")
|
|
||||||
signup.order = order
|
|
||||||
signup.save()
|
|
||||||
|
|
||||||
if signup:
|
|
||||||
# Determine status based on activity setting
|
|
||||||
# Use the model method if available, otherwise manual logic
|
|
||||||
if hasattr(signup, 'check_payment_status'):
|
|
||||||
signup.check_payment_status()
|
|
||||||
print(f"活动报名状态已更新(check_payment_status): {signup.id} -> {signup.status}")
|
|
||||||
else:
|
|
||||||
new_status = 'confirmed' if signup.activity.auto_confirm else 'pending'
|
|
||||||
signup.status = new_status
|
|
||||||
signup.save()
|
|
||||||
print(f"活动报名状态已更新: {signup.id} -> {new_status}")
|
|
||||||
|
|
||||||
# Send Activity SMS
|
|
||||||
try:
|
|
||||||
from .sms_utils import notify_user_activity_signup_success
|
|
||||||
notify_user_activity_signup_success(order, signup)
|
|
||||||
except Exception as sms_e:
|
|
||||||
print(f"发送活动报名短信失败: {str(sms_e)}")
|
|
||||||
|
|
||||||
else:
|
|
||||||
print(f"Error: No ActivitySignup found for paid order {order.id}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"更新活动报名状态失败: {str(e)}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
# 计算佣金 (旧版销售员系统)
|
|
||||||
try:
|
|
||||||
salesperson = order.salesperson
|
|
||||||
if salesperson:
|
|
||||||
# 1. 计算直接佣金 (一级)
|
|
||||||
# 优先级: 产品独立分润比例 > 销售员个人分润比例
|
|
||||||
rate_1 = 0
|
|
||||||
if order.config:
|
|
||||||
rate_1 = order.config.commission_rate if order.config.commission_rate > 0 else salesperson.commission_rate
|
|
||||||
elif order.course:
|
|
||||||
# 课程暂时使用销售员默认比例
|
|
||||||
rate_1 = salesperson.commission_rate
|
|
||||||
|
|
||||||
amount_1 = order.total_price * rate_1
|
|
||||||
|
|
||||||
if amount_1 > 0:
|
|
||||||
CommissionLog.objects.create(
|
|
||||||
order=order,
|
|
||||||
salesperson=salesperson,
|
|
||||||
amount=amount_1,
|
|
||||||
level=1,
|
|
||||||
status='pending'
|
|
||||||
)
|
|
||||||
print(f"生成一级佣金(Salesperson): {salesperson.name} - {amount_1}")
|
|
||||||
|
|
||||||
# 2. 计算上级佣金 (二级)
|
|
||||||
parent = salesperson.parent
|
|
||||||
if parent:
|
|
||||||
rate_2 = parent.second_level_rate
|
|
||||||
amount_2 = order.total_price * rate_2
|
|
||||||
|
|
||||||
if amount_2 > 0:
|
|
||||||
CommissionLog.objects.create(
|
|
||||||
order=order,
|
|
||||||
salesperson=parent,
|
|
||||||
amount=amount_2,
|
|
||||||
level=2,
|
|
||||||
status='pending'
|
|
||||||
)
|
|
||||||
print(f"生成二级佣金(Salesperson): {parent.name} - {amount_2}")
|
|
||||||
|
|
||||||
# 计算佣金 (新版分销员系统)
|
|
||||||
distributor = order.distributor
|
|
||||||
if distributor:
|
|
||||||
# 1. 计算直接佣金 (一级)
|
|
||||||
# 优先级: 产品独立分润比例 > 分销员个人分润比例
|
|
||||||
rate_1 = 0
|
|
||||||
if order.config:
|
|
||||||
rate_1 = order.config.commission_rate if order.config.commission_rate > 0 else distributor.commission_rate
|
|
||||||
elif order.course:
|
|
||||||
# 课程暂时使用分销员默认比例
|
|
||||||
rate_1 = distributor.commission_rate
|
|
||||||
|
|
||||||
amount_1 = order.total_price * rate_1
|
|
||||||
|
|
||||||
if amount_1 > 0:
|
|
||||||
CommissionLog.objects.create(
|
|
||||||
order=order,
|
|
||||||
distributor=distributor,
|
|
||||||
amount=amount_1,
|
|
||||||
level=1,
|
|
||||||
status='settled' # 简化流程,直接结算到余额
|
|
||||||
)
|
|
||||||
# 更新余额
|
|
||||||
distributor.total_earnings += amount_1
|
|
||||||
distributor.withdrawable_balance += amount_1
|
|
||||||
distributor.save()
|
|
||||||
print(f"生成一级佣金(Distributor): {distributor.user.nickname} - {amount_1}")
|
|
||||||
|
|
||||||
# 2. 计算上级佣金 (二级)
|
|
||||||
parent = distributor.parent
|
|
||||||
if parent:
|
|
||||||
# 二级固定比例 2% (0.02)
|
|
||||||
rate_2 = 0.02
|
|
||||||
amount_2 = order.total_price * models.DecimalField(max_digits=5, decimal_places=4).to_python(rate_2)
|
|
||||||
|
|
||||||
if amount_2 > 0:
|
|
||||||
CommissionLog.objects.create(
|
|
||||||
order=order,
|
|
||||||
distributor=parent,
|
|
||||||
amount=amount_2,
|
|
||||||
level=2,
|
|
||||||
status='settled'
|
|
||||||
)
|
|
||||||
# 更新余额
|
|
||||||
parent.total_earnings += amount_2
|
|
||||||
parent.withdrawable_balance += amount_2
|
|
||||||
parent.save()
|
|
||||||
print(f"生成二级佣金(Distributor): {parent.user.nickname} - {amount_2}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"佣金计算失败: {str(e)}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
# 发送普通商品/课程购买的短信通知(排除活动报名,避免重复发送)
|
|
||||||
# 活动报名的短信已经在上面发送过了
|
|
||||||
if not (hasattr(order, 'activity') and order.activity):
|
|
||||||
try:
|
|
||||||
from .sms_utils import notify_admins_order_paid, notify_user_order_paid
|
|
||||||
notify_admins_order_paid(order)
|
|
||||||
notify_user_order_paid(order)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"发送短信通知失败: {str(e)}")
|
|
||||||
else:
|
|
||||||
# 额外保险:如果是活动订单,手动标记不触发 signals 中的支付/发货通知
|
|
||||||
# 因为 signals 可能会在 save() 时触发
|
|
||||||
order._was_paid = False
|
|
||||||
order._was_shipped = False
|
|
||||||
# order.save() # 不需要再 save,因为已经是 post-save 或者不影响数据库的标记
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"订单更新失败: {str(e)}")
|
print(f"订单更新失败: {str(e)}")
|
||||||
@@ -1044,6 +888,10 @@ class OrderViewSet(viewsets.ModelViewSet):
|
|||||||
order.status = 'paid'
|
order.status = 'paid'
|
||||||
order.wechat_trade_no = result.get('transaction_id')
|
order.wechat_trade_no = result.get('transaction_id')
|
||||||
order.save()
|
order.save()
|
||||||
|
|
||||||
|
# 处理支付后逻辑
|
||||||
|
handle_post_payment(order)
|
||||||
|
|
||||||
return Response({'status': 'paid', 'message': '支付成功', 'detail': result})
|
return Response({'status': 'paid', 'message': '支付成功', 'detail': result})
|
||||||
|
|
||||||
return Response({'status': 'pending', 'trade_state': trade_state, 'message': result.get('trade_state_desc')})
|
return Response({'status': 'pending', 'trade_state': trade_state, 'message': result.get('trade_state_desc')})
|
||||||
@@ -1062,6 +910,9 @@ class OrderViewSet(viewsets.ModelViewSet):
|
|||||||
order.status = 'paid'
|
order.status = 'paid'
|
||||||
order.wechat_trade_no = f"WX_{str(uuid.uuid4())[:18]}"
|
order.wechat_trade_no = f"WX_{str(uuid.uuid4())[:18]}"
|
||||||
order.save()
|
order.save()
|
||||||
|
|
||||||
|
handle_post_payment(order)
|
||||||
|
|
||||||
return Response({'status': 'success', 'message': '支付成功'})
|
return Response({'status': 'success', 'message': '支付成功'})
|
||||||
|
|
||||||
def get_current_wechat_user(request):
|
def get_current_wechat_user(request):
|
||||||
|
|||||||
Reference in New Issue
Block a user