update pay ui

This commit is contained in:
2025-12-12 13:30:55 +08:00
parent ff9da338ad
commit 0d1343f330
10 changed files with 1152 additions and 5 deletions

455
app.py
View File

@@ -1063,9 +1063,12 @@ class PaymentOrder(db.Model):
original_amount = db.Column(db.Numeric(10, 2), nullable=True) # 原价
discount_amount = db.Column(db.Numeric(10, 2), nullable=True, default=0) # 折扣金额
promo_code_id = db.Column(db.Integer, db.ForeignKey('promo_codes.id'), nullable=True) # 优惠码ID
wechat_order_id = db.Column(db.String(64), nullable=True)
payment_method = db.Column(db.String(20), default='wechat') # 支付方式: wechat/alipay
wechat_order_id = db.Column(db.String(64), nullable=True) # 微信交易号
alipay_trade_no = db.Column(db.String(64), nullable=True) # 支付宝交易号
prepay_id = db.Column(db.String(64), nullable=True)
qr_code_url = db.Column(db.String(200), nullable=True)
qr_code_url = db.Column(db.String(200), nullable=True) # 微信支付二维码URL
pay_url = db.Column(db.String(2000), nullable=True) # 支付宝支付链接(较长)
status = db.Column(db.String(20), default='pending')
created_at = db.Column(db.DateTime, default=beijing_now)
paid_at = db.Column(db.DateTime, nullable=True)
@@ -1097,10 +1100,25 @@ class PaymentOrder(db.Model):
except Exception as e:
return False
def mark_as_paid(self, wechat_order_id, transaction_id=None):
def mark_as_paid(self, transaction_id, payment_method=None):
"""
标记订单为已支付
Args:
transaction_id: 交易号(微信或支付宝)
payment_method: 支付方式(可选,如果已设置则不覆盖)
"""
self.status = 'paid'
self.paid_at = beijing_now()
self.wechat_order_id = wechat_order_id
# 根据支付方式存储交易号
if payment_method:
self.payment_method = payment_method
if self.payment_method == 'alipay':
self.alipay_trade_no = transaction_id
else:
self.wechat_order_id = transaction_id
def to_dict(self):
return {
@@ -1113,7 +1131,9 @@ class PaymentOrder(db.Model):
'original_amount': float(self.original_amount) if self.original_amount else None,
'discount_amount': float(self.discount_amount) if self.discount_amount else 0,
'promo_code': self.promo_code.code if self.promo_code else None,
'payment_method': self.payment_method or 'wechat',
'qr_code_url': self.qr_code_url,
'pay_url': self.pay_url,
'status': self.status,
'is_expired': self.is_expired(),
'created_at': self.created_at.isoformat() if self.created_at else None,
@@ -2645,6 +2665,433 @@ def _parse_xml_callback(xml_data):
return None
# ========================================
# 支付宝支付相关API
# ========================================
@app.route('/api/payment/alipay/create-order', methods=['POST'])
def create_alipay_order():
"""
创建支付宝支付订单
Request Body:
{
"plan_name": "pro",
"billing_cycle": "yearly",
"promo_code": "WELCOME2025" // 可选
}
"""
try:
if 'user_id' not in session:
return jsonify({'success': False, 'error': '未登录'}), 401
data = request.get_json()
plan_name = data.get('plan_name')
billing_cycle = data.get('billing_cycle')
promo_code = (data.get('promo_code') or '').strip() or None
if not plan_name or not billing_cycle:
return jsonify({'success': False, 'error': '参数不完整'}), 400
# 使用简化价格计算
price_result = calculate_subscription_price_simple(session['user_id'], plan_name, billing_cycle, promo_code)
if 'error' in price_result:
return jsonify({'success': False, 'error': price_result['error']}), 400
amount = price_result['final_amount']
subscription_type = price_result.get('subscription_type', 'new')
# 检查是否为免费升级
if amount <= 0 and price_result.get('is_upgrade'):
return jsonify({
'success': False,
'error': '当前剩余价值可直接免费升级,请使用免费升级功能',
'should_free_upgrade': True,
'price_info': price_result
}), 400
# 创建订单
try:
original_amount = price_result.get('original_amount', amount)
discount_amount = price_result.get('discount_amount', 0)
order = PaymentOrder(
user_id=session['user_id'],
plan_name=plan_name,
billing_cycle=billing_cycle,
amount=amount,
original_amount=original_amount,
discount_amount=discount_amount
)
# 设置支付方式为支付宝
order.payment_method = 'alipay'
order.remark = f"{subscription_type}订阅" if subscription_type == 'renew' else "新购订阅"
# 关联优惠码
if promo_code and price_result.get('promo_code'):
promo_obj = PromoCode.query.filter_by(code=promo_code.upper()).first()
if promo_obj:
order.promo_code_id = promo_obj.id
print(f"📦 订单关联优惠码: {promo_obj.code} (ID: {promo_obj.id})")
db.session.add(order)
db.session.commit()
except Exception as e:
db.session.rollback()
return jsonify({'success': False, 'error': f'订单创建失败: {str(e)}'}), 500
# 调用支付宝支付API使用 subprocess 绕过 eventlet DNS 问题)
try:
import subprocess
script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'alipay_pay_worker.py')
# 先检查配置
check_result = subprocess.run(
[sys.executable, script_path, 'check'],
capture_output=True, text=True, timeout=10
)
if check_result.returncode != 0:
check_data = json.loads(check_result.stdout) if check_result.stdout else {}
error_msg = check_data.get('error', check_data.get('message', '支付宝配置错误'))
order.remark = f"支付宝配置错误 - {error_msg}"
db.session.commit()
return jsonify({
'success': False,
'error': f'支付宝支付暂不可用: {error_msg}'
}), 500
# 创建支付宝订单
plan_display_name = f"{plan_name.upper()}版本-{billing_cycle}"
subject = f"VFr-{plan_display_name}"
body = f"价值前沿订阅服务-{plan_display_name}"
create_result = subprocess.run(
[sys.executable, script_path, 'create', order.order_no, str(float(amount)), subject, body],
capture_output=True, text=True, timeout=60
)
print(f"[支付宝] 创建订单返回: {create_result.stdout}")
if create_result.stderr:
print(f"[支付宝] 错误输出: {create_result.stderr}")
alipay_result = json.loads(create_result.stdout) if create_result.stdout else {'success': False, 'error': '无返回'}
if alipay_result.get('success'):
# 获取支付宝返回的支付链接
pay_url = alipay_result['pay_url']
order.pay_url = pay_url
order.remark = f"支付宝支付 - 订单已创建"
db.session.commit()
return jsonify({
'success': True,
'data': order.to_dict(),
'message': '订单创建成功'
})
else:
order.remark = f"支付宝支付失败: {alipay_result.get('error')}"
db.session.commit()
return jsonify({
'success': False,
'error': f"支付宝订单创建失败: {alipay_result.get('error')}"
}), 500
except subprocess.TimeoutExpired:
order.remark = "支付宝支付超时"
db.session.commit()
return jsonify({'success': False, 'error': '支付宝支付超时'}), 500
except json.JSONDecodeError as e:
order.remark = f"支付宝返回解析失败: {str(e)}"
db.session.commit()
return jsonify({'success': False, 'error': '支付宝返回数据异常'}), 500
except Exception as e:
import traceback
print(f"[支付宝] Exception: {e}")
traceback.print_exc()
order.remark = f"支付异常: {str(e)}"
db.session.commit()
return jsonify({'success': False, 'error': '支付异常'}), 500
except Exception as e:
db.session.rollback()
return jsonify({'success': False, 'error': '创建订单失败'}), 500
@app.route('/api/payment/alipay/callback', methods=['POST'])
def alipay_payment_callback():
"""支付宝异步回调处理"""
try:
# 获取POST参数
callback_params = request.form.to_dict()
print(f"📥 收到支付宝支付回调: {callback_params}")
# 验证回调数据
try:
from alipay_pay import create_alipay_instance
alipay = create_alipay_instance()
verify_result = alipay.verify_callback(callback_params.copy())
if not verify_result['success']:
print(f"❌ 支付宝回调签名验证失败: {verify_result['error']}")
return 'fail'
callback_data = verify_result['data']
except Exception as e:
print(f"❌ 支付宝回调处理异常: {e}")
return 'fail'
# 获取关键字段
trade_status = callback_data.get('trade_status')
out_trade_no = callback_data.get('out_trade_no') # 商户订单号
trade_no = callback_data.get('trade_no') # 支付宝交易号
total_amount = callback_data.get('total_amount')
print(f"📦 支付宝回调数据解析:")
print(f" 交易状态: {trade_status}")
print(f" 订单号: {out_trade_no}")
print(f" 交易号: {trade_no}")
print(f" 金额: {total_amount}")
if not out_trade_no:
print("❌ 缺少订单号")
return 'fail'
# 查找订单
order = PaymentOrder.query.filter_by(order_no=out_trade_no).first()
if not order:
print(f"❌ 订单不存在: {out_trade_no}")
return 'fail'
# 只处理交易成功的回调
if trade_status in ['TRADE_SUCCESS', 'TRADE_FINISHED']:
print(f"🎉 支付宝支付成功: 订单 {out_trade_no}")
# 检查订单是否已经处理过
if order.status == 'paid':
print(f" 订单已处理过: {out_trade_no}")
return 'success'
# 更新订单状态
old_status = order.status
order.mark_as_paid(trade_no, 'alipay')
print(f"📝 订单状态已更新: {old_status} -> paid")
# 激活用户订阅
subscription = activate_user_subscription(order.user_id, order.plan_name, order.billing_cycle)
if subscription:
print(f"✅ 用户订阅已激活: 用户{order.user_id}, 套餐{order.plan_name}")
else:
print(f"⚠️ 订阅激活失败,但订单已标记为已支付")
# 记录优惠码使用情况
if order.promo_code_id:
try:
existing_usage = PromoCodeUsage.query.filter_by(order_id=order.id).first()
if not existing_usage:
usage = PromoCodeUsage(
promo_code_id=order.promo_code_id,
user_id=order.user_id,
order_id=order.id,
original_amount=order.original_amount or order.amount,
discount_amount=order.discount_amount or 0,
final_amount=order.amount
)
db.session.add(usage)
promo = PromoCode.query.get(order.promo_code_id)
if promo:
promo.current_uses = (promo.current_uses or 0) + 1
print(f"🎫 优惠码使用记录已创建: {promo.code}, 当前使用次数: {promo.current_uses}")
else:
print(f" 优惠码使用记录已存在,跳过")
except Exception as e:
print(f"⚠️ 记录优惠码使用失败: {e}")
db.session.commit()
elif trade_status == 'TRADE_CLOSED':
# 交易关闭
if order.status not in ['paid', 'cancelled']:
order.status = 'cancelled'
db.session.commit()
print(f"📝 订单已关闭: {out_trade_no}")
# 返回成功响应给支付宝
return 'success'
except Exception as e:
db.session.rollback()
print(f"❌ 支付宝回调处理失败: {e}")
import traceback
app.logger.error(f"支付宝回调处理错误: {e}", exc_info=True)
return 'fail'
@app.route('/api/payment/alipay/return', methods=['GET'])
def alipay_payment_return():
"""支付宝同步返回处理(用户支付后跳转回来)"""
try:
# 获取GET参数
return_params = request.args.to_dict()
print(f"📥 支付宝同步返回: {return_params}")
out_trade_no = return_params.get('out_trade_no')
if out_trade_no:
# 重定向到前端支付结果页面
return redirect(f'/pricing?payment_return=alipay&order_no={out_trade_no}')
else:
return redirect('/pricing?payment_return=alipay&error=missing_order')
except Exception as e:
print(f"❌ 支付宝同步返回处理失败: {e}")
return redirect('/pricing?payment_return=alipay&error=exception')
@app.route('/api/payment/alipay/order/<order_id>/status', methods=['GET'])
def check_alipay_order_status(order_id):
"""查询支付宝订单支付状态"""
try:
if 'user_id' not in session:
return jsonify({'success': False, 'error': '未登录'}), 401
# 查找订单
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,
'data': order.to_dict(),
'message': '订单已支付',
'payment_success': True
})
# 如果订单过期,标记为过期
if order.is_expired():
order.status = 'expired'
db.session.commit()
return jsonify({
'success': True,
'data': order.to_dict(),
'message': '订单已过期'
})
# 调用支付宝API查询真实状态
try:
import subprocess
script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'alipay_pay_worker.py')
query_proc = subprocess.run(
[sys.executable, script_path, 'query', order.order_no],
capture_output=True, text=True, timeout=30
)
query_result = json.loads(query_proc.stdout) if query_proc.stdout else {'success': False, 'error': '无返回'}
if query_result.get('success'):
trade_state = query_result.get('trade_state')
trade_no = query_result.get('trade_no')
if trade_state == 'SUCCESS':
# 支付成功,更新订单状态
order.mark_as_paid(trade_no, 'alipay')
# 激活用户订阅
activate_user_subscription(order.user_id, order.plan_name, order.billing_cycle)
# 记录优惠码使用情况
if order.promo_code_id:
try:
existing_usage = PromoCodeUsage.query.filter_by(order_id=order.id).first()
if not existing_usage:
usage = PromoCodeUsage(
promo_code_id=order.promo_code_id,
user_id=order.user_id,
order_id=order.id,
original_amount=order.original_amount or order.amount,
discount_amount=order.discount_amount or 0,
final_amount=order.amount
)
db.session.add(usage)
promo = PromoCode.query.get(order.promo_code_id)
if promo:
promo.current_uses = (promo.current_uses or 0) + 1
print(f"🎫 优惠码使用记录已创建: {promo.code}")
except Exception as e:
print(f"⚠️ 记录优惠码使用失败: {e}")
db.session.commit()
return jsonify({
'success': True,
'data': order.to_dict(),
'message': '支付成功!订阅已激活',
'payment_success': True
})
elif trade_state in ['NOTPAY', 'WAIT_BUYER_PAY']:
# 未支付或等待支付
return jsonify({
'success': True,
'data': order.to_dict(),
'message': '等待支付...',
'payment_success': False
})
elif trade_state in ['CLOSED', 'TRADE_CLOSED']:
# 交易关闭
order.status = 'cancelled'
db.session.commit()
return jsonify({
'success': True,
'data': order.to_dict(),
'message': '交易已关闭',
'payment_success': False
})
else:
# 其他状态
return jsonify({
'success': True,
'data': order.to_dict(),
'message': f'当前状态: {trade_state}',
'payment_success': False
})
else:
# 支付宝查询失败,返回当前状态
return jsonify({
'success': True,
'data': order.to_dict(),
'message': f"查询失败: {query_result.get('error')}",
'payment_success': False
})
except Exception as e:
# 查询失败,返回当前订单状态
return jsonify({
'success': True,
'data': order.to_dict(),
'message': '无法查询支付状态,请稍后重试',
'payment_success': False
})
except Exception as e:
return jsonify({'success': False, 'error': '查询失败'}), 500
@app.route('/api/auth/session', methods=['GET'])
def get_session_info():
"""获取当前登录用户信息"""