# -*- coding: utf-8 -*- """ 新版订阅支付系统 API 路由 版本: v2.0.0 日期: 2025-11-19 使用方法: 将这些路由添加到你的 Flask app.py 中 """ from flask import jsonify, request, session from new_subscription_logic import ( calculate_subscription_price, create_subscription_order, activate_subscription_after_payment, get_subscription_button_text, get_current_subscription, check_subscription_status, get_user_subscription_history ) # ============================================ # API 路由定义 # ============================================ @app.route('/api/v2/subscription/plans', methods=['GET']) def get_subscription_plans_v2(): """ 获取订阅套餐列表(新版) Response: { "success": true, "data": [ { "plan_code": "pro", "plan_name": "Pro 专业版", "description": "为专业投资者打造", "prices": { "monthly": 299.00, "quarterly": 799.00, "semiannual": 1499.00, "yearly": 2699.00 }, "features": [...], "is_active": true }, ... ] } """ try: from models import SubscriptionPlan plans = SubscriptionPlan.query.filter_by(is_active=True).order_by( SubscriptionPlan.display_order ).all() data = [] for plan in plans: data.append({ 'plan_code': plan.plan_code, 'plan_name': plan.plan_name, 'description': plan.description, 'prices': { 'monthly': float(plan.price_monthly), 'quarterly': float(plan.price_quarterly), 'semiannual': float(plan.price_semiannual), 'yearly': float(plan.price_yearly) }, 'features': json.loads(plan.features) if plan.features else [], 'is_active': plan.is_active, 'display_order': plan.display_order }) return jsonify({ 'success': True, 'data': data }) except Exception as e: return jsonify({ 'success': False, 'error': f'获取套餐列表失败: {str(e)}' }), 500 @app.route('/api/v2/subscription/calculate-price', methods=['POST']) def calculate_price_v2(): """ 计算订阅价格(新版 - 新购和续费价格一致) Request Body: { "plan_code": "pro", "billing_cycle": "yearly", "promo_code": "WELCOME2025" // 可选 } Response: { "success": true, "data": { "plan_code": "pro", "plan_name": "Pro 专业版", "billing_cycle": "yearly", "original_price": 2699.00, "discount_amount": 539.80, "final_amount": 2159.20, "promo_code": "WELCOME2025", "promo_error": null } } """ try: if 'user_id' not in session: return jsonify({'success': False, 'error': '未登录'}), 401 data = request.get_json() plan_code = data.get('plan_code') billing_cycle = data.get('billing_cycle') promo_code = data.get('promo_code') if not plan_code or not billing_cycle: return jsonify({ 'success': False, 'error': '参数不完整' }), 400 # 计算价格 result = calculate_subscription_price( plan_code=plan_code, billing_cycle=billing_cycle, promo_code=promo_code, user_id=session['user_id'], db_session=db.session ) if not result.get('success'): return jsonify(result), 400 return jsonify({ 'success': True, 'data': result }) except Exception as e: return jsonify({ 'success': False, 'error': f'计算价格失败: {str(e)}' }), 500 @app.route('/api/v2/payment/create-order', methods=['POST']) def create_order_v2(): """ 创建支付订单(新版) Request Body: { "plan_code": "pro", "billing_cycle": "yearly", "promo_code": "WELCOME2025" // 可选 } Response: { "success": true, "data": { "order_no": "1732012345678901231234", "plan_code": "pro", "billing_cycle": "yearly", "subscription_type": "renew", // 或 "new" "original_price": 2699.00, "discount_amount": 539.80, "final_amount": 2159.20, "qr_code_url": "https://...", "status": "pending", "expired_at": "2025-11-19T15:30:00", ... } } """ try: if 'user_id' not in session: return jsonify({'success': False, 'error': '未登录'}), 401 data = request.get_json() plan_code = data.get('plan_code') billing_cycle = data.get('billing_cycle') promo_code = data.get('promo_code') if not plan_code or not billing_cycle: return jsonify({ 'success': False, 'error': '参数不完整' }), 400 # 创建订单 order_result = create_subscription_order( user_id=session['user_id'], plan_code=plan_code, billing_cycle=billing_cycle, promo_code=promo_code, db_session=db.session ) if not order_result.get('success'): return jsonify({ 'success': False, 'error': order_result.get('error') }), 400 order = order_result['order'] # 生成微信支付二维码 try: from wechat_pay import create_wechat_pay_instance, check_wechat_pay_ready is_ready, ready_msg = check_wechat_pay_ready() if not is_ready: # 使用模拟二维码 order.qr_code_url = f"https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=wxpay://order/{order.order_no}" order.remark = f"演示模式 - {ready_msg}" else: wechat_pay = create_wechat_pay_instance() # 创建微信支付订单 plan_display = f"{plan_code.upper()}-{billing_cycle}" wechat_result = wechat_pay.create_native_order( order_no=order.order_no, total_fee=float(order.final_amount), body=f"VFr-{plan_display}", product_id=f"{plan_code}_{billing_cycle}" ) if wechat_result['success']: wechat_code_url = wechat_result['code_url'] import urllib.parse encoded_url = urllib.parse.quote(wechat_code_url, safe='') qr_image_url = f"https://api.qrserver.com/v1/create-qr-code/?size=200x200&data={encoded_url}" order.qr_code_url = qr_image_url order.prepay_id = wechat_result.get('prepay_id') order.remark = f"微信支付 - {wechat_code_url}" else: order.qr_code_url = f"https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=wxpay://order/{order.order_no}" order.remark = f"微信支付失败: {wechat_result.get('error')}" except Exception as e: order.qr_code_url = f"https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=wxpay://order/{order.order_no}" order.remark = f"支付异常: {str(e)}" db.session.commit() return jsonify({ 'success': True, 'data': { 'id': order.id, 'order_no': order.order_no, 'plan_code': order.plan_code, 'billing_cycle': order.billing_cycle, 'subscription_type': order.subscription_type, 'original_price': float(order.original_price), 'discount_amount': float(order.discount_amount), 'final_amount': float(order.final_amount), 'promo_code': order.promo_code, 'qr_code_url': order.qr_code_url, 'status': order.status, 'expired_at': order.expired_at.isoformat() if order.expired_at else None, 'created_at': order.created_at.isoformat() if order.created_at else None }, 'message': '订单创建成功' }) except Exception as e: db.session.rollback() return jsonify({ 'success': False, 'error': f'创建订单失败: {str(e)}' }), 500 @app.route('/api/v2/payment/order//status', methods=['GET']) def check_order_status_v2(order_id): """ 查询订单支付状态(新版) Response: { "success": true, "payment_success": true, // 是否支付成功 "data": { "order_no": "...", "status": "paid", ... }, "message": "支付成功!订阅已激活" } """ try: if 'user_id' not in session: return jsonify({'success': False, 'error': '未登录'}), 401 from models import PaymentOrder order = PaymentOrder.query.filter_by( id=order_id, user_id=session['user_id'] ).first() if not order: return jsonify({'success': False, 'error': '订单不存在'}), 404 # 如果订单已经是已支付状态 if order.status == 'paid': return jsonify({ 'success': True, 'payment_success': True, 'data': { 'order_no': order.order_no, 'status': order.status, 'final_amount': float(order.final_amount) }, 'message': '订单已支付' }) # 如果订单过期 if order.is_expired(): order.status = 'expired' db.session.commit() return jsonify({ 'success': True, 'payment_success': False, 'data': {'status': 'expired'}, 'message': '订单已过期' }) # 调用微信支付API查询状态 try: from wechat_pay import create_wechat_pay_instance wechat_pay = create_wechat_pay_instance() query_result = wechat_pay.query_order(order_no=order.order_no) if query_result['success']: trade_state = query_result.get('trade_state') transaction_id = query_result.get('transaction_id') if trade_state == 'SUCCESS': # 支付成功 order.mark_as_paid(transaction_id) db.session.commit() # 激活订阅 activate_result = activate_subscription_after_payment( order.id, db_session=db.session ) if activate_result.get('success'): return jsonify({ 'success': True, 'payment_success': True, 'data': { 'order_no': order.order_no, 'status': 'paid' }, 'message': '支付成功!订阅已激活' }) else: return jsonify({ 'success': True, 'payment_success': True, 'data': {'status': 'paid'}, 'message': '支付成功,但激活失败,请联系客服' }) elif trade_state in ['NOTPAY', 'USERPAYING']: return jsonify({ 'success': True, 'payment_success': False, 'data': {'status': 'pending'}, 'message': '等待支付...' }) else: order.status = 'cancelled' db.session.commit() return jsonify({ 'success': True, 'payment_success': False, 'data': {'status': 'cancelled'}, 'message': '支付已取消' }) except Exception as e: # 查询失败,返回当前状态 pass return jsonify({ 'success': True, 'payment_success': False, 'data': {'status': order.status}, 'message': '无法查询支付状态,请稍后重试' }) except Exception as e: return jsonify({ 'success': False, 'error': f'查询失败: {str(e)}' }), 500 @app.route('/api/v2/payment/order//force-update', methods=['POST']) def force_update_status_v2(order_id): """ 强制更新订单支付状态(新版) 用于支付完成但页面未更新的情况 """ try: if 'user_id' not in session: return jsonify({'success': False, 'error': '未登录'}), 401 from models import PaymentOrder order = PaymentOrder.query.filter_by( id=order_id, user_id=session['user_id'] ).first() if not order: return jsonify({'success': False, 'error': '订单不存在'}), 404 # 检查微信支付状态 try: from wechat_pay import create_wechat_pay_instance wechat_pay = create_wechat_pay_instance() query_result = wechat_pay.query_order(order_no=order.order_no) if query_result['success'] and query_result.get('trade_state') == 'SUCCESS': transaction_id = query_result.get('transaction_id') # 标记订单为已支付 order.mark_as_paid(transaction_id) db.session.commit() # 激活订阅 activate_result = activate_subscription_after_payment( order.id, db_session=db.session ) if activate_result.get('success'): return jsonify({ 'success': True, 'payment_success': True, 'message': '状态更新成功!订阅已激活' }) else: return jsonify({ 'success': True, 'payment_success': True, 'message': '支付成功,但激活失败,请联系客服', 'error': activate_result.get('error') }) else: return jsonify({ 'success': True, 'payment_success': False, 'message': '微信支付状态未更新,请继续等待' }) except Exception as e: return jsonify({ 'success': False, 'error': f'查询微信支付状态失败: {str(e)}' }), 500 except Exception as e: return jsonify({ 'success': False, 'error': f'强制更新失败: {str(e)}' }), 500 @app.route('/api/v2/subscription/current', methods=['GET']) def get_current_subscription_v2(): """ 获取当前用户订阅信息(新版) Response: { "success": true, "data": { "subscription_id": "SUB_1732012345_12345", "plan_code": "pro", "plan_name": "Pro 专业版", "billing_cycle": "yearly", "status": "active", "start_date": "2025-11-19T00:00:00", "end_date": "2026-11-19T00:00:00", "days_left": 365, "auto_renew": false } } """ try: if 'user_id' not in session: return jsonify({'success': False, 'error': '未登录'}), 401 from models import SubscriptionPlan subscription = get_current_subscription(session['user_id']) if not subscription: return jsonify({ 'success': True, 'data': { 'plan_code': 'free', 'plan_name': '免费版', 'status': 'active' } }) # 获取套餐名称 plan = SubscriptionPlan.query.filter_by(plan_code=subscription.plan_code).first() plan_name = plan.plan_name if plan else subscription.plan_code.upper() # 计算剩余天数 from datetime import datetime now = datetime.now() days_left = (subscription.end_date - now).days if subscription.end_date > now else 0 return jsonify({ 'success': True, 'data': { 'subscription_id': subscription.subscription_id, 'plan_code': subscription.plan_code, 'plan_name': plan_name, 'billing_cycle': subscription.billing_cycle, 'status': subscription.status, 'start_date': subscription.start_date.isoformat() if subscription.start_date else None, 'end_date': subscription.end_date.isoformat() if subscription.end_date else None, 'days_left': days_left, 'auto_renew': subscription.auto_renew } }) except Exception as e: return jsonify({ 'success': False, 'error': f'获取订阅信息失败: {str(e)}' }), 500 @app.route('/api/v2/subscription/history', methods=['GET']) def get_subscription_history_v2(): """ 获取用户订阅历史(新版) Query Params: limit: 返回记录数量(默认10) Response: { "success": true, "data": [ { "subscription_id": "SUB_...", "plan_code": "pro", "billing_cycle": "yearly", "start_date": "...", "end_date": "...", "paid_amount": 2699.00, "status": "expired" }, ... ] } """ try: if 'user_id' not in session: return jsonify({'success': False, 'error': '未登录'}), 401 limit = request.args.get('limit', 10, type=int) subscriptions = get_user_subscription_history(session['user_id'], limit) data = [] for sub in subscriptions: data.append({ 'subscription_id': sub.subscription_id, 'plan_code': sub.plan_code, 'billing_cycle': sub.billing_cycle, 'start_date': sub.start_date.isoformat() if sub.start_date else None, 'end_date': sub.end_date.isoformat() if sub.end_date else None, 'paid_amount': float(sub.paid_amount), 'original_price': float(sub.original_price), 'discount_amount': float(sub.discount_amount), 'status': sub.status, 'created_at': sub.created_at.isoformat() if sub.created_at else None }) return jsonify({ 'success': True, 'data': data }) except Exception as e: return jsonify({ 'success': False, 'error': f'获取订阅历史失败: {str(e)}' }), 500 @app.route('/api/v2/subscription/button-text', methods=['POST']) def get_button_text_v2(): """ 获取订阅按钮文字(新版) Request Body: { "plan_code": "pro", "billing_cycle": "yearly" } Response: { "success": true, "button_text": "续费 Pro 专业版" } """ try: if 'user_id' not in session: return jsonify({ 'success': True, 'button_text': '选择套餐' }) data = request.get_json() plan_code = data.get('plan_code') billing_cycle = data.get('billing_cycle') if not plan_code or not billing_cycle: return jsonify({ 'success': False, 'error': '参数不完整' }), 400 button_text = get_subscription_button_text( session['user_id'], plan_code, billing_cycle ) return jsonify({ 'success': True, 'button_text': button_text }) except Exception as e: return jsonify({ 'success': False, 'error': f'获取按钮文字失败: {str(e)}' }), 500