from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response from django.shortcuts import render from django.views.decorators.csrf import csrf_exempt from django.http import HttpResponse from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiExample from .models import ESP32Config, Order, WeChatPayConfig, Service, ARService, ServiceOrder from .serializers import ESP32ConfigSerializer, OrderSerializer, ServiceSerializer, ARServiceSerializer, ServiceOrderSerializer import xml.etree.ElementTree as ET import uuid import time import hashlib @csrf_exempt def payment_finish(request): """ 微信支付回调接口 URL: /api/finish/ """ if request.method != 'POST': return HttpResponse("Method not allowed", status=405) try: # 解析微信发送的 XML xml_data = request.body if not xml_data: return HttpResponse("Empty body", status=400) root = ET.fromstring(xml_data) # 将 XML 转为字典 data = {child.tag: child.text for child in root} # 检查支付结果 # WeChat Pay V2 回调参数中 return_code 为通信标识,result_code 为业务结果 if data.get('return_code') == 'SUCCESS' and data.get('result_code') == 'SUCCESS': # out_trade_no 是我们在统一下单时传给微信的订单号 order_id = data.get('out_trade_no') transaction_id = data.get('transaction_id') # 微信支付订单号 # 找到订单并更新状态 try: # 兼容处理:如果是字符串 ID,尝试转换 order = Order.objects.get(id=order_id) if order.status != 'paid': order.status = 'paid' order.wechat_trade_no = transaction_id order.save() print(f"Order {order_id} marked as paid via callback.") except Order.DoesNotExist: print(f"Order {order_id} not found in callback.") except Exception as e: print(f"Error processing order {order_id} in callback: {e}") # 返回成功响应给微信,否则微信会不断重试通知 success_response = """ """ return HttpResponse(success_response, content_type='application/xml') except ET.ParseError: return HttpResponse("Invalid XML", status=400) except Exception as e: print(f"Payment callback error: {e}") error_response = f""" """ return HttpResponse(error_response, content_type='application/xml') @extend_schema_view( list=extend_schema(summary="获取AR服务列表", description="获取所有可用的AR服务"), retrieve=extend_schema(summary="获取AR服务详情", description="获取指定AR服务的详细信息") ) class ARServiceViewSet(viewsets.ReadOnlyModelViewSet): """ AR服务列表和详情 """ queryset = ARService.objects.all().order_by('-created_at') serializer_class = ARServiceSerializer def order_check_view(request): """ 订单查询页面视图 """ return render(request, 'shop/order_check.html') @extend_schema_view( list=extend_schema(summary="获取AI服务列表", description="获取所有可用的AI服务"), retrieve=extend_schema(summary="获取AI服务详情", description="获取指定AI服务的详细信息") ) class ServiceViewSet(viewsets.ReadOnlyModelViewSet): """ AI服务列表和详情 """ queryset = Service.objects.all().order_by('-created_at') serializer_class = ServiceSerializer class ServiceOrderViewSet(viewsets.ModelViewSet): """ AI服务订单管理 """ queryset = ServiceOrder.objects.all() serializer_class = ServiceOrderSerializer @extend_schema_view( list=extend_schema(summary="获取ESP32配置列表", description="获取所有可用的ESP32硬件配置选项"), retrieve=extend_schema(summary="获取ESP32配置详情", description="获取指定ESP32配置的详细信息") ) class ESP32ConfigViewSet(viewsets.ReadOnlyModelViewSet): """ 提供ESP32配置选项的列表和详情 """ queryset = ESP32Config.objects.all() serializer_class = ESP32ConfigSerializer class OrderViewSet(viewsets.ModelViewSet): """ 订单管理视图集 支持创建订单和查询订单状态 """ queryset = Order.objects.all() serializer_class = OrderSerializer @action(detail=False, methods=['get']) def lookup(self, request): """ 根据电话号码查询订单状态 URL: /api/orders/lookup/?phone=13800138000 """ phone = request.query_params.get('phone') if not phone: return Response({'error': '请提供电话号码'}, status=status.HTTP_400_BAD_REQUEST) # 简单校验 orders = Order.objects.filter(phone_number=phone).order_by('-created_at') serializer = self.get_serializer(orders, many=True) return Response(serializer.data) @action(detail=True, methods=['post']) def initiate_payment(self, request, pk=None): """ 发起支付请求 获取微信支付配置并生成签名 """ order = self.get_object() if order.status == 'paid': return Response({'error': '订单已支付'}, status=status.HTTP_400_BAD_REQUEST) # 获取微信支付配置 wechat_config = WeChatPayConfig.objects.filter(is_active=True).first() if not wechat_config: # 如果没有配置,为了演示方便,回退到模拟数据,或者报错 # 这里我们报错提示需要在后台配置 return Response({'error': '支付系统维护中 (未配置支付参数)'}, status=status.HTTP_503_SERVICE_UNAVAILABLE) # 构造支付参数 # 注意:实际生产环境必须在此处调用微信【统一下单】接口获取真实的 prepay_id # 这里为了演示完整流程,我们使用配置中的参数生成合法的签名结构,但 prepay_id 是模拟的 app_id = wechat_config.app_id timestamp = str(int(time.time())) nonce_str = str(uuid.uuid4()).replace('-', '') # 模拟的 prepay_id prepay_id = f"wx{str(uuid.uuid4()).replace('-', '')}" package = f"prepay_id={prepay_id}" sign_type = 'MD5' # 生成签名 (WeChat Pay V2 MD5 Signature) # 签名步骤: # 1. 设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序) # 2. 使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA # 3. 在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写 stringA = f"appId={app_id}&nonceStr={nonce_str}&package={package}&signType={sign_type}&timeStamp={timestamp}" string_sign_temp = f"{stringA}&key={wechat_config.api_key}" pay_sign = hashlib.md5(string_sign_temp.encode('utf-8')).hexdigest().upper() payment_params = { 'appId': app_id, 'timeStamp': timestamp, 'nonceStr': nonce_str, 'package': package, 'signType': sign_type, 'paySign': pay_sign, 'orderId': order.id, 'amount': str(order.total_price) } return Response(payment_params) @action(detail=True, methods=['post']) def confirm_payment(self, request, pk=None): """ 模拟支付成功回调/确认 """ order = self.get_object() order.status = 'paid' order.wechat_trade_no = f"WX_{str(uuid.uuid4())[:18]}" order.save() return Response({'status': 'success', 'message': '支付成功'})