"""orders.views.payment - auto-generated by split script.""" # ==================== 标准库 ==================== import hmac import os import json import time import random import string import traceback import threading import requests import hashlib import xml.etree.ElementTree as ET import xmltodict import logging from decimal import Decimal from django.conf import settings from django.db import models, transaction from gvsdsdk.fluent import db, func, FQ from django.db.models import F, Q, OuterRef, Subquery from django.core.cache import cache from django.core.exceptions import ObjectDoesNotExist from django.http import HttpResponse from django.utils import timezone from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import permissions, status from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.parsers import JSONParser from rest_framework.throttling import AnonRateThrottle from rest_framework_simplejwt.authentication import JWTAuthentication from tencentcloud.common import credential from tencentcloud.common.profile.client_profile import ClientProfile from tencentcloud.common.profile.http_profile import HttpProfile from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException from tencentcloud.sts.v20180813 import sts_client, models from utils.weixin_broadcast import WeixinBroadcastSender from utils.money import yuan_to_fen from utils.chat_utils import ( _send_group_message, _subscribe_users_to_group, establish_order_chat, prepare_order_group_chat, resolve_pair_group_id, parse_pair_from_group_id, resolve_pair_from_order, check_pair_chat_permission, query_pair_orders, order_to_chat_dict, ) from utils.fadan_utils import check_fadan_qiangdan_eligible from orders.utils import ( calc_shangjia_order_fencheng, update_daily_payout, settle_shangjia_order_guanshi_fenhong ) from shop.utils import calculate_pingtai_and_dianpu_shouyi, validate_shangpin_and_dianpu, update_dianpu_daily_stat from products.utils import update_shangpin_daily_stat from backend.utils import update_shangjia_daily, update_dashou_daily_by_action, pick_leixing_id, datetime_aliases, fmt_datetime from rank.services import record_dashou_biaoxian from rank.utils import check_dashou_biaoqian_required from orders.notice_tasks import dingdan_guangbo from ..models import ( Order, MerchantOrderExt, PlatformOrderExt, PlayerDeliveryImage, PenaltyRecord, PenaltyEvidenceImage, Penalty, PenaltyAppealImage, PenaltyBonus, PenaltyBonusRate, CommissionRate, PlayerRating, RefundRecord, ) from jituan.services.club_config import get_commission_rate, get_commission_rate_object from jituan.services.club_context import resolve_club_id_from_request from jituan.services.club_user import get_payment_openid from jituan.services.club_penalty import resolve_penalty_club_id, resolve_penalty_record_club_id from users.fadan_fenhong_utils import lock_penalty_bonus from users.fadan_fenhong_utils import lock_penalty_bonus from products.models import Shangpin, ShangpinLeixing, Huiyuangoumai from users.models import UserDashou, UserShangjia, UserBoss from users.business_models import User from rank.models import DashouBiaoxian, Chenghao, DingdanBiaoqian, YonghuChenghao from config.models import ( ShangjiaLianjie ) logger = logging.getLogger(__name__) class PaymentFailView(APIView): """ 前端支付失败接口 - 处理用户主动取消或支付失败 只允许删除状态为9(未支付)的订单 """ permission_classes = [permissions.IsAuthenticated] def post(self, request): try: dingdanid = request.data.get('dingdanid') if not dingdanid: return Response({'code': 1, 'msg': '订单ID不能为空', 'data': None}) with transaction.atomic(): # 锁定订单防止并发 dingdan = Order.query.select_for_update().filter(OrderID=dingdanid).first() if not dingdan: return Response({'code': 2, 'msg': '订单不存在', 'data': None}) if dingdan.User1ID != f"Boss{request.user.UserUID}": return Response({'code': 3, 'msg': '无权操作此订单', 'data': None}) # 只允许删除未支付订单(状态9) if dingdan.Status != 9: return Response({'code': 3, 'msg': '只能删除未支付的订单', 'data': None}) # 删除扩展表和主表 PlatformOrderExt.query.filter(Order=dingdan).delete() dingdan.delete() return Response({'code': 0, 'msg': '订单已删除', 'data': None}) except Exception as e: return Response({'code': 99, 'msg': f'删除订单失败: {str(e)}', 'data': None}) class WechatPayNotifyView(APIView): """微信支付回调接口 - 接收微信官方支付结果通知(含详细调试日志)""" throttle_classes = [AnonRateThrottle] permission_classes = [AllowAny] def post(self, request): # 记录原始请求数据(调试用) xml_data = request.body.decode('utf-8') # 最外层异常捕获,确保任何错误都能被记录并返回适当响应 try: # 1. 解析 XML try: root = ET.fromstring(xml_data) except ET.ParseError as e: logger.error(f"XML解析失败: {e}") return self._gen_response_xml('FAIL', 'XML解析失败') # 2. 获取关键参数 return_code = root.find('return_code').text if root.find('return_code') is not None else '' result_code = root.find('result_code').text if root.find('result_code') is not None else '' out_trade_no = root.find('out_trade_no').text if root.find('out_trade_no') is not None else '' transaction_id = root.find('transaction_id').text if root.find('transaction_id') is not None else '' from jituan.services.wechat_pay import club_id_from_order, verify_wechat_v2_signature notify_club_id = club_id_from_order(out_trade_no) # 3. 签名验证 if not verify_wechat_v2_signature(xml_data, notify_club_id): logger.warning("签名验证失败") return self._gen_response_xml('FAIL', '签名验证失败') # 4. 根据支付结果处理 if return_code == 'SUCCESS' and result_code == 'SUCCESS': self._handle_success(out_trade_no, transaction_id, root) else: logger.info(f"支付失败或异常,return_code={return_code}, result_code={result_code}") self._handle_failure(out_trade_no) # 返回微信要求的成功响应(必须返回SUCCESS,即使内部处理失败也要返回,否则微信会重复通知) return self._gen_response_xml('SUCCESS', 'OK') except Exception as e: # 捕获任何未处理的异常,记录完整堆栈 logger.error(f"回调处理发生未捕获异常: {str(e)}") logger.error(traceback.format_exc()) return self._gen_response_xml('FAIL', f'服务器内部错误: {str(e)}') def _verify_signature(self, xml_data): """微信支付签名验证(MD5)""" try: root = ET.fromstring(xml_data) sign = root.find('sign').text # 构建待签名字符串(排除sign字段) data = {child.tag: child.text for child in root if child.tag != 'sign'} # 按key排序 sorted_keys = sorted(data.keys()) stringA = '&'.join([f"{k}={data[k]}" for k in sorted_keys]) stringSignTemp = f"{stringA}&key={settings.WEIXIN_SHANGHUMIYAO}" calc_sign = hashlib.md5(stringSignTemp.encode('utf-8')).hexdigest().upper() logger.debug(f"计算签名: {calc_sign}, 微信签名: {sign}") return calc_sign == sign except Exception as e: logger.error(f"签名验证过程异常: {e}") logger.error(traceback.format_exc()) return False def _gen_response_xml(self, return_code, return_msg): """生成返回给微信的XML响应""" xml = f""" """ logger.info(f"返回给微信的响应: {xml}") return HttpResponse(xml, content_type='application/xml') @transaction.atomic def _handle_success(self, out_trade_no, transaction_id, root): """处理支付成功""" logger.info(f"进入 _handle_success, out_trade_no={out_trade_no}, transaction_id={transaction_id}") # 锁定订单防止并发 try: dingdan = Order.query.select_for_update().select_related( 'pingtai_kuozhan', 'shangjia_kuozhan' ).filter(OrderID=out_trade_no).first() except Exception as e: logger.error(f"查询订单异常: {e}") raise if not dingdan: logger.error(f"订单不存在: {out_trade_no}") return logger.info(f"订单当前状态: {dingdan.Status}") # 幂等:已支付则补试收支记账(防止首次入账失败后再也无法统计) if dingdan.Status != 9: logger.info(f"订单 {out_trade_no} 已处理过,状态={dingdan.Status},补试收支记账") try: from jituan.services.szjilu_accounting import repair_wechat_income_if_missing repair_wechat_income_if_missing( dingdan.Amount, getattr(dingdan, 'ClubID', None), biz_ref=f'order:{dingdan.OrderID}', ) except Exception as e: logger.error(f'订单补记收支失败 {out_trade_no}: {e}', exc_info=True) return # 保存微信交易号 if transaction_id: dingdan.WechatTransactionID = transaction_id logger.info(f"保存微信交易号: {transaction_id}") else: logger.warning("微信未返回transaction_id") # 更新订单状态 if dingdan.AssignedID and dingdan.AssignedID.strip(): dingdan.Status = 7 logger.info("指定单,状态设为7") else: dingdan.Status = 1 logger.info("普通单,状态设为1") dingdan.save() logger.info(f"订单状态已更新为 {dingdan.Status}") # 删除同一用户同一商品的其他未支付订单(改为逻辑删除,状态25) try: abandoned_count = Order.query.filter( User1ID=dingdan.User1ID, ProductID=dingdan.ProductID, Status=9 ).exclude(OrderID=out_trade_no).update(Status=25) logger.info(f"将 {abandoned_count} 个未支付订单标记为已废弃(25)") except Exception as e: logger.error(f"标记其他未支付订单异常: {e}") # 更新老板扩展表 try: laoban_id = None if hasattr(dingdan, 'pingtai_kuozhan'): laoban_id = dingdan.pingtai_kuozhan.BossID if laoban_id: user_main = User.query.filter(UserUID=laoban_id).select_related('BossProfile').first() if user_main and hasattr(user_main, 'BossProfile'): boss = user_main.BossProfile boss.zonge = (boss.zonge or 0) + (dingdan.Amount or 0) boss.alldingdan = (boss.alldingdan or 0) + 1 boss.save() logger.info(f"更新老板 {laoban_id} 数据成功") except Exception as e: logger.error(f"更新老板数据异常: {e}") # 更新商品库存 try: if dingdan.ProductID: Shangpin.query.filter(id=dingdan.ProductID).update( kucun=F('kucun') - 1 ) logger.info(f"商品 {dingdan.ProductID} 库存减1") except Exception as e: logger.error(f"更新商品库存异常: {e}") # ========== 新增:商品和店铺每日统计 ========== try: # 商品每日统计(只要有商品ID就执行) if dingdan.ProductID: # 从扩展表获取已计算好的收益值(下单时已存储) pingtai_shouyi = Decimal('0.00') dianpu_shouyi = Decimal('0.00') if hasattr(dingdan, 'pingtai_kuozhan'): pingtai_shouyi = dingdan.pingtai_kuozhan.PlatformIncome or Decimal('0.00') dianpu_shouyi = dingdan.pingtai_kuozhan.ShopIncome or Decimal('0.00') update_shangpin_daily_stat( ProductID=dingdan.ProductID, caozuo_leixing=1, # 1 = 下单 dingdan_jiage=dingdan.Amount, PlatformIncome=pingtai_shouyi, dianpu_zongshouyi=dianpu_shouyi ) logger.info(f"商品统计更新完成,商品ID: {dingdan.ProductID}") # 店铺每日统计(仅当存在店铺ID时执行) dianpu_id = None if hasattr(dingdan, 'pingtai_kuozhan'): dianpu_id = dingdan.pingtai_kuozhan.ShopID if dianpu_id: dianpu_shouyi = dingdan.pingtai_kuozhan.ShopIncome or Decimal('0.00') update_dianpu_daily_stat( ShopID=dianpu_id, caozuo_leixing=1, # 1 = 下单 dingdan_jiage=dingdan.Amount, ShopIncome=dianpu_shouyi ) logger.info(f"店铺统计更新完成,店铺ID: {dianpu_id}") except Exception as e: logger.error(f"商品/店铺统计更新失败: {e}") logger.error(traceback.format_exc()) # 统计失败不影响主流程 # ============================================= # 更新收支记录(总累计);统计失败不得回滚订单支付 try: from jituan.services.szjilu_accounting import repair_wechat_income_if_missing repair_wechat_income_if_missing( dingdan.Amount, getattr(dingdan, 'ClubID', None), biz_ref=f'order:{dingdan.OrderID}', ) except Exception as e: logger.error(f'订单收支统计失败 {out_trade_no}: {e}', exc_info=True) # 构建通知数据(你的 _get_game_type_name 方法保留在视图里直接用) order_info = { 'OrderID': dingdan.OrderID, 'game_type': self._get_game_type_name(dingdan.ProductTypeID), 'amount': str(dingdan.Amount), 'order_desc': (dingdan.Description or '')[:50], } dingdan_guangbo.delay(order_info) # 瞬间返回,不阻塞请求 # 异步广播通知 #self._broadcast_async(dingdan) logger.info(f"订单 {out_trade_no} 支付成功处理完成") @transaction.atomic def _handle_failure(self, out_trade_no): """处理支付失败""" logger.info(f"进入 _handle_failure, out_trade_no={out_trade_no}") try: dingdan = Order.query.select_for_update().filter(OrderID=out_trade_no).first() if not dingdan: logger.warning(f"订单不存在: {out_trade_no}") return logger.info(f"订单当前状态: {dingdan.Status}") # 仅当订单状态为9(未支付)时才标记失败,不直接删除 if dingdan.Status == 9: dingdan.Status = 14 # 自定义支付失败状态 dingdan.save() logger.info(f"订单 {out_trade_no} 支付失败,标记为14") else: logger.info(f"订单 {out_trade_no} 状态非9,不处理") except Exception as e: logger.error(f"处理支付失败异常: {e}") logger.error(traceback.format_exc()) def _update_szjilu(self, jine, club_id=None): """szjilu 已废弃,财务只写 daily_income_stat,此处不再操作。""" pass def _broadcast_async(self, dingdan): """异步广播通知""" try: def task(): try: order_info = { 'OrderID': dingdan.OrderID, 'game_type': self._get_game_type_name(dingdan.ProductTypeID), 'amount': str(dingdan.Amount), 'order_desc': (dingdan.Description or '')[:50], } logger.info(f"准备广播订单: {order_info}") sender = WeixinBroadcastSender() result = sender.broadcast_order(order_info) if result.get('success'): logger.info(f"订单广播完成: {result.get('msg')}") else: logger.warning(f"订单广播异常: {result.get('msg')}") except Exception as e: logger.error(f"广播任务异常: {e}") logger.error(traceback.format_exc()) thread = threading.Thread(target=task) thread.daemon = True thread.start() logger.info("广播线程已启动") except Exception as e: logger.error(f"启动广播线程异常: {e}") def _get_game_type_name(self, leixing_id): try: gt = ShangpinLeixing.query.filter(id=leixing_id).first() return gt.jieshao if gt else '游戏订单' except Exception as e: logger.error(f"获取游戏类型名称异常: {e}") return '游戏订单' class PaymentVerifyView(APIView): """ 订单支付成功复查接口 - 前端支付成功后调用,确认订单状态 当订单状态为9时,主动查询微信支付状态,若已支付则更新本地状态 需要token认证 """ permission_classes = [permissions.IsAuthenticated] parser_classes = [JSONParser] def post(self, request): try: # ---------- 1. 获取参数 ---------- dingdanid = request.data.get('dingdanid') if not dingdanid: return Response({'code': 1, 'msg': '订单ID不能为空'}) # ---------- 2. 查询订单 ---------- dingdan = Order.query.filter(OrderID=dingdanid).first() if not dingdan: return Response({'code': 2, 'msg': '订单不存在'}) # ---------- 3. 验证订单归属(安全) ---------- current_user = request.user user_identifier = f"Boss{current_user.UserUID}" if dingdan.User1ID != user_identifier: logger.warning(f"用户 {current_user.UserUID} 尝试复查他人订单 {dingdanid}") return Response({'code': 403, 'msg': '无权访问'}) # ---------- 4. 如果订单状态已是1/7,直接返回成功 ---------- if dingdan.Status in [1, 7]: return Response({ 'code': 0, 'msg': '订单状态正常', 'data': {'dingdanid': dingdanid, 'zhuangtai': dingdan.Status} }) # ---------- 5. 如果状态为9,则主动查询微信支付状态 ---------- if dingdan.Status == 9: # 使用分布式锁防止并发处理同一订单 lock_key = f"payment_verify_lock_{dingdanid}" if not cache.add(lock_key, "locked", timeout=10): # 已有其他进程在处理,让前端稍后重试 return Response({'code': 1, 'msg': '订单正在处理中,请稍后'}) try: # 调用微信订单查询接口 trade_state, transaction_id = self._query_wechat_payment(dingdanid) if trade_state == 'SUCCESS': # 支付成功,执行本地更新 self._process_payment_success(dingdan, transaction_id, current_user.UserUID) # 重新获取订单以获取最新状态 dingdan.refresh_from_db() return Response({ 'code': 0, 'msg': '支付成功', 'data': {'dingdanid': dingdanid, 'zhuangtai': dingdan.Status} }) elif trade_state in ['NOTPAY', 'USERPAYING']: # 用户尚未支付或支付中 return Response({'code': 1, 'msg': '支付中', 'data': {'zhuangtai': 9}}) else: # 其他状态:CLOSED, REVOKED, PAYERROR 等,视为支付失败 return Response({'code': 2, 'msg': '支付失败'}) except Exception as e: logger.error(f"查询微信支付异常: {e}", exc_info=True) return Response({'code': 1, 'msg': '查询失败,请稍后重试'}) finally: cache.delete(lock_key) else: # ---------- 6. 其他异常状态 ---------- status_msg = { 2: '订单已取消', 3: '订单已完成', 4: '退款审核中', 5: '已退款', 6: '退款失败', 8: '结算中', 0: '订单已作废', }.get(dingdan.Status, '订单状态异常') return Response({'code': 3, 'msg': status_msg, 'data': {'zhuangtai': dingdan.Status}}) except Exception as e: logger.error(f"订单复查接口异常: {e}", exc_info=True) return Response({'code': 99, 'msg': f'系统错误: {str(e)}'}) def _process_payment_success(self, dingdan, transaction_id, operator_id): """ 支付成功后的原子操作(与微信回调逻辑完全一致) 在事务内完成所有状态更新和数据一致性操作 """ with transaction.atomic(): # 重新锁定订单,防止并发更新 dingdan = Order.query.select_for_update().get(pk=dingdan.pk) if dingdan.Status != 9: try: from jituan.services.szjilu_accounting import repair_wechat_income_if_missing repair_wechat_income_if_missing( dingdan.Amount, getattr(dingdan, 'ClubID', None), biz_ref=f'order:{dingdan.OrderID}', ) except Exception as e: logger.error(f'订单复查补记收支失败 {dingdan.OrderID}: {e}', exc_info=True) return # 更新订单状态 dingdan.Status = 7 if dingdan.AssignedID and dingdan.AssignedID.strip() else 1 dingdan.WechatTransactionID = transaction_id dingdan.AssignedCS = operator_id # 可选:记录操作人ID dingdan.save() # 删除同一用户同一商品的其他未支付订单(逻辑删除,状态25) Order.query.filter( User1ID=dingdan.User1ID, ProductID=dingdan.ProductID, Status=9 ).exclude(OrderID=dingdan.OrderID).update(Status=25) # 更新老板扩展表 laoban_id = dingdan.pingtai_kuozhan.BossID if hasattr(dingdan, 'pingtai_kuozhan') else None if laoban_id: user_main = User.query.filter(UserUID=laoban_id).select_related('BossProfile').first() if user_main and hasattr(user_main, 'BossProfile'): boss = user_main.BossProfile boss.zonge = (boss.zonge or 0) + (dingdan.Amount or 0) boss.alldingdan = (boss.alldingdan or 0) + 1 boss.save() # 更新商品库存 if dingdan.ProductID: Shangpin.query.filter(id=dingdan.ProductID).update( kucun=F('kucun') - 1 ) # ========== 新增:商品和店铺每日统计 ========== try: # 商品每日统计(只要有商品ID就执行) if dingdan.ProductID: # 从扩展表获取已计算好的收益值(下单时已存储) pingtai_shouyi = Decimal('0.00') dianpu_shouyi = Decimal('0.00') if hasattr(dingdan, 'pingtai_kuozhan'): pingtai_shouyi = dingdan.pingtai_kuozhan.PlatformIncome or Decimal('0.00') dianpu_shouyi = dingdan.pingtai_kuozhan.ShopIncome or Decimal('0.00') update_shangpin_daily_stat( ProductID=dingdan.ProductID, caozuo_leixing=1, # 1 = 下单 dingdan_jiage=dingdan.Amount, PlatformIncome=pingtai_shouyi, dianpu_zongshouyi=dianpu_shouyi ) logger.info(f"商品统计更新完成,商品ID: {dingdan.ProductID}") # 店铺每日统计(仅当存在店铺ID时执行) dianpu_id = None if hasattr(dingdan, 'pingtai_kuozhan'): dianpu_id = dingdan.pingtai_kuozhan.ShopID if dianpu_id: dianpu_shouyi = dingdan.pingtai_kuozhan.ShopIncome or Decimal('0.00') update_dianpu_daily_stat( ShopID=dianpu_id, caozuo_leixing=1, # 1 = 下单 dingdan_jiage=dingdan.Amount, ShopIncome=dianpu_shouyi ) logger.info(f"店铺统计更新完成,店铺ID: {dianpu_id}") except Exception as e: logger.error(f"商品/店铺统计更新失败: {e}") logger.error(traceback.format_exc()) # 统计失败不影响主流程 # ============================================= try: from jituan.services.szjilu_accounting import repair_wechat_income_if_missing repair_wechat_income_if_missing( dingdan.Amount, getattr(dingdan, 'ClubID', None), biz_ref=f'order:{dingdan.OrderID}', ) except Exception as e: logger.error(f'订单收支统计失败 {dingdan.OrderID}: {e}', exc_info=True) def _query_wechat_payment(self, out_trade_no): """ 调用微信支付订单查询接口,返回 (trade_state, transaction_id) 若查询失败,抛出异常 """ appid = settings.WEIXIN_APPID mch_id = settings.WEIXIN_MCHID key = settings.WEIXIN_SHANGHUMIYAO nonce_str = self._generate_nonce_str() params = { 'appid': appid, 'mch_id': mch_id, 'out_trade_no': out_trade_no, 'nonce_str': nonce_str, } # 生成签名 sorted_keys = sorted(params.keys()) stringA = '&'.join([f"{k}={params[k]}" for k in sorted_keys]) stringSignTemp = f"{stringA}&key={key}" sign = hashlib.md5(stringSignTemp.encode('utf-8')).hexdigest().upper() params['sign'] = sign xml_data = self._dict_to_xml(params) url = 'https://api.mch.weixin.qq.com/pay/orderquery' response = requests.post(url, data=xml_data, timeout=10) result = xmltodict.parse(response.content)['xml'] if result.get('return_code') == 'SUCCESS' and result.get('result_code') == 'SUCCESS': return result.get('trade_state'), result.get('transaction_id') else: err_msg = result.get('return_msg', '未知错误') raise Exception(f"微信订单查询失败: {err_msg}") def _update_szjilu(self, jine, club_id=None): """szjilu 已废弃,财务只写 daily_income_stat,此处不再操作。""" pass # 不抛出异常,因为收支记录不是核心流程,但记录错误供排查 def _generate_nonce_str(self): """生成随机字符串,长度32""" return ''.join(random.choices(string.ascii_letters + string.digits, k=32)) def _dict_to_xml(self, data): """将字典转换为XML字符串""" xml = [''] for k, v in data.items(): xml.append(f'<{k}>{v}') xml.append('') return ''.join(xml) class CreateOrderView(APIView): """ 创建订单并调用微信支付接口 """ permission_classes = [permissions.IsAuthenticated] def post(self, request): try: # 1. 获取前端传递的字段 shangpin_id = request.data.get('shangpin_id') nicheng = request.data.get('nicheng', '').strip() beizhu = request.data.get('Remark', '').strip() zhiding = request.data.get('zhiding', '').strip() jine = request.data.get('jine', 0) # 2. 验证必填字段 if not shangpin_id: return Response({'code': 1, 'msg': '商品ID不能为空', 'data': None}) if not nicheng: return Response({'code': 2, 'msg': '游戏昵称不能为空', 'data': None}) if len(nicheng) > 50: return Response({'code': 3, 'msg': '游戏昵称过长', 'data': None}) # 3. 查询商品信息 shangpin = Shangpin.query.filter(id=shangpin_id).first() if not shangpin: return Response({'code': 4, 'msg': '商品不存在', 'data': None}) is_valid, err_msg, _ = validate_shangpin_and_dianpu(shangpin_id) if not is_valid: return Response({'code': 5, 'msg': err_msg, 'data': None}) # 检查库存 if hasattr(shangpin, 'kucun') and shangpin.kucun <= 0: return Response({'code': 5, 'msg': '商品库存不足', 'data': None}) # 获取商品价格 shangpin_jiage = getattr(shangpin, 'jiage', 0) if not shangpin_jiage: return Response({'code': 6, 'msg': '商品价格异常', 'data': None}) #获取商品图片 shangpin_tupian = getattr(shangpin, 'tupian_url', None) # 4. 验证指定打手(如果存在) zhiding_id_value = None if zhiding: # 查询主表中是否存在该打手 zhiding_user = User.query.filter( UserUID=zhiding, #user_type='PlayerID' ).select_related('DashouProfile').first() if not zhiding_user: return Response({'code': 7, 'msg': '指定打手不存在', 'data': None}) # 查询打手扩展表 try: dashou_profile = zhiding_user.DashouProfile except UserDashou.DoesNotExist: dashou_profile = None if not dashou_profile: return Response({'code': 8, 'msg': '指定用户不存在或已封禁', 'data': None}) # 检查在线状态 if getattr(dashou_profile, 'zhanghaozhuangtai', 0) == 0: return Response({'code': 8, 'msg': '指定用户不存在或已封禁', 'data': None}) zhiding_id_value = zhiding # 5. 生成订单ID timestamp = int(time.time()) timestamp_ns = int(time.time_ns() // 1000) % 1000000 random_num = random.randint(0, 99) dingdanid = f"PT{timestamp:011d}{timestamp_ns:06d}{random_num:02d}" # 6. 查询利率并计算分成 club_id = resolve_club_id_from_request(request) dashou_lilu = get_commission_rate_object(club_id, '1') # 🔥 新增:判断是否使用商品配置的额外分成 if shangpin.kaioi_ewai_dashou_fencheng and shangpin.ewai_dashou_fencheng is not None: # 使用商品指定的额外分成金额 dashou_fencheng = shangpin.ewai_dashou_fencheng else: # 按原有逻辑计算打手分成 dashou_fencheng = Decimal(str(shangpin_jiage)) * Decimal(str(dashou_lilu.Rate)) # 查询管事利率(字符'2') #guanshi_lilu = CommissionRate.query.filter(Platform='2').first() if not (shangpin.kaioi_ewai_dashou_fencheng and shangpin.ewai_dashou_fencheng is not None): if get_commission_rate(club_id, '1') <= 0 and not CommissionRate.query.filter(Platform='1').exists(): return Response({'code': 9, 'msg': '系统利率配置不完整', 'data': None}) # 开始数据库事务 with transaction.atomic(): # 获取当前用户信息 current_user = request.user laoban_yonghuid = getattr(current_user, 'UserUID', '') or getattr(current_user, 'yonghuid', '') user_openid, club_id = get_payment_openid(request, current_user, club_id) if not user_openid: return Response({'code': 10, 'msg': '用户openid不存在', 'data': None}) # 创建订单主表数据 order_data = { 'OrderID': dingdanid, 'Status': 9, # 未付款状态 'Amount': shangpin_jiage, 'Platform':1, 'PlayerCommission': dashou_fencheng, 'AssignedID': zhiding_id_value, 'ProductID': shangpin_id, 'GrabRequirement': getattr(shangpin, 'yaoqiuleixing', None), 'MembershipID': getattr(shangpin, 'huiyuan_id', ''), 'CommissionReq': getattr(shangpin, 'yongjin', 0), 'Description': getattr(shangpin, 'jieshao', ''), 'Remark': beizhu, 'Nickname': nicheng, 'ImageURL':shangpin_tupian, 'User1ID':f"Boss{request.user.UserUID}", 'ProductTypeID':shangpin.leixing_id, 'ClubID': club_id, } # 创建订单记录 dingdan = Order.query.create(**order_data) # CreateOrderView.post 事务内部,替换原有的扩展表创建代码 dianpu_id, pingtai_shouyi, dianpu_shouyi, shangji_dianpu_id, shangji_fenhong = calculate_pingtai_and_dianpu_shouyi( shangpin_id=shangpin_id, dingdan_jiage=shangpin_jiage, dashou_fencheng=dashou_fencheng ) pingtaikuozhan_data = { 'Order': dingdan, 'BossID': laoban_yonghuid, 'PlatformIncome': pingtai_shouyi, # ---------- 新增上级分红快照 ---------- 'ParentShopID': shangji_dianpu_id, 'ParentBonus': shangji_fenhong if shangji_fenhong else Decimal('0.00'), } if dianpu_id: pingtaikuozhan_data['ShopID'] = dianpu_id pingtaikuozhan_data['ShopIncome'] = dianpu_shouyi else: pingtaikuozhan_data['ShopIncome'] = Decimal('0.00') PlatformOrderExt.query.create(**pingtaikuozhan_data) # 计算收益 '''dianpu_id, pingtai_shouyi, dianpu_shouyi = calculate_pingtai_and_dianpu_shouyi( ProductID=shangpin_id, dingdan_jiage=shangpin_jiage, PlayerCommission=dashou_fencheng ) # 构建扩展表数据,平台收益必定保存,店铺收益和店铺ID仅当dianpu_id存在时才赋值 pingtaikuozhan_data = { 'dingdan': dingdan, 'laoban_id': laoban_yonghuid, 'pingtai_shouyi': pingtai_shouyi, } if dianpu_id: pingtaikuozhan_data['dianpu_id'] = dianpu_id pingtaikuozhan_data['dianpu_shouyi'] = dianpu_shouyi else: # 无店铺时,店铺收益字段保持默认(0.00),也可显式设为0 pingtaikuozhan_data['dianpu_shouyi'] = Decimal('0.00') PlatformOrderExt.query.create(**pingtaikuozhan_data)''' # 7. 生成微信支付参数(向微信官方发送请求) pay_params = self.generate_wechat_pay_params( dingdanid=dingdanid, jine=shangpin_jiage, beizhu=getattr(settings, 'WEIXIN_PAY_DESCRIPTION', '星阙订单'), user_ip=self.get_client_ip(request), openid=user_openid, club_id=club_id, ) if not pay_params: # 手动抛出异常触发事务回滚 raise Exception('支付参数生成失败') # 8. 返回成功响应 response_data = { 'code': 0, 'msg': '订单创建成功', 'data': { 'dingdanid': dingdanid, 'payParams': pay_params } } return Response(response_data) except Exception as e: # 这里会自动回滚事务 return Response({'code': 99, 'msg': f'系统错误: {str(e)}', 'data': None}) def get_client_ip(self, request): """获取客户端真实IP地址""" x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: ip = x_forwarded_for.split(',')[0] else: ip = request.META.get('REMOTE_ADDR') return ip def generate_wechat_pay_params(self, dingdanid, jine, beizhu, user_ip, openid, club_id=None): """ 调用微信支付统一下单接口,生成支付参数 """ try: from jituan.services.wechat_pay import get_wechat_v2_config pay_cfg = get_wechat_v2_config(club_id) APPID = pay_cfg['appid'] MCHID = pay_cfg['mch_id'] KEY = pay_cfg['key'] NOTIFY_URL = getattr(settings, 'WEIXIN_NOTIFY_URL', '') if not all([APPID, MCHID, KEY, NOTIFY_URL]): raise Exception('微信支付配置不完整') # 生成随机字符串 nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32)) # 金额(单位:分) total_fee = yuan_to_fen(jine) # 交易类型 trade_type = 'JSAPI' # 生成签名 sign_data = { 'appid': APPID, 'mch_id': MCHID, 'nonce_str': nonce_str, 'body': beizhu, 'out_trade_no': dingdanid, 'total_fee': total_fee, 'spbill_create_ip': user_ip, 'notify_url': NOTIFY_URL, 'trade_type': trade_type, 'openid': openid } # 按照参数名ASCII码从小到大排序 sorted_sign_data = sorted(sign_data.items(), key=lambda x: x[0]) # 拼接字符串 sign_string = '&'.join([f"{key}={value}" for key, value in sorted_sign_data if value]) sign_string += f'&key={KEY}' # MD5签名 sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest().upper() # 构造XML数据 xml_data = f""" {APPID} {beizhu} {MCHID} {nonce_str} {NOTIFY_URL} {openid} {dingdanid} {user_ip} {total_fee} {trade_type} {sign} """ # 调用微信统一下单接口 response = requests.post( 'https://api.mch.weixin.qq.com/pay/unifiedorder', data=xml_data.encode('utf-8'), headers={'Content-Type': 'application/xml'}, timeout=10 ) # 解析返回的XML root = ET.fromstring(response.content) return_code = root.find('return_code').text result_code = root.find('result_code').text if return_code == 'SUCCESS' and result_code == 'SUCCESS': prepay_id = root.find('prepay_id').text # 生成前端支付参数 timestamp = str(int(time.time())) nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32)) package = f'prepay_id={prepay_id}' # 生成支付签名 pay_sign_data = { 'appId': APPID, 'timeStamp': timestamp, 'nonceStr': nonce_str, 'package': package, 'signType': 'MD5' } sorted_pay_sign_data = sorted(pay_sign_data.items(), key=lambda x: x[0]) pay_sign_string = '&'.join([f"{key}={value}" for key, value in sorted_pay_sign_data]) pay_sign_string += f'&key={KEY}' pay_sign = hashlib.md5(pay_sign_string.encode('utf-8')).hexdigest().upper() # 返回支付参数(格式与前端完全匹配) result = { 'timeStamp': timestamp, 'nonceStr': nonce_str, 'package': package, 'signType': 'MD5', 'paySign': pay_sign } return result else: # 获取详细的错误信息 err_code = root.find('err_code') err_code_des = root.find('err_code_des') return_msg = root.find('return_msg') error_details = f"return_code: {return_code}, " if err_code is not None: error_details += f"err_code: {err_code.text}, " if err_code_des is not None: error_details += f"err_code_des: {err_code_des.text}, " if return_msg is not None: error_details += f"return_msg: {return_msg.text}" raise Exception(f"微信支付下单失败: {error_details}") except Exception as e: raise e