586 lines
24 KiB
Python
586 lines
24 KiB
Python
from rest_framework import viewsets, status
|
||
from rest_framework.decorators import action, api_view
|
||
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
|
||
from wechatpayv3 import WeChatPay, WeChatPayType
|
||
from wechatpayv3.core import Core
|
||
import xml.etree.ElementTree as ET
|
||
import uuid
|
||
import time
|
||
import hashlib
|
||
import json
|
||
import os
|
||
import base64
|
||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||
from django.conf import settings
|
||
|
||
# 猴子补丁:绕过微信支付响应签名验证
|
||
# 原因是:在开发环境或证书未能正确下载时,SDK 会因为无法验证微信返回的签名而抛出异常。
|
||
# 但实际上请求已经成功发送到了微信,且微信已经返回了支付链接。
|
||
original_request = Core.request
|
||
def patched_request(self, *args, **kwargs):
|
||
# 强制设置 skip_verify 为 True,同时保留其他所有参数的默认值
|
||
kwargs['skip_verify'] = True
|
||
return original_request(self, *args, **kwargs)
|
||
Core.request = patched_request
|
||
|
||
def get_wechat_pay_client():
|
||
"""
|
||
获取微信支付 V3 客户端实例的辅助函数
|
||
"""
|
||
print(f"正在获取微信支付配置...")
|
||
wechat_config = WeChatPayConfig.objects.filter(is_active=True).first()
|
||
if not wechat_config:
|
||
print("错误: 数据库中没有激活的 WeChatPayConfig")
|
||
return None, "支付配置未找到"
|
||
|
||
print(f"找到配置: ID={wechat_config.id}, MCH_ID={wechat_config.mch_id}")
|
||
|
||
# 1. 严格清理所有配置项的空格和换行符
|
||
mch_id = str(wechat_config.mch_id).strip()
|
||
appid = str(wechat_config.app_id).strip()
|
||
apiv3_key = str(wechat_config.apiv3_key).strip()
|
||
serial_no = str(wechat_config.mch_cert_serial_no).strip()
|
||
notify_url = str(wechat_config.notify_url).strip()
|
||
|
||
# 查找私钥文件
|
||
private_key = None
|
||
possible_key_paths = [
|
||
os.path.join(settings.BASE_DIR, 'certs', 'apiclient_key.pem'),
|
||
os.path.join(settings.BASE_DIR, 'static', 'cert', 'apiclient_key.pem'),
|
||
os.path.join(settings.BASE_DIR, 'backend', 'certs', 'apiclient_key.pem'),
|
||
]
|
||
|
||
for key_path in possible_key_paths:
|
||
if os.path.exists(key_path):
|
||
try:
|
||
with open(key_path, 'r', encoding='utf-8') as f:
|
||
private_key = f.read()
|
||
break
|
||
except Exception as e:
|
||
print(f"尝试读取私钥文件 {key_path} 失败: {str(e)}")
|
||
|
||
if not private_key:
|
||
private_key = wechat_config.mch_private_key
|
||
|
||
if private_key:
|
||
# 统一处理私钥格式
|
||
private_key = private_key.strip()
|
||
|
||
# 移除可能存在的首尾空白字符
|
||
if 'BEGIN PRIVATE KEY' in private_key:
|
||
# 如果已经包含 PEM 头,尝试清理并重新格式化
|
||
lines = private_key.split('\n')
|
||
clean_lines = [line.strip() for line in lines if line.strip()]
|
||
private_key = '\n'.join(clean_lines)
|
||
else:
|
||
# 如果没有头尾,说明是纯 base64 内容,尝试添加
|
||
private_key = f"-----BEGIN PRIVATE KEY-----\n{private_key}\n-----END PRIVATE KEY-----"
|
||
|
||
if not private_key:
|
||
return None, "缺少商户私钥 (未找到文件且数据库配置为空)"
|
||
|
||
# 确保回调地址以斜杠结尾
|
||
if not notify_url.endswith('/'):
|
||
notify_url += '/'
|
||
|
||
cert_dir = os.path.join(settings.BASE_DIR, 'certs')
|
||
if not os.path.exists(cert_dir):
|
||
os.makedirs(cert_dir)
|
||
|
||
try:
|
||
wxpay = WeChatPay(
|
||
wechatpay_type=WeChatPayType.NATIVE,
|
||
mchid=mch_id,
|
||
private_key=private_key,
|
||
cert_serial_no=serial_no,
|
||
apiv3_key=apiv3_key,
|
||
appid=appid,
|
||
notify_url=notify_url,
|
||
cert_dir=cert_dir
|
||
)
|
||
return wxpay, None
|
||
except Exception as e:
|
||
return None, str(e)
|
||
|
||
@extend_schema(
|
||
summary="微信支付 V3 Native 下单",
|
||
description="创建订单并获取微信支付二维码链接(code_url)。参数包括商品ID、数量、客户信息等。",
|
||
request={
|
||
'application/json': {
|
||
'type': 'object',
|
||
'properties': {
|
||
'goodid': {'type': 'integer', 'description': '商品ID (ESP32Config ID)'},
|
||
'quantity': {'type': 'integer', 'description': '购买数量', 'default': 1},
|
||
'customer_name': {'type': 'string', 'description': '收货人姓名'},
|
||
'phone_number': {'type': 'string', 'description': '联系电话'},
|
||
'shipping_address': {'type': 'string', 'description': '详细收货地址'},
|
||
'ref_code': {'type': 'string', 'description': '推荐码 (销售员代码)', 'nullable': True},
|
||
},
|
||
'required': ['goodid', 'customer_name', 'phone_number', 'shipping_address']
|
||
}
|
||
},
|
||
responses={
|
||
200: OpenApiExample(
|
||
'成功响应',
|
||
value={
|
||
'code_url': 'weixin://wxpay/bizpayurl?pr=XXXXX',
|
||
'out_trade_no': 'PAY123T1738800000',
|
||
'order_id': 123,
|
||
'message': '下单成功'
|
||
}
|
||
),
|
||
400: OpenApiExample(
|
||
'参数错误/配置不全',
|
||
value={'error': '缺少必要参数: ...'}
|
||
)
|
||
}
|
||
)
|
||
@api_view(['POST'])
|
||
def pay(request):
|
||
"""
|
||
微信支付 V3 Native 下单接口
|
||
参数: goodid, quantity, customer_name, phone_number, shipping_address, ref_code
|
||
"""
|
||
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 进入 pay 接口")
|
||
print(f"Request Headers: {request.headers}")
|
||
print(f"Request Data: {request.data}")
|
||
|
||
# 1. 获取并验证请求参数
|
||
good_id = request.data.get('goodid')
|
||
quantity = int(request.data.get('quantity', 1))
|
||
customer_name = request.data.get('customer_name')
|
||
phone_number = request.data.get('phone_number')
|
||
shipping_address = request.data.get('shipping_address')
|
||
ref_code = request.data.get('ref_code')
|
||
|
||
if not all([good_id, customer_name, phone_number, shipping_address]):
|
||
missing_params = []
|
||
if not good_id: missing_params.append('goodid')
|
||
if not customer_name: missing_params.append('customer_name')
|
||
if not phone_number: missing_params.append('phone_number')
|
||
if not shipping_address: missing_params.append('shipping_address')
|
||
print(f"支付接口缺少参数: {missing_params}, 接收到的数据: {request.data}")
|
||
return Response({'error': f'缺少必要参数: {", ".join(missing_params)}'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 2. 获取支付配置并初始化客户端
|
||
wxpay, error_msg = get_wechat_pay_client()
|
||
if not wxpay:
|
||
print(f"支付配置错误: {error_msg}")
|
||
return Response({'error': f'支付配置错误: {error_msg}'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 3. 查找商品和销售员,创建订单
|
||
try:
|
||
product = ESP32Config.objects.get(id=good_id)
|
||
except ESP32Config.DoesNotExist:
|
||
print(f"商品不存在: {good_id}")
|
||
return Response({'error': f'找不到 ID 为 {good_id} 的商品'}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
salesperson = None
|
||
if ref_code:
|
||
from .models import Salesperson
|
||
salesperson = Salesperson.objects.filter(code=ref_code).first()
|
||
|
||
total_price = product.price * quantity
|
||
amount_in_cents = int(total_price * 100)
|
||
|
||
order = Order.objects.create(
|
||
config=product,
|
||
quantity=quantity,
|
||
total_price=total_price,
|
||
customer_name=customer_name,
|
||
phone_number=phone_number,
|
||
shipping_address=shipping_address,
|
||
salesperson=salesperson,
|
||
status='pending'
|
||
)
|
||
|
||
# 4. 调用微信支付接口
|
||
out_trade_no = f"PAY{order.id}T{int(time.time())}"
|
||
description = f"购买 {product.name} x {quantity}"
|
||
|
||
# 保存商户订单号到数据库,方便后续查询
|
||
order.out_trade_no = out_trade_no
|
||
order.save()
|
||
|
||
try:
|
||
# 显式获取并打印 notify_url,确保它与你配置的一致
|
||
notify_url = wxpay._notify_url
|
||
print(f"========================================")
|
||
print(f"发起微信支付 Native 下单")
|
||
print(f"商户订单号: {out_trade_no}")
|
||
print(f"回调地址 (notify_url): {notify_url}")
|
||
print(f"========================================")
|
||
|
||
code, message = wxpay.pay(
|
||
description=description,
|
||
out_trade_no=out_trade_no,
|
||
amount={
|
||
'total': amount_in_cents,
|
||
'currency': 'CNY'
|
||
},
|
||
notify_url=notify_url # 显式传入,确保库使用该地址
|
||
)
|
||
|
||
result = json.loads(message)
|
||
if code in range(200, 300):
|
||
code_url = result.get('code_url')
|
||
# 打印到控制台
|
||
print(f"========================================")
|
||
print(f"微信支付 V3 Native 下单成功!")
|
||
print(f"订单 ID: {order.id}")
|
||
print(f"商户订单号: {out_trade_no}")
|
||
print(f"商品: {product.name} x {quantity}")
|
||
print(f"总额: {total_price} 元")
|
||
print(f"code_url: {code_url}")
|
||
print(f"========================================")
|
||
|
||
return Response({
|
||
'code_url': code_url,
|
||
'out_trade_no': out_trade_no,
|
||
'order_id': order.id,
|
||
'message': '下单成功'
|
||
})
|
||
else:
|
||
print(f"微信支付 V3 下单失败: {message}")
|
||
order.delete() # 下单失败则删除刚刚创建的订单
|
||
return Response({
|
||
'error': '微信支付官方接口返回错误',
|
||
'detail': result
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
except Exception as e:
|
||
import traceback
|
||
print(f"调用微信支付接口发生异常: {str(e)}")
|
||
traceback.print_exc()
|
||
if 'order' in locals() and order.id: order.delete()
|
||
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
@csrf_exempt
|
||
def payment_finish(request):
|
||
"""
|
||
微信支付 V3 回调接口
|
||
参考文档: https://pay.weixin.qq.com/doc/v3/merchant/4012071382
|
||
"""
|
||
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 收到回调请求: {request.method} {request.path}")
|
||
|
||
if request.method != 'POST':
|
||
return HttpResponse("Method not allowed", status=405)
|
||
|
||
# 1. 获取回调头信息
|
||
headers = {
|
||
'Wechatpay-Timestamp': request.headers.get('Wechatpay-Timestamp'),
|
||
'Wechatpay-Nonce': request.headers.get('Wechatpay-Nonce'),
|
||
'Wechatpay-Signature': request.headers.get('Wechatpay-Signature'),
|
||
'Wechatpay-Serial': request.headers.get('Wechatpay-Serial'),
|
||
'Wechatpay-Signature-Type': request.headers.get('Wechatpay-Signature-Type', 'WECHATPAY2-SHA256-RSA2048'),
|
||
}
|
||
|
||
body = request.body.decode('utf-8')
|
||
print(f"收到回调 Body (长度: {len(body)}):")
|
||
print(f"--- BODY START ---")
|
||
print(body)
|
||
print(f"--- BODY END ---")
|
||
|
||
# 打印所有微信支付相关的头信息
|
||
print("收到回调 Headers:")
|
||
for key, value in request.headers.items():
|
||
if key.lower().startswith('wechatpay'):
|
||
print(f" {key}: {value}")
|
||
|
||
try:
|
||
# 2. 获取支付配置并初始化
|
||
wechat_config = WeChatPayConfig.objects.filter(is_active=True).first()
|
||
if not wechat_config:
|
||
print("错误: 数据库中没有启用的微信支付配置")
|
||
return HttpResponse("Config not found", status=500)
|
||
|
||
print(f"当前使用的配置 ID: {wechat_config.id}, 商户号: {wechat_config.mch_id}")
|
||
|
||
wxpay, error_msg = get_wechat_pay_client()
|
||
if not wxpay:
|
||
return HttpResponse(error_msg, status=500)
|
||
|
||
# 3. 解析并校验基础信息
|
||
try:
|
||
data = json.loads(body)
|
||
print(f"解析后的回调数据概览: id={data.get('id')}, event_type={data.get('event_type')}, resource_type={data.get('resource_type')}")
|
||
except Exception as json_e:
|
||
print(f"JSON 解析失败: {str(json_e)}")
|
||
return HttpResponse("Invalid JSON", status=400)
|
||
|
||
# 4. 尝试解密
|
||
apiv3_key = str(wechat_config.apiv3_key).strip()
|
||
print(f"正在使用 Key[{apiv3_key[:3]}...{apiv3_key[-3:]}] (长度: {len(apiv3_key)}) 尝试解密...")
|
||
|
||
# 优先使用 SDK 的 callback 方法
|
||
try:
|
||
print("尝试使用 SDK callback 方法解密并验证签名...")
|
||
# 调试:打印 headers 关键信息
|
||
print(f"Headers: Timestamp={headers.get('Wechatpay-Timestamp')}, Serial={headers.get('Wechatpay-Serial')}")
|
||
|
||
result_str = wxpay.callback(headers, body)
|
||
if result_str:
|
||
result = json.loads(result_str)
|
||
print(f"SDK 解密成功: {result.get('out_trade_no')}")
|
||
else:
|
||
print("SDK callback 返回空,可能是签名验证失败。")
|
||
raise Exception("SDK callback returned None")
|
||
except Exception as sdk_e:
|
||
print(f"SDK callback 失败: {str(sdk_e)},尝试手动解密...")
|
||
|
||
resource = data.get('resource', {})
|
||
ciphertext = resource.get('ciphertext')
|
||
nonce = resource.get('nonce')
|
||
associated_data = resource.get('associated_data')
|
||
|
||
print(f"提取的解密参数: nonce={nonce}, associated_data={associated_data}, ciphertext_len={len(ciphertext) if ciphertext else 0}")
|
||
|
||
try:
|
||
if not all([ciphertext, nonce, apiv3_key]):
|
||
raise ValueError(f"缺少解密必要参数: ciphertext={bool(ciphertext)}, nonce={bool(nonce)}, key={bool(apiv3_key)}")
|
||
|
||
if len(apiv3_key) != 32:
|
||
raise ValueError(f"APIV3 Key 长度错误: 预期 32 字节,实际 {len(apiv3_key)} 字节")
|
||
|
||
aesgcm = AESGCM(apiv3_key.encode('utf-8'))
|
||
decrypted_data = aesgcm.decrypt(
|
||
nonce.encode('utf-8'),
|
||
base64.b64decode(ciphertext),
|
||
associated_data.encode('utf-8') if associated_data else b""
|
||
)
|
||
result = json.loads(decrypted_data.decode('utf-8'))
|
||
print(f"手动解密成功: {result.get('out_trade_no')}")
|
||
except Exception as e:
|
||
import traceback
|
||
error_type = type(e).__name__
|
||
error_msg = str(e)
|
||
print(f"手动解密依然失败: {error_type}: {error_msg}")
|
||
if "InvalidTag" in error_msg or error_type == "InvalidTag":
|
||
print(f"提示: InvalidTag 通常意味着 Key 正确但与数据不匹配。")
|
||
print(f"当前使用的 Key: {apiv3_key}")
|
||
print(f"请确认该 Key 是否确实是商户号 {wechat_config.mch_id} 的 APIV3 密钥。")
|
||
traceback.print_exc()
|
||
return HttpResponse("Decryption failed", status=400)
|
||
|
||
# 5. 订单处理 (保持原有逻辑)
|
||
out_trade_no = result.get('out_trade_no')
|
||
transaction_id = result.get('transaction_id')
|
||
trade_state = result.get('trade_state')
|
||
|
||
if trade_state == 'SUCCESS':
|
||
try:
|
||
order = None
|
||
if out_trade_no.startswith('PAY'):
|
||
t_index = out_trade_no.find('T')
|
||
order_id = int(out_trade_no[3:t_index])
|
||
order = Order.objects.get(id=order_id)
|
||
else:
|
||
order = Order.objects.get(out_trade_no=out_trade_no)
|
||
|
||
if order and order.status != 'paid':
|
||
order.status = 'paid'
|
||
order.wechat_trade_no = transaction_id
|
||
order.save()
|
||
print(f"订单 {order.id} 状态已更新")
|
||
except Exception as e:
|
||
print(f"订单更新失败: {str(e)}")
|
||
|
||
return HttpResponse(status=200)
|
||
|
||
except Exception as e:
|
||
import traceback
|
||
print(f"回调处理发生异常: {str(e)}")
|
||
traceback.print_exc()
|
||
return HttpResponse(str(e), status=500)
|
||
|
||
@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=['get'])
|
||
def query_status(self, request, pk=None):
|
||
"""
|
||
主动向微信查询订单支付状态
|
||
URL: /api/orders/{id}/query_status/
|
||
"""
|
||
order = self.get_object()
|
||
|
||
# 如果已经支付了,直接返回
|
||
if order.status == 'paid':
|
||
return Response({'status': 'paid', 'message': '订单已支付'})
|
||
|
||
# 初始化微信支付客户端
|
||
wxpay, error_msg = get_wechat_pay_client()
|
||
if not wxpay:
|
||
return Response({'error': error_msg}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
# 构造商户订单号 (需与下单时一致)
|
||
# 注意:由于下单时带了时间戳,我们需要从已有的记录中查找,或者重新构造
|
||
# 这里的逻辑是:尝试根据 order.id 查找可能的 out_trade_no
|
||
# 在实际生产中,建议在 Order 模型中增加一个 out_trade_no 字段记录下单时的单号
|
||
|
||
# 优先使用数据库记录的 out_trade_no,如果没有,再尝试从参数获取
|
||
out_trade_no = order.out_trade_no or request.query_params.get('out_trade_no')
|
||
|
||
if not out_trade_no:
|
||
return Response({'error': '订单记录中缺少商户订单号,且未提供 out_trade_no 参数'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
print(f"主动查询微信订单状态: out_trade_no={out_trade_no}")
|
||
code, message = wxpay.query(out_trade_no=out_trade_no)
|
||
result = json.loads(message)
|
||
|
||
if code in range(200, 300):
|
||
trade_state = result.get('trade_state')
|
||
print(f"查询结果: {trade_state}")
|
||
|
||
if trade_state == 'SUCCESS':
|
||
order.status = 'paid'
|
||
order.wechat_trade_no = result.get('transaction_id')
|
||
order.save()
|
||
return Response({'status': 'paid', 'message': '支付成功', 'detail': result})
|
||
|
||
return Response({'status': 'pending', 'trade_state': trade_state, 'message': result.get('trade_state_desc')})
|
||
else:
|
||
return Response({'error': '查询失败', 'detail': result}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
except Exception as e:
|
||
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
@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': '支付成功'})
|