849 lines
28 KiB
Python
849 lines
28 KiB
Python
from flask import request, jsonify, current_app
|
||
from app import db
|
||
from app.models import Product, License, Ticket, Version, Order, Package
|
||
from . import api_bp
|
||
from app.utils.logger import log_operation
|
||
from app.utils.cors_middleware import handle_preflight, cors_after
|
||
import re
|
||
from datetime import datetime
|
||
|
||
# 注册请求和响应处理器
|
||
@api_bp.before_request
|
||
def before_request():
|
||
"""在每个请求前处理"""
|
||
# 处理预检请求
|
||
preflight_response = handle_preflight()
|
||
if preflight_response:
|
||
return preflight_response
|
||
|
||
@api_bp.after_request
|
||
def after_request(response):
|
||
"""在每个请求后添加CORS头部"""
|
||
return cors_after(response)
|
||
|
||
# ==================== 产品相关接口 ====================
|
||
|
||
@api_bp.route('/user/products', methods=['GET'])
|
||
def get_user_products():
|
||
"""用户端获取产品列表(无需认证)"""
|
||
try:
|
||
page = request.args.get('page', 1, type=int)
|
||
per_page = min(request.args.get('per_page', 20, type=int), 100)
|
||
keyword = request.args.get('keyword', '').strip()
|
||
product_type = request.args.get('type') # 产品类型筛选
|
||
is_paid = request.args.get('is_paid', type=int) # 付费筛选:1=付费,0=免费
|
||
|
||
query = Product.query.filter_by(status=1) # 只显示启用的产品
|
||
|
||
# 关键词搜索
|
||
if keyword:
|
||
query = query.filter(
|
||
db.or_(
|
||
Product.product_name.like(f'%{keyword}%'),
|
||
Product.description.like(f'%{keyword}%')
|
||
)
|
||
)
|
||
|
||
# 产品类型筛选
|
||
if product_type:
|
||
query = query.filter(Product.product_type == product_type)
|
||
|
||
query = query.order_by(Product.create_time.desc())
|
||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||
|
||
products = []
|
||
for product in pagination.items:
|
||
product_dict = product.to_dict()
|
||
# 获取最新版本信息
|
||
latest_version = Version.query.filter_by(
|
||
product_id=product.product_id,
|
||
publish_status=1
|
||
).order_by(Version.update_time.desc(), Version.create_time.desc()).first()
|
||
|
||
product_dict['latest_version'] = latest_version.version_num if latest_version else None
|
||
# 移除不存在的is_paid字段
|
||
products.append(product_dict)
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'data': {
|
||
'products': products,
|
||
'pagination': {
|
||
'page': page,
|
||
'per_page': per_page,
|
||
'total': pagination.total,
|
||
'pages': pagination.pages,
|
||
'has_prev': pagination.has_prev,
|
||
'has_next': pagination.has_next
|
||
}
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
current_app.logger.error(f"获取产品列表失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误,请稍后重试'
|
||
}), 500
|
||
|
||
|
||
@api_bp.route('/user/products/<product_id>', methods=['GET'])
|
||
def get_user_product(product_id):
|
||
"""用户端获取产品详情(无需认证)"""
|
||
try:
|
||
product = Product.query.filter_by(product_id=product_id, status=1).first()
|
||
if not product:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '产品不存在或已下架'
|
||
}), 404
|
||
|
||
product_dict = product.to_dict(include_stats=True)
|
||
|
||
# 获取最新版本信息
|
||
latest_version = Version.query.filter_by(
|
||
product_id=product_id,
|
||
publish_status=1
|
||
).order_by(Version.update_time.desc(), Version.create_time.desc()).first()
|
||
|
||
if latest_version:
|
||
product_dict['latest_version'] = latest_version.version_num
|
||
|
||
# 获取最近3条更新日志(包含下载URL信息)
|
||
recent_versions = Version.query.filter_by(
|
||
product_id=product_id,
|
||
publish_status=1
|
||
).order_by(Version.update_time.desc(), Version.create_time.desc()).limit(3).all()
|
||
|
||
product_dict['recent_updates'] = [
|
||
{
|
||
'version_num': v.version_num,
|
||
'update_time': v.create_time.isoformat(),
|
||
'update_log': v.update_log,
|
||
'download_url': v.download_url, # 添加下载URL信息
|
||
'file_hash': v.file_hash # 添加文件哈希信息
|
||
} for v in recent_versions
|
||
]
|
||
|
||
# 移除不存在的is_paid字段
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'data': product_dict
|
||
})
|
||
|
||
except Exception as e:
|
||
current_app.logger.error(f"获取产品详情失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误'
|
||
}), 500
|
||
|
||
|
||
# ==================== 卡密相关接口 ====================
|
||
|
||
@api_bp.route('/user/licenses/packages', methods=['GET'])
|
||
def get_license_packages():
|
||
"""用户端获取卡密套餐(按产品筛选,无需认证)"""
|
||
try:
|
||
product_id = request.args.get('product_id')
|
||
if not product_id:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '缺少产品ID参数'
|
||
}), 400
|
||
|
||
# 验证产品是否存在且启用
|
||
product = Product.query.filter_by(product_id=product_id, status=1).first()
|
||
if not product:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '产品不存在或已下架'
|
||
}), 404
|
||
|
||
# 从套餐表获取该产品的套餐信息
|
||
from app.models.package import Package
|
||
|
||
# 获取启用的套餐
|
||
packages = Package.query.filter_by(
|
||
product_id=product_id,
|
||
status=1
|
||
).order_by(Package.sort_order).all()
|
||
|
||
# 转换为字典格式
|
||
package_list = [pkg.to_dict() for pkg in packages]
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'data': {
|
||
'product': product.to_dict(),
|
||
'packages': package_list
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
current_app.logger.error(f"获取卡密套餐失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误'
|
||
}), 500
|
||
|
||
|
||
@api_bp.route('/user/licenses/verify', methods=['GET'])
|
||
def user_verify_license():
|
||
"""用户端验证卡密(下载用,无需认证)"""
|
||
try:
|
||
license_key = request.args.get('license_key')
|
||
product_id = request.args.get('product_id')
|
||
|
||
if not all([license_key, product_id]):
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '缺少必要参数'
|
||
}), 400
|
||
|
||
# 查找卡密
|
||
license_obj = License.query.filter_by(
|
||
license_key=license_key,
|
||
product_id=product_id
|
||
).first()
|
||
|
||
if not license_obj:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '卡密不存在'
|
||
}), 404
|
||
|
||
# 检查卡密状态
|
||
if license_obj.status != 1:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '卡密未激活或已失效'
|
||
}), 403
|
||
|
||
# 检查是否过期
|
||
if license_obj.is_expired():
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '卡密已过期'
|
||
}), 403
|
||
|
||
# 获取产品信息
|
||
product = Product.query.filter_by(product_id=product_id, status=1).first()
|
||
if not product:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '产品不存在或已下架'
|
||
}), 404
|
||
|
||
# 获取下载链接(从最新版本获取)
|
||
latest_version = Version.query.filter_by(
|
||
product_id=product_id,
|
||
publish_status=1
|
||
).order_by(Version.update_time.desc(), Version.create_time.desc()).first()
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'message': '卡密验证成功',
|
||
'data': {
|
||
'license_key': license_obj.license_key,
|
||
'product_name': product.product_name,
|
||
'expire_time': license_obj.expire_time.isoformat() if license_obj.expire_time else None,
|
||
'download_url': latest_version.download_url if latest_version else None,
|
||
'can_download': True
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
current_app.logger.error(f"卡密验证失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误'
|
||
}), 500
|
||
|
||
|
||
# ==================== 工单相关接口 ====================
|
||
|
||
@api_bp.route('/user/tickets', methods=['POST'])
|
||
def create_user_ticket():
|
||
"""用户端创建工单(无需认证)"""
|
||
try:
|
||
data = request.get_json()
|
||
current_app.logger.info(f"收到工单创建请求: {data}")
|
||
|
||
if not data:
|
||
current_app.logger.warning("工单创建请求数据为空")
|
||
return jsonify({'success': False, 'message': '请求数据为空'}), 400
|
||
|
||
product_id = data.get('product_id')
|
||
contact_person = data.get('contact_person', '').strip()
|
||
phone = data.get('phone', '').strip()
|
||
title = data.get('title', '').strip()
|
||
description = data.get('description', '').strip()
|
||
attachment = data.get('attachment') # 附件信息
|
||
|
||
# 验证必填字段
|
||
if not all([product_id, contact_person, phone, title, description]):
|
||
missing_fields = []
|
||
if not product_id:
|
||
missing_fields.append('product_id')
|
||
if not contact_person:
|
||
missing_fields.append('contact_person')
|
||
if not phone:
|
||
missing_fields.append('phone')
|
||
if not title:
|
||
missing_fields.append('title')
|
||
if not description:
|
||
missing_fields.append('description')
|
||
|
||
current_app.logger.warning(f"工单创建缺少必要参数: {missing_fields}")
|
||
return jsonify({'success': False, 'message': f'缺少必要参数: {", ".join(missing_fields)}'}), 400
|
||
|
||
# 验证手机号格式
|
||
if not phone or not re.match(r'^1[3-9]\d{9}$', phone):
|
||
current_app.logger.warning(f"工单创建手机号格式不正确: {phone}")
|
||
return jsonify({'success': False, 'message': '手机号格式不正确'}), 400
|
||
|
||
# 验证产品存在且启用
|
||
product = Product.query.filter_by(product_id=product_id, status=1).first()
|
||
if not product:
|
||
current_app.logger.warning(f"工单创建产品不存在或已下架: {product_id}")
|
||
return jsonify({'success': False, 'message': '产品不存在或已下架'}), 404
|
||
|
||
ticket = Ticket(
|
||
title=title,
|
||
product_id=product_id,
|
||
description=description,
|
||
contact_person=contact_person,
|
||
phone=phone,
|
||
priority=data.get('priority', 1)
|
||
# 移除了不存在的source和attachment字段
|
||
)
|
||
|
||
db.session.add(ticket)
|
||
db.session.commit()
|
||
|
||
# 记录操作日志
|
||
log_operation('CREATE_USER_TICKET', 'TICKET', ticket.ticket_id, {
|
||
'title': ticket.title,
|
||
'product_id': ticket.product_id,
|
||
'contact_person': contact_person,
|
||
'phone': phone
|
||
})
|
||
|
||
current_app.logger.info(f"工单创建成功: ticket_id={ticket.ticket_id}, ticket_number={ticket.ticket_number}")
|
||
return jsonify({
|
||
'success': True,
|
||
'message': '工单提交成功',
|
||
'data': {
|
||
'ticket_id': ticket.ticket_id,
|
||
'ticket_number': ticket.ticket_number
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
db.session.rollback()
|
||
current_app.logger.error(f"创建工单失败: {str(e)}", exc_info=True)
|
||
return jsonify({'success': False, 'message': '服务器内部错误'}), 500
|
||
|
||
|
||
@api_bp.route('/user/tickets', methods=['GET'])
|
||
def get_user_tickets():
|
||
"""用户端获取工单列表(通过手机号查询,无需认证)"""
|
||
try:
|
||
phone = request.args.get('phone')
|
||
|
||
if not phone:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '缺少手机号参数'
|
||
}), 400
|
||
|
||
# 验证手机号格式
|
||
if not re.match(r'^1[3-9]\d{9}$', phone):
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '手机号格式不正确'
|
||
}), 400
|
||
|
||
# 查询该手机号提交的工单
|
||
tickets = Ticket.query.filter_by(phone=phone).order_by(Ticket.create_time.desc()).all()
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'data': [ticket.to_dict() for ticket in tickets]
|
||
})
|
||
|
||
except Exception as e:
|
||
current_app.logger.error(f"获取工单列表失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误'
|
||
}), 500
|
||
|
||
|
||
@api_bp.route('/user/tickets/query', methods=['GET'])
|
||
def query_user_ticket():
|
||
"""用户端查询工单(通过工单编号+手机号,无需认证)"""
|
||
try:
|
||
ticket_number = request.args.get('ticket_number')
|
||
phone = request.args.get('phone')
|
||
|
||
if not all([ticket_number, phone]):
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '缺少必要参数'
|
||
}), 400
|
||
|
||
# 验证手机号格式
|
||
if not phone or not re.match(r'^1[3-9]\d{9}$', phone):
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '手机号格式不正确'
|
||
}), 400
|
||
|
||
# 查找工单
|
||
ticket = Ticket.query.filter_by(
|
||
ticket_number=ticket_number,
|
||
phone=phone
|
||
).first()
|
||
|
||
if not ticket:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '工单不存在或手机号不匹配'
|
||
}), 404
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'data': ticket.to_dict()
|
||
})
|
||
|
||
except Exception as e:
|
||
current_app.logger.error(f"查询工单失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误'
|
||
}), 500
|
||
|
||
|
||
# ==================== 订单相关接口 ====================
|
||
|
||
@api_bp.route('/user/orders', methods=['POST'])
|
||
def create_user_order():
|
||
"""用户端创建订单(无需认证)"""
|
||
try:
|
||
data = request.get_json()
|
||
if not data:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '请求数据为空'
|
||
}), 400
|
||
|
||
product_id = data.get('product_id')
|
||
package_id = data.get('package_id')
|
||
contact_person = data.get('contact_person', '').strip()
|
||
phone = data.get('phone', '').strip()
|
||
quantity = data.get('quantity', 1)
|
||
|
||
# 验证必填字段
|
||
if not all([product_id, package_id, contact_person, phone]):
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '缺少必要参数'
|
||
}), 400
|
||
|
||
# 验证手机号格式
|
||
if not phone or not re.match(r'^1[3-9]\d{9}$', phone):
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '手机号格式不正确'
|
||
}), 400
|
||
|
||
# 验证数量
|
||
if not isinstance(quantity, int) or quantity < 1 or quantity > 5:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '购买数量必须在1-5之间'
|
||
}), 400
|
||
|
||
# 验证产品存在且启用
|
||
product = Product.query.filter_by(product_id=product_id, status=1).first()
|
||
if not product:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '产品不存在或已下架'
|
||
}), 404
|
||
|
||
# 验证套餐存在且启用
|
||
package = Package.query.filter_by(package_id=package_id, status=1, product_id=product_id).first()
|
||
if not package:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '套餐不存在或已下架'
|
||
}), 404
|
||
|
||
# 检查库存
|
||
if not package.has_stock():
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '套餐库存不足'
|
||
}), 400
|
||
|
||
# 计算总金额
|
||
total_amount = package.price * quantity
|
||
|
||
# 创建订单
|
||
order = Order(
|
||
product_id=product_id,
|
||
package_id=package_id,
|
||
contact_person=contact_person,
|
||
phone=phone,
|
||
quantity=quantity,
|
||
amount=total_amount,
|
||
status=0 # 待支付
|
||
)
|
||
|
||
# 保存到数据库
|
||
db.session.add(order)
|
||
db.session.commit()
|
||
|
||
# 记录操作日志
|
||
log_operation('CREATE_USER_ORDER', 'ORDER', order.order_id, {
|
||
'order_number': order.order_number,
|
||
'product_id': product_id,
|
||
'package_id': package_id,
|
||
'contact_person': contact_person,
|
||
'phone': phone,
|
||
'quantity': quantity,
|
||
'amount': total_amount
|
||
})
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'message': '订单创建成功',
|
||
'data': {
|
||
'order_number': order.order_number,
|
||
'amount': total_amount
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
db.session.rollback()
|
||
current_app.logger.error(f"创建订单失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误'
|
||
}), 500
|
||
|
||
|
||
@api_bp.route('/user/orders/query', methods=['GET'])
|
||
def query_user_order():
|
||
"""用户端查询订单(通过手机号+订单号,无需认证)"""
|
||
try:
|
||
order_number = request.args.get('order_number')
|
||
phone = request.args.get('phone')
|
||
|
||
if not all([order_number, phone]):
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '缺少必要参数'
|
||
}), 400
|
||
|
||
# 验证手机号格式
|
||
if not phone or not re.match(r'^1[3-9]\d{9}$', phone):
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '手机号格式不正确'
|
||
}), 400
|
||
|
||
# 查询订单表
|
||
order = Order.query.filter_by(order_number=order_number, phone=phone).first()
|
||
if not order:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '订单不存在'
|
||
}), 404
|
||
|
||
# 返回订单信息
|
||
return jsonify({
|
||
'success': True,
|
||
'data': order.to_dict()
|
||
})
|
||
|
||
except Exception as e:
|
||
current_app.logger.error(f"查询订单失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误'
|
||
}), 500
|
||
|
||
|
||
@api_bp.route('/user/orders', methods=['GET'])
|
||
def get_user_orders():
|
||
"""用户端获取订单列表(通过手机号查询,无需认证)"""
|
||
try:
|
||
phone = request.args.get('phone')
|
||
|
||
if not phone:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '缺少手机号参数'
|
||
}), 400
|
||
|
||
# 验证手机号格式
|
||
if not re.match(r'^1[3-9]\d{9}$', phone):
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '手机号格式不正确'
|
||
}), 400
|
||
|
||
# 查询该手机号的所有订单
|
||
orders = Order.query.filter_by(phone=phone).order_by(Order.create_time.desc()).all()
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'data': {
|
||
'orders': [order.to_dict() for order in orders]
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
current_app.logger.error(f"获取订单列表失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误'
|
||
}), 500
|
||
|
||
|
||
@api_bp.route('/user/pay/alipay', methods=['POST'])
|
||
def create_alipay_payment():
|
||
"""创建支付宝支付订单"""
|
||
try:
|
||
data = request.get_json()
|
||
if not data:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '请求数据为空'
|
||
}), 400
|
||
|
||
order_number = data.get('order_number')
|
||
payment_type = data.get('payment_type', 'pc') # pc 或 wap
|
||
|
||
if not order_number:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '缺少订单号参数'
|
||
}), 400
|
||
|
||
# 查询订单
|
||
order = Order.query.filter_by(order_number=order_number).first()
|
||
if not order:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '订单不存在'
|
||
}), 404
|
||
|
||
# 检查订单状态
|
||
if order.status != 0: # 只有待支付的订单才能支付
|
||
return jsonify({
|
||
'success': False,
|
||
'message': f'订单当前状态不允许支付,当前状态:{order.get_status_name()}'
|
||
}), 400
|
||
|
||
# 获取支付宝配置
|
||
from app.utils.alipay import AlipayHelper
|
||
alipay_helper = AlipayHelper(current_app)
|
||
|
||
# 构建支付参数
|
||
subject = f"{order.product.product_name if order.product else '软件授权'}"
|
||
notify_url = f"{request.url_root}api/v/pay/alipay1/user/notify"
|
||
return_url = f"{request.url_root}payment/result?order_number={order_number}"
|
||
|
||
# 创建支付链接
|
||
if payment_type == 'wap':
|
||
# 手机网站支付
|
||
payment_url = alipay_helper.create_wap_payment_url(
|
||
order_number=order_number,
|
||
amount=order.amount,
|
||
subject=subject,
|
||
notify_url=notify_url,
|
||
return_url=return_url
|
||
)
|
||
else:
|
||
# PC网站支付
|
||
payment_url = alipay_helper.create_payment_url(
|
||
order_number=order_number,
|
||
amount=order.amount,
|
||
subject=subject,
|
||
notify_url=notify_url,
|
||
return_url=return_url
|
||
)
|
||
|
||
# 更新订单支付方式
|
||
order.payment_method = 'alipay'
|
||
db.session.commit()
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'message': '支付链接创建成功',
|
||
'data': {
|
||
'payment_url': payment_url,
|
||
'order_number': order_number,
|
||
'amount': order.amount
|
||
}
|
||
})
|
||
|
||
except ValueError as e:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': f'支付配置错误: {str(e)}'
|
||
}), 500
|
||
except Exception as e:
|
||
db.session.rollback()
|
||
current_app.logger.error(f"创建支付宝支付失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误'
|
||
}), 500
|
||
|
||
|
||
@api_bp.route('/pay/alipay1/user/notify', methods=['POST'])
|
||
def alipay_notify():
|
||
"""支付宝异步通知回调处理"""
|
||
try:
|
||
# 获取支付宝通知数据
|
||
notify_data = request.form.to_dict()
|
||
|
||
# 记录通知日志
|
||
current_app.logger.info(f"收到支付宝异步通知: {notify_data}")
|
||
|
||
# 获取支付宝配置
|
||
from app.utils.alipay import AlipayHelper
|
||
alipay_helper = AlipayHelper(current_app)
|
||
|
||
# 验证交易状态
|
||
is_valid, trade_status = alipay_helper.verify_trade_status(notify_data)
|
||
|
||
if not is_valid:
|
||
current_app.logger.warning(f"支付宝通知验证失败: {notify_data}")
|
||
return 'fail', 400
|
||
|
||
# 获取订单号
|
||
order_number = notify_data.get('out_trade_no')
|
||
trade_no = notify_data.get('trade_no') # 支付宝交易号
|
||
total_amount = notify_data.get('total_amount')
|
||
|
||
# 查询订单
|
||
order = Order.query.filter_by(order_number=order_number).first()
|
||
if not order:
|
||
current_app.logger.error(f"订单不存在: {order_number}")
|
||
return 'fail', 404
|
||
|
||
# 检查订单状态
|
||
if order.status != 0: # 订单已处理
|
||
current_app.logger.info(f"订单已处理: {order_number}, 状态: {order.status}")
|
||
return 'success'
|
||
|
||
# 验证订单金额
|
||
if float(order.amount) != float(total_amount):
|
||
current_app.logger.error(f"订单金额不匹配: 订单{order.amount} vs 通知{total_amount}")
|
||
return 'fail', 400
|
||
|
||
# 更新订单状态
|
||
order.status = 1 # 已支付
|
||
order.payment_time = datetime.utcnow()
|
||
order.payment_method = 'alipay'
|
||
db.session.commit()
|
||
|
||
# 记录操作日志
|
||
log_operation('ALIPAY_NOTIFY', 'ORDER', order.order_id, {
|
||
'order_number': order_number,
|
||
'trade_no': trade_no,
|
||
'amount': total_amount,
|
||
'trade_status': trade_status
|
||
})
|
||
|
||
# 支付成功后生成许可证
|
||
try:
|
||
from app.utils.license_generator import LicenseGenerator
|
||
license_gen = LicenseGenerator()
|
||
license_key = license_gen.generate_license(
|
||
product_id=order.product_id,
|
||
package_id=order.package_id,
|
||
contact_person=order.contact_person,
|
||
phone=order.phone,
|
||
quantity=order.quantity
|
||
)
|
||
|
||
# 记录许可证生成日志
|
||
log_operation('GENERATE_LICENSE', 'LICENSE', None, {
|
||
'order_number': order_number,
|
||
'license_key': license_key,
|
||
'product_id': order.product_id,
|
||
'quantity': order.quantity
|
||
})
|
||
|
||
current_app.logger.info(f"订单{order_number}支付成功,已生成许可证: {license_key}")
|
||
except Exception as e:
|
||
current_app.logger.error(f"生成许可证失败: {str(e)}")
|
||
# 许可证生成失败不应该影响支付状态
|
||
# 可以添加重试机制或人工处理
|
||
|
||
current_app.logger.info(f"订单{order_number}支付成功处理完成")
|
||
return 'success'
|
||
|
||
except Exception as e:
|
||
db.session.rollback()
|
||
current_app.logger.error(f"处理支付宝异步通知失败: {str(e)}")
|
||
return 'fail', 500
|
||
|
||
|
||
# ==================== 下载相关接口 ====================
|
||
|
||
@api_bp.route('/user/downloads/check', methods=['GET'])
|
||
def check_download_permission():
|
||
"""用户端检查下载权限(无需认证)"""
|
||
try:
|
||
product_id = request.args.get('product_id')
|
||
|
||
if not product_id:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '缺少产品ID参数'
|
||
}), 400
|
||
|
||
# 验证产品存在且启用
|
||
product = Product.query.filter_by(product_id=product_id, status=1).first()
|
||
if not product:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '产品不存在或已下架'
|
||
}), 404
|
||
|
||
# 获取产品是否为付费产品
|
||
is_paid = getattr(product, 'is_paid', False)
|
||
|
||
# 获取最新版本信息
|
||
latest_version = Version.query.filter_by(
|
||
product_id=product_id,
|
||
publish_status=1
|
||
).order_by(Version.update_time.desc(), Version.create_time.desc()).first()
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'data': {
|
||
'product_id': product_id,
|
||
'product_name': product.product_name,
|
||
'is_paid': is_paid,
|
||
'latest_version': latest_version.version_num if latest_version else None,
|
||
'download_url': latest_version.download_url if latest_version else None
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
current_app.logger.error(f"检查下载权限失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误'
|
||
}), 500 |