update pay function
This commit is contained in:
669
new_subscription_routes.py
Normal file
669
new_subscription_routes.py
Normal file
@@ -0,0 +1,669 @@
|
||||
# -*- 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/<int:order_id>/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/<int:order_id>/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
|
||||
Reference in New Issue
Block a user