211 lines
8.2 KiB
Python
211 lines
8.2 KiB
Python
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 = """
|
||
<xml>
|
||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||
<return_msg><![CDATA[OK]]></return_msg>
|
||
</xml>
|
||
"""
|
||
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"""
|
||
<xml>
|
||
<return_code><![CDATA[FAIL]]></return_code>
|
||
<return_msg><![CDATA[{str(e)}]]></return_msg>
|
||
</xml>
|
||
"""
|
||
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': '支付成功'})
|