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': '支付成功'})