From 3f363dbd8ef6536f39baa5cb0f2faa89846b0a91 Mon Sep 17 00:00:00 2001 From: jeremygan2021 Date: Mon, 23 Feb 2026 18:01:56 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E7=AB=AF=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/shop/services.py | 177 ++++++++++++++++++++++++++++++++++++++ backend/shop/sms_utils.py | 4 +- backend/shop/views.py | 171 +++--------------------------------- 3 files changed, 190 insertions(+), 162 deletions(-) create mode 100644 backend/shop/services.py diff --git a/backend/shop/services.py b/backend/shop/services.py new file mode 100644 index 0000000..cd68c9b --- /dev/null +++ b/backend/shop/services.py @@ -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 diff --git a/backend/shop/sms_utils.py b/backend/shop/sms_utils.py index 6c30664..e6cc301 100644 --- a/backend/shop/sms_utils.py +++ b/backend/shop/sms_utils.py @@ -17,7 +17,7 @@ def send_sms(phone_number, template_code, template_params): "phone_number": phone_number, "template_code": template_code, "sign_name": SIGN_NAME, - "additionalProp1": template_params + "template_params": template_params } headers = { "Content-Type": "application/json", @@ -113,7 +113,7 @@ def notify_user_activity_signup_success(order, signup): user_nick = order.wechat_user.nickname # 2. unit_name (Activity Title) - unit_name = signup.activity.title + unit_name = f"【{signup.activity.title}】" # 3. time start_time = signup.activity.start_time diff --git a/backend/shop/views.py b/backend/shop/views.py index 48cdf90..f50ba25 100644 --- a/backend/shop/views.py +++ b/backend/shop/views.py @@ -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 .serializers import ESP32ConfigSerializer, OrderSerializer, ServiceSerializer, VCCourseSerializer, ServiceOrderSerializer, WeChatUserSerializer, DistributorSerializer, WithdrawalSerializer, CommissionLogSerializer, CourseEnrollmentSerializer from .utils import get_access_token +from .services import handle_post_payment from django.db import transaction, models from django.core.signing import TimestampSigner, BadSignature, SignatureExpired from django.contrib.auth.models import User @@ -507,166 +508,9 @@ def payment_finish(request): order.wechat_trade_no = transaction_id order.save() print(f"订单 {order.id} 状态已更新") - - # 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() - - # 计算佣金 (旧版销售员系统) - 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 或者不影响数据库的标记 + + # 6. 处理支付后业务逻辑 (活动报名、佣金、短信通知) + handle_post_payment(order) except Exception as e: print(f"订单更新失败: {str(e)}") @@ -1044,6 +888,10 @@ class OrderViewSet(viewsets.ModelViewSet): order.status = 'paid' order.wechat_trade_no = result.get('transaction_id') order.save() + + # 处理支付后逻辑 + handle_post_payment(order) + return Response({'status': 'paid', 'message': '支付成功', 'detail': result}) 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.wechat_trade_no = f"WX_{str(uuid.uuid4())[:18]}" order.save() + + handle_post_payment(order) + return Response({'status': 'success', 'message': '支付成功'}) def get_current_wechat_user(request):