#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 订单状态同步任务(兜底机制) 功能: 1. 定时查询 'created' 状态的订单 2. 调用微信支付接口查询真实状态 3. 同步订单状态(paid / expired) 4. 更新用户购买记录 运行方式: - 手动: python scripts/sync_order_status.py - 定时: crontab -e 添加 "*/5 * * * * python /path/to/sync_order_status.py" - Node.js: 使用 node-cron 定时调用 """ import sys import os import json import time import hashlib import random import string from datetime import datetime, timedelta try: import pymysql import requests except ImportError: print("[ERROR] 缺少依赖库,请安装:") print(" pip install pymysql requests") sys.exit(1) # 数据库配置 DB = { "host": "56b4c23f6853c.gz.cdb.myqcloud.com", "port": 14413, "user": "cdb_outerroot", "password": "Zhiqun1984", "database": "soul_miniprogram", "charset": "utf8mb4", "cursorclass": pymysql.cursors.DictCursor, "connect_timeout": 15, } # 微信支付配置(从环境变量或配置文件读取) WECHAT_PAY_CONFIG = { "appid": os.environ.get("WECHAT_APPID", "wxb8bbb2b10dec74aa"), "mch_id": os.environ.get("WECHAT_MCH_ID", "1318592501"), "api_key": os.environ.get("WECHAT_API_KEY", "YOUR_API_KEY_HERE"), # 需要配置真实的 API Key } # 订单超时时间(分钟) ORDER_TIMEOUT_MINUTES = 30 def log(message, level="INFO"): """统一日志输出""" timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"[{timestamp}] [{level}] {message}") def generate_nonce_str(length=32): """生成随机字符串""" return ''.join(random.choices(string.ascii_letters + string.digits, k=length)) def create_sign(params, api_key): """生成微信支付签名""" # 1. 参数排序 sorted_params = sorted(params.items()) # 2. 拼接字符串 string_a = '&'.join([f"{k}={v}" for k, v in sorted_params if v]) string_sign_temp = f"{string_a}&key={api_key}" # 3. MD5 加密并转大写 sign = hashlib.md5(string_sign_temp.encode('utf-8')).hexdigest().upper() return sign def query_wechat_order_status(out_trade_no): """ 查询微信支付订单状态 文档: https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_2 """ url = "https://api.mch.weixin.qq.com/pay/orderquery" params = { "appid": WECHAT_PAY_CONFIG["appid"], "mch_id": WECHAT_PAY_CONFIG["mch_id"], "out_trade_no": out_trade_no, "nonce_str": generate_nonce_str(), } # 生成签名 params["sign"] = create_sign(params, WECHAT_PAY_CONFIG["api_key"]) # 构建 XML 请求体 xml_data = "" for key, value in params.items(): xml_data += f"<{key}>{value}" xml_data += "" try: response = requests.post(url, data=xml_data.encode('utf-8'), headers={'Content-Type': 'application/xml'}, timeout=10) # 解析 XML 响应(简单处理,生产环境建议用 xml.etree.ElementTree) resp_text = response.text # 提取关键字段 if '' in resp_text: if '' in resp_text: return 'SUCCESS' elif '' in resp_text: return 'NOTPAY' elif '' in resp_text: return 'CLOSED' elif '' in resp_text: return 'REFUND' else: return 'UNKNOWN' else: log(f"查询订单失败: {resp_text}", "WARN") return 'ERROR' except Exception as e: log(f"查询微信订单异常: {e}", "ERROR") return 'ERROR' def sync_order_status(): """同步订单状态(主函数)""" log("========== 订单状态同步任务开始 ==========") conn = pymysql.connect(**DB) cursor = conn.cursor() try: # 1. 查询所有 'created' 状态的订单(最近 2 小时内创建的) two_hours_ago = datetime.now() - timedelta(hours=2) cursor.execute(""" SELECT id, order_sn, user_id, product_type, product_id, amount, created_at FROM orders WHERE status = 'created' AND created_at >= %s ORDER BY created_at DESC """, (two_hours_ago,)) pending_orders = cursor.fetchall() if not pending_orders: log("没有需要同步的订单") return log(f"找到 {len(pending_orders)} 个待同步订单") synced_count = 0 expired_count = 0 for order in pending_orders: order_sn = order['order_sn'] created_at = order['created_at'] # 2. 判断订单是否超时(超过 30 分钟) time_diff = datetime.now() - created_at if time_diff > timedelta(minutes=ORDER_TIMEOUT_MINUTES): # 超时订单:标记为 expired log(f"订单 {order_sn} 超时 ({time_diff.seconds // 60} 分钟),标记为 expired") cursor.execute(""" UPDATE orders SET status = 'expired', updated_at = NOW() WHERE order_sn = %s """, (order_sn,)) expired_count += 1 continue # 3. 查询微信支付状态(跳过,因为需要真实 API Key) # 生产环境中取消下面的注释 """ log(f"查询订单 {order_sn} 的微信支付状态...") wechat_status = query_wechat_order_status(order_sn) if wechat_status == 'SUCCESS': # 微信支付成功,更新本地订单为 paid log(f"订单 {order_sn} 微信支付成功,更新为 paid") cursor.execute(''' UPDATE orders SET status = 'paid', updated_at = NOW() WHERE order_sn = %s ''', (order_sn,)) # 更新用户购买记录 if order['product_type'] == 'fullbook': cursor.execute(''' UPDATE users SET has_full_book = 1 WHERE id = %s ''', (order['user_id'],)) synced_count += 1 elif wechat_status == 'NOTPAY': log(f"订单 {order_sn} 尚未支付,保持 created 状态") elif wechat_status == 'CLOSED': log(f"订单 {order_sn} 已关闭,标记为 cancelled") cursor.execute(''' UPDATE orders SET status = 'cancelled', updated_at = NOW() WHERE order_sn = %s ''', (order_sn,)) else: log(f"订单 {order_sn} 查询失败或状态未知: {wechat_status}", "WARN") """ # 测试环境:模拟查询(跳过微信接口) log(f"[TEST] 订单 {order_sn} 跳过微信查询(需配置 API Key)") conn.commit() log(f"同步完成: 同步 {synced_count} 个,超时 {expired_count} 个") except Exception as e: conn.rollback() log(f"同步失败: {e}", "ERROR") import traceback traceback.print_exc() finally: cursor.close() conn.close() log("========== 订单状态同步任务结束 ==========\n") if __name__ == "__main__": sync_order_status()