ai_v/blueprints/payment.py
24024 72bacacf4f feat(auth): 增加管理员订单管理和个人充值记录功能
- 新增全员订单管理页面,仅限管理员访问
- 购买积分页新增显示用户个人充值记录,管理员可见全员记录
- 支付模块增加用户充值历史查看页面及对应 API 接口
- buy.html 模板新增充值记录展示,包括个人和管理员视角的切换标签
- base.html 添加网站图标图像替换原图标字体图标
- 更新支付宝配置 AppID 和应用私钥
- 增加页面脚本支持充值记录标签页切换功能
- 补充日志,记录更多用户登录成功信息
2026-01-14 20:18:35 +08:00

178 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from flask import Blueprint, request, redirect, url_for, session, jsonify, render_template
from extensions import db
from models import Order, User
from services.alipay_service import AlipayService
import uuid
from datetime import datetime
import logging
logger = logging.getLogger(__name__)
payment_bp = Blueprint('payment', __name__, url_prefix='/payment')
# 积分价格配置
POINTS_PACKAGES = {
'50': {'points': 50, 'amount': 5.00},
'200': {'points': 200, 'amount': 20.00},
'1000': {'points': 1000, 'amount': 100.00},
'5000': {'points': 5000, 'amount': 500.00},
}
@payment_bp.route('/create', methods=['POST'])
def create_payment():
if 'user_id' not in session:
return jsonify({'code': 401, 'msg': '请先登录'}), 401
package_id = request.form.get('package_id')
if package_id not in POINTS_PACKAGES:
return jsonify({'code': 400, 'msg': '无效的套餐'}), 400
package = POINTS_PACKAGES[package_id]
user_id = session['user_id']
# 生成唯一订单号 (时间戳 + 随机位)
out_trade_no = datetime.now().strftime('%Y%m%d%H%M%S') + str(uuid.uuid4().hex[:6])
# 创建订单记录
try:
order = Order(
out_trade_no=out_trade_no,
user_id=user_id,
amount=package['amount'],
points=package['points'],
status='PENDING'
)
db.session.add(order)
db.session.commit()
except Exception as e:
db.session.rollback()
return f"订单创建失败: {str(e)}", 500
# 获取支付链接
try:
alipay_service = AlipayService()
pay_url = alipay_service.create_order_url(
out_trade_no=out_trade_no,
total_amount=package['amount'],
subject=f"购买{package['points']}积分"
)
return redirect(pay_url)
except Exception as e:
return f"支付链接生成失败: {str(e)}", 500
@payment_bp.route('/return')
def payment_return():
"""支付成功后的同步跳转页面"""
try:
logger.info(f"收到支付宝同步回调,参数: {dict(request.args)}")
data = request.args.to_dict()
signature = data.get("sign")
if not signature:
logger.error("同步回调缺少签名参数")
return "参数错误:缺少签名", 400
alipay_service = AlipayService()
# 直接传递原始字典,由 verify_notify 处理
success = alipay_service.verify_notify(data, signature)
out_trade_no = data.get('out_trade_no')
order = Order.query.filter_by(out_trade_no=out_trade_no).first()
if success:
logger.info(f"同步回调验证成功,订单号: {out_trade_no}")
# 同步通知仅用于页面展示,实际业务逻辑在异步通知 notify 中处理
return render_template('buy.html', success=True, order=order)
else:
logger.error(f"同步回调验证失败,订单号: {out_trade_no}")
return "支付验证失败", 400
except Exception as e:
logger.error(f"处理同步回调时发生异常: {str(e)}", exc_info=True)
return f"处理支付回调失败: {str(e)}", 500
@payment_bp.route('/history', methods=['GET'])
def payment_history():
"""获取当前用户的充值历史记录"""
if 'user_id' not in session:
return redirect(url_for('auth.login'))
user_id = session['user_id']
orders = Order.query.filter_by(user_id=user_id).order_by(Order.created_at.desc()).all()
return render_template('recharge_history.html', orders=orders)
@payment_bp.route('/api/history', methods=['GET'])
def api_payment_history():
"""API 获取当前用户的充值历史记录"""
if 'user_id' not in session:
return jsonify({'code': 401, 'msg': '请先登录'}), 401
user_id = session['user_id']
orders = Order.query.filter_by(user_id=user_id).order_by(Order.created_at.desc()).all()
return jsonify({
"orders": [{
"id": o.id,
"out_trade_no": o.out_trade_no,
"amount": float(o.amount),
"points": o.points,
"status": o.status,
"trade_no": o.trade_no,
"created_at": o.created_at.strftime('%Y-%m-%d %H:%M:%S'),
"paid_at": o.paid_at.strftime('%Y-%m-%d %H:%M:%S') if o.paid_at else None
} for o in orders]
})
@payment_bp.route('/notify', methods=['POST'])
def payment_notify():
"""支付宝异步通知"""
try:
logger.info(f"收到支付宝异步通知,参数: {request.form.to_dict()}")
data = request.form.to_dict()
signature = data.get("sign") # 不要pop保留原始数据
if not signature:
logger.error("异步通知缺少签名参数")
return "fail"
alipay_service = AlipayService()
success = alipay_service.verify_notify(data, signature)
if success and data.get('trade_status') in ['TRADE_SUCCESS', 'TRADE_FINISHED']:
out_trade_no = data.get('out_trade_no')
trade_no = data.get('trade_no')
logger.info(f"异步通知验证成功,订单号: {out_trade_no}, 支付宝交易号: {trade_no}")
order = Order.query.filter_by(out_trade_no=out_trade_no).first()
if order and order.status == 'PENDING':
order.status = 'PAID'
order.trade_no = trade_no
order.paid_at = datetime.utcnow()
# 给用户加积分
user = User.query.get(order.user_id)
if user:
user.points += order.points
logger.info(f"用户 {user.id} 充值 {order.points} 积分")
db.session.commit()
logger.info(f"订单 {out_trade_no} 处理成功")
return "success"
elif order:
logger.warning(f"订单 {out_trade_no} 状态为 {order.status},跳过处理")
return "success" # 已处理过的订单也返回success
else:
logger.error(f"未找到订单: {out_trade_no}")
return "fail"
else:
logger.error(f"异步通知验证失败或交易状态异常: {data.get('trade_status')}")
return "fail"
except Exception as e:
logger.error(f"处理异步通知时发生异常: {str(e)}", exc_info=True)
db.session.rollback()
return "fail"