diff --git a/app.py b/app.py index ec5bd79..0996925 100644 --- a/app.py +++ b/app.py @@ -5,12 +5,77 @@ from blueprints.auth import auth_bp from blueprints.api import api_bp from blueprints.admin import admin_bp from blueprints.payment import payment_bp +from apscheduler.schedulers.background import BackgroundScheduler +from apscheduler.triggers.interval import IntervalTrigger import threading import time +import logging -# 导入模型(必须在 db.create_all() 之前导入) +# 导入模律(必需在 db.create_all() 之前导入) import models +# 定时任务函数 +def sync_pending_orders(): + """定时任务: 检查并同步30分钟内的待支付订单""" + from models import Order, User, get_bj_now + from services.alipay_service import AlipayService + from services.logger import system_logger + from datetime import timedelta + + try: + # 查询最还30分钟内的待支付订单 + thirty_min_ago = get_bj_now() - timedelta(minutes=30) + pending_orders = Order.query.filter( + Order.status == 'PENDING', + Order.created_at >= thirty_min_ago + ).all() + + if not pending_orders: + return + + alipay_service = AlipayService() + updated_count = 0 + + for order in pending_orders: + try: + # 查询订单状态 + alipay_result = alipay_service.query_order_status(order.out_trade_no) + + if alipay_result and alipay_result.get('trade_status') in ['TRADE_SUCCESS', 'TRADE_FINISHED']: + # 使用行级锁重新查询,防止并发问题 + order_locked = Order.query.filter_by(out_trade_no=order.out_trade_no).with_for_update().first() + + # 二次校验状态,防止异步回调已经处理 + if order_locked and order_locked.status == 'PENDING': + # 更新订单 + order_locked.status = 'PAID' + order_locked.trade_no = alipay_result.get('trade_no') + if not order_locked.paid_at: + order_locked.paid_at = get_bj_now() + + # 增加用户积分 + user = db.session.get(User, order_locked.user_id) + if user: + user.points += order_locked.points + system_logger.info(f"定时任务-订单支付成功", order_id=order_locked.out_trade_no, points=order_locked.points, user_id=user.id) + updated_count += 1 + + db.session.commit() + elif order_locked and order_locked.status == 'PAID': + # 订单已经被处理,跳过 + logging.info(f"定时任务-订单 {order.out_trade_no} 已被处理,跳过") + except Exception as e: + db.session.rollback() + logging.error(f"定时任务处理订单 {order.out_trade_no} 失败: {str(e)}") + + if updated_count > 0: + logging.info(f"定时任务完成,帮助更新了{updated_count}个订单") + + except Exception as e: + logging.error(f"定时任务异常: {str(e)}", exc_info=True) + +# 导入模律(必需在 db.create_all() 之前导入) + def create_app(): app = Flask(__name__) app.config.from_object(Config) @@ -110,6 +175,22 @@ def create_app(): db.create_all() print("✅ 数据库表已就绪") + # 创建并启动定时任务调度器 + try: + scheduler = BackgroundScheduler(daemon=True) + # 每分钟检查一次待支付订单 + scheduler.add_job( + func=sync_pending_orders, + trigger=IntervalTrigger(minutes=1), + id='sync_pending_orders', + name='同步待支付订单', + replace_existing=True + ) + scheduler.start() + print("🚀 定时任务引擎已启动,将每分钟检查一次待支付订单") + except Exception as e: + print(f"⚠️ 定时任务启动失败: {str(e)}") + return app app = create_app() diff --git a/blueprints/payment.py b/blueprints/payment.py index 6a273a7..4de5924 100644 --- a/blueprints/payment.py +++ b/blueprints/payment.py @@ -164,13 +164,110 @@ def api_payment_history(): "paid_at": o.paid_at_bj.strftime('%Y-%m-%d %H:%M:%S') if o.paid_at else None } for o in orders] }) +@payment_bp.route('/api/sync_order', methods=['POST']) +def api_sync_order(): + """主动查询订单状态并同步 - 用于用户手动关闭支付页面后检查是否支付成功""" + if 'user_id' not in session: + return jsonify({'code': 401, 'msg': '请先登录'}), 401 + + out_trade_no = request.form.get('out_trade_no') + if not out_trade_no: + return jsonify({'code': 400, 'msg': '订单号不能为空'}), 400 + + try: + # 查询订单 + order = Order.query.filter_by(out_trade_no=out_trade_no).first() + if not order: + return jsonify({'code': 404, 'msg': '订单不存在'}), 404 + + # 只有当前用户才能查询自己的订单 + if order.user_id != session['user_id']: + return jsonify({'code': 403, 'msg': '无权限访问此订单'}), 403 + + # 如果订单已经是PAID或FAILED状态,直接返回 + if order.status in ['PAID', 'FAILED']: + return jsonify({ + 'code': 200, + 'msg': '订单状态已确定', + 'status': order.status, + 'paid_at': order.paid_at_bj.strftime('%Y-%m-%d %H:%M:%S') if order.paid_at else None + }) + + # 向支付宝查询订单状态 + alipay_service = AlipayService() + alipay_result = alipay_service.query_order_status(out_trade_no) + + if not alipay_result: + return jsonify({'code': 500, 'msg': '查询支付宝订单失败,请稍后重试'}), 500 + + trade_status = alipay_result.get('trade_status') + + # 如果支付宝显示已支付,更新本地订单状态 + if trade_status in ['TRADE_SUCCESS', 'TRADE_FINISHED']: + # 使用行级锁重新查询,防止并发问题 + order_locked = Order.query.filter_by(out_trade_no=out_trade_no).with_for_update().first() + + # 二次校验状态,防止异步回调/定时任务已经处理 + if order_locked and order_locked.status == 'PENDING': + order_locked.status = 'PAID' + order_locked.trade_no = alipay_result.get('trade_no') + if not order_locked.paid_at: + order_locked.paid_at = get_bj_now() + + # 增加用户积分 + user = db.session.get(User, order_locked.user_id) + if user: + user.points += order_locked.points + system_logger.info(f"主动查询-订单支付成功", order_id=out_trade_no, points=order_locked.points, user_id=user.id) + + db.session.commit() + + return jsonify({ + 'code': 200, + 'msg': '订单已支付,积分已增加', + 'status': 'PAID', + 'points': order_locked.points, + 'paid_at': order_locked.paid_at_bj.strftime('%Y-%m-%d %H:%M:%S') + }) + elif order_locked and order_locked.status == 'PAID': + # 订单已经被处理,直接返回 + return jsonify({ + 'code': 200, + 'msg': '订单已支付', + 'status': 'PAID', + 'points': order_locked.points, + 'paid_at': order_locked.paid_at_bj.strftime('%Y-%m-%d %H:%M:%S') if order_locked.paid_at else None + }) + + # 支付宝显示未支付 + elif trade_status in ['TRADE_CLOSED', 'WAIT_BUYER_PAY']: + return jsonify({ + 'code': 200, + 'msg': '订单未支付', + 'status': 'PENDING', + 'trade_status': trade_status + }) + + else: + return jsonify({ + 'code': 200, + 'msg': f'订单状态: {trade_status}', + 'status': order.status, + 'trade_status': trade_status + }) + + except Exception as e: + system_logger.error(f"主动查询订单异常: {str(e)}") + db.session.rollback() + return jsonify({'code': 500, 'msg': f'查询失败: {str(e)}'}), 500 + @payment_bp.route('/notify', methods=['POST']) def payment_notify(): """支付宝异步通知""" try: data = request.form.to_dict() - with open("e:\\ai_v\\notify.log", "a") as f: - f.write(f"Received data: {data}\n") + # 记录异步通知到系统日志,而不是本地文件 + system_logger.info(f"支付宝异步通知接收", extra_data=str(data)) signature = data.get("sign") @@ -209,3 +306,31 @@ def payment_notify(): system_logger.error(f"处理异步通知异常: {str(e)}") db.session.rollback() return "fail" + +@payment_bp.route('/api/query/', methods=['GET']) +def api_query_order(out_trade_no): + """简单查询接口 - 获取订单当前状态而不自动更新""" + if 'user_id' not in session: + return jsonify({'code': 401, 'msg': '请先登录'}), 401 + + try: + order = Order.query.filter_by(out_trade_no=out_trade_no).first() + if not order: + return jsonify({'code': 404, 'msg': '订单不存在'}), 404 + + if order.user_id != session['user_id']: + return jsonify({'code': 403, 'msg': '无权限访问'}), 403 + + return jsonify({ + 'code': 200, + 'out_trade_no': order.out_trade_no, + 'status': order.status, + 'amount': float(order.amount), + 'points': order.points, + 'trade_no': order.trade_no, + 'created_at': order.created_at_bj.strftime('%Y-%m-%d %H:%M:%S'), + 'paid_at': order.paid_at_bj.strftime('%Y-%m-%d %H:%M:%S') if order.paid_at else None + }) + + except Exception as e: + return jsonify({'code': 500, 'msg': f'查询失败: {str(e)}'}), 500 \ No newline at end of file diff --git a/config.py b/config.py index faace9a..f5696c4 100644 --- a/config.py +++ b/config.py @@ -59,8 +59,8 @@ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC+BMpGTJMzDoOnjyGh69rDLbV/8rlB ALIPAY_PUBLIC_KEY = """-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlDx4KdOtOQE+tBq6jHKKFenRaRe2gbBnleBk++5gki9IQuxVyZUGTJixstf2gELFHWrGanpnwmGggXsqG+Rm5ZLJOlmFM1k0XeAIDvi6tP/rM+ZDFSu1bMBYtT5vzgVZC7mzIvOp9gsT/puqd3aNZmlviLD0R6OYN0zvFX+5qADZV7A9ziA+nXPFSHreBh7yY/q9ophVZNeHGPoYkDVI5++RrF1cALKOdit0giN5vxpe3ch9z3E6+FZg3LiP+1RW3tMiDQfp/SlVs6bNhLUtmlI5r7+mtFCKDUCEpnQ3S9e0II6rzyVXRyKCFs7qi5YzyhhmO3tJJoe9ilEFyNzfRQIDAQAB -----END PUBLIC KEY-----""" # 支付宝公钥 - ALIPAY_RETURN_URL = "http://860576.xyz/payment/return" # 支付成功跳转地址 - ALIPAY_NOTIFY_URL = "http://860576.xyz/payment/notify" # 支付异步通知地址 + ALIPAY_RETURN_URL = "https://860576.xyz/payment/return" # 支付成功跳转地址 + ALIPAY_NOTIFY_URL = "https://860576.xyz/payment/notify" # 支付异步通知地址 ALIPAY_DEBUG = False # 是否使用沙箱环境 # 开发模式配置 diff --git a/services/alipay_service.py b/services/alipay_service.py index dfe730f..9229eab 100644 --- a/services/alipay_service.py +++ b/services/alipay_service.py @@ -77,3 +77,21 @@ class AlipayService: except Exception as e: logger.error(f"验证签名时发生异常: {str(e)}", exc_info=True) return False + + def query_order_status(self, out_trade_no): + """查询订单状态 - 主动查询支付宝获取真实支付状态""" + try: + alipay = self.get_alipay_client() + # 调用支付宝订单查询接口 + result = alipay.api_alipay_trade_query(out_trade_no=out_trade_no) + + if result: + logger.info(f"订单查询成功: {out_trade_no}, 状态: {result.get('trade_status')}") + return result + else: + logger.warning(f"订单查询返回为空: {out_trade_no}") + return None + + except Exception as e: + logger.error(f"查询订单状态异常: {str(e)}", exc_info=True) + return None