update pay function
This commit is contained in:
316
app.py
316
app.py
@@ -1127,36 +1127,61 @@ def get_user_subscription_safe(user_id):
|
||||
|
||||
|
||||
def activate_user_subscription(user_id, plan_type, billing_cycle, extend_from_now=False):
|
||||
"""激活用户订阅
|
||||
"""
|
||||
激活用户订阅(新版:续费时从当前订阅结束时间开始延长)
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
plan_type: 套餐类型
|
||||
billing_cycle: 计费周期
|
||||
extend_from_now: 是否从当前时间开始延长(用于升级场景)
|
||||
plan_type: 套餐类型 (pro/max)
|
||||
billing_cycle: 计费周期 (monthly/quarterly/semiannual/yearly)
|
||||
extend_from_now: 废弃参数,保留以兼容(现在自动判断)
|
||||
|
||||
Returns:
|
||||
UserSubscription 对象 或 None
|
||||
"""
|
||||
try:
|
||||
subscription = UserSubscription.query.filter_by(user_id=user_id).first()
|
||||
if not subscription:
|
||||
# 新用户,创建订阅记录
|
||||
subscription = UserSubscription(user_id=user_id)
|
||||
db.session.add(subscription)
|
||||
|
||||
# 更新订阅类型和状态
|
||||
subscription.subscription_type = plan_type
|
||||
subscription.subscription_status = 'active'
|
||||
subscription.billing_cycle = billing_cycle
|
||||
|
||||
if not extend_from_now or not subscription.start_date:
|
||||
subscription.start_date = beijing_now()
|
||||
# 计算订阅周期天数
|
||||
cycle_days_map = {
|
||||
'monthly': 30,
|
||||
'quarterly': 90, # 3个月
|
||||
'semiannual': 180, # 6个月
|
||||
'yearly': 365
|
||||
}
|
||||
days = cycle_days_map.get(billing_cycle, 30)
|
||||
|
||||
if billing_cycle == 'monthly':
|
||||
subscription.end_date = beijing_now() + timedelta(days=30)
|
||||
else: # yearly
|
||||
subscription.end_date = beijing_now() + timedelta(days=365)
|
||||
now = beijing_now()
|
||||
|
||||
# 判断是新购还是续费
|
||||
if subscription.end_date and subscription.end_date > now:
|
||||
# 续费:从当前订阅结束时间开始延长
|
||||
start_date = subscription.end_date
|
||||
end_date = start_date + timedelta(days=days)
|
||||
else:
|
||||
# 新购或过期后重新购买:从当前时间开始
|
||||
start_date = now
|
||||
end_date = now + timedelta(days=days)
|
||||
subscription.start_date = start_date
|
||||
|
||||
subscription.end_date = end_date
|
||||
subscription.updated_at = now
|
||||
|
||||
subscription.updated_at = beijing_now()
|
||||
db.session.commit()
|
||||
return subscription
|
||||
|
||||
except Exception as e:
|
||||
print(f"激活订阅失败: {e}")
|
||||
db.session.rollback()
|
||||
return None
|
||||
|
||||
|
||||
@@ -1233,33 +1258,29 @@ def calculate_discount(promo_code, amount):
|
||||
return 0
|
||||
|
||||
|
||||
def calculate_remaining_value(subscription, current_plan):
|
||||
"""计算当前订阅的剩余价值"""
|
||||
try:
|
||||
if not subscription or not subscription.end_date:
|
||||
return 0
|
||||
def calculate_subscription_price_simple(user_id, to_plan_name, to_cycle, promo_code=None):
|
||||
"""
|
||||
简化版价格计算:续费用户和新用户价格完全一致,不计算剩余价值
|
||||
|
||||
now = beijing_now()
|
||||
if subscription.end_date <= now:
|
||||
return 0
|
||||
|
||||
days_left = (subscription.end_date - now).days
|
||||
|
||||
if subscription.billing_cycle == 'monthly':
|
||||
daily_value = float(current_plan.monthly_price) / 30
|
||||
else: # yearly
|
||||
daily_value = float(current_plan.yearly_price) / 365
|
||||
|
||||
return daily_value * days_left
|
||||
except:
|
||||
return 0
|
||||
|
||||
|
||||
def calculate_upgrade_price(user_id, to_plan_name, to_cycle, promo_code=None):
|
||||
"""计算升级所需价格
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
to_plan_name: 目标套餐名称 (pro/max)
|
||||
to_cycle: 计费周期 (monthly/quarterly/semiannual/yearly)
|
||||
promo_code: 优惠码(可选)
|
||||
|
||||
Returns:
|
||||
dict: 包含价格计算结果的字典
|
||||
dict: {
|
||||
'is_renewal': False/True, # 是否为续费
|
||||
'subscription_type': 'new'/'renew', # 订阅类型
|
||||
'current_plan': 'pro', # 当前套餐(如果有)
|
||||
'current_cycle': 'yearly', # 当前周期(如果有)
|
||||
'new_plan_price': 2699.00, # 新套餐价格
|
||||
'original_amount': 2699.00, # 原价
|
||||
'discount_amount': 0, # 优惠金额
|
||||
'final_amount': 2699.00, # 实付金额
|
||||
'promo_code': None, # 使用的优惠码
|
||||
'promo_error': None # 优惠码错误信息
|
||||
}
|
||||
"""
|
||||
try:
|
||||
# 1. 获取当前订阅
|
||||
@@ -1270,83 +1291,90 @@ def calculate_upgrade_price(user_id, to_plan_name, to_cycle, promo_code=None):
|
||||
if not to_plan:
|
||||
return {'error': '目标套餐不存在'}
|
||||
|
||||
# 3. 计算目标套餐价格
|
||||
new_price = float(to_plan.yearly_price if to_cycle == 'yearly' else to_plan.monthly_price)
|
||||
# 3. 根据计费周期获取价格
|
||||
# 优先从 pricing_options 获取价格
|
||||
price = None
|
||||
if to_plan.pricing_options:
|
||||
try:
|
||||
pricing_opts = json.loads(to_plan.pricing_options)
|
||||
# 查找匹配的周期
|
||||
for opt in pricing_opts:
|
||||
cycle_key = opt.get('cycle_key', '')
|
||||
months = opt.get('months', 0)
|
||||
|
||||
# 4. 如果是新订阅(非升级)
|
||||
if not current_sub or current_sub.subscription_type == 'free':
|
||||
result = {
|
||||
'is_upgrade': False,
|
||||
'new_plan_price': new_price,
|
||||
'remaining_value': 0,
|
||||
'upgrade_amount': new_price,
|
||||
'original_amount': new_price,
|
||||
'discount_amount': 0,
|
||||
'final_amount': new_price,
|
||||
'promo_code': None
|
||||
}
|
||||
# 匹配逻辑
|
||||
if (cycle_key == to_cycle or
|
||||
(to_cycle == 'monthly' and months == 1) or
|
||||
(to_cycle == 'quarterly' and months == 3) or
|
||||
(to_cycle == 'semiannual' and months == 6) or
|
||||
(to_cycle == 'yearly' and months == 12)):
|
||||
price = float(opt.get('price', 0))
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
# 应用优惠码
|
||||
if promo_code:
|
||||
promo, error = validate_promo_code(promo_code, to_plan_name, to_cycle, new_price, user_id)
|
||||
if promo:
|
||||
discount = calculate_discount(promo, new_price)
|
||||
result['discount_amount'] = discount
|
||||
result['final_amount'] = new_price - discount
|
||||
result['promo_code'] = promo.code
|
||||
elif error:
|
||||
result['promo_error'] = error
|
||||
# 如果 pricing_options 中没有找到,使用旧的 monthly_price/yearly_price
|
||||
if price is None:
|
||||
if to_cycle == 'yearly':
|
||||
price = float(to_plan.yearly_price) if to_plan.yearly_price else 0
|
||||
else: # 默认月付
|
||||
price = float(to_plan.monthly_price) if to_plan.monthly_price else 0
|
||||
|
||||
return result
|
||||
if price <= 0:
|
||||
return {'error': f'{to_cycle} 周期价格未配置'}
|
||||
|
||||
# 5. 升级场景:计算剩余价值
|
||||
current_plan = SubscriptionPlan.query.filter_by(name=current_sub.subscription_type, is_active=True).first()
|
||||
if not current_plan:
|
||||
return {'error': '当前套餐信息不存在'}
|
||||
# 4. 判断是新购还是续费
|
||||
is_renewal = False
|
||||
subscription_type = 'new'
|
||||
current_plan = None
|
||||
current_cycle = None
|
||||
|
||||
remaining_value = calculate_remaining_value(current_sub, current_plan)
|
||||
|
||||
# 6. 计算升级差价
|
||||
upgrade_amount = max(0, new_price - remaining_value)
|
||||
|
||||
# 7. 判断升级类型
|
||||
upgrade_type = 'new'
|
||||
if current_sub.subscription_type != to_plan_name and current_sub.billing_cycle != to_cycle:
|
||||
upgrade_type = 'both'
|
||||
elif current_sub.subscription_type != to_plan_name:
|
||||
upgrade_type = 'plan_upgrade'
|
||||
elif current_sub.billing_cycle != to_cycle:
|
||||
upgrade_type = 'cycle_change'
|
||||
if current_sub and current_sub.subscription_type in ['pro', 'max']:
|
||||
# 如果当前是付费用户,则为续费
|
||||
is_renewal = True
|
||||
subscription_type = 'renew'
|
||||
current_plan = current_sub.subscription_type
|
||||
current_cycle = current_sub.billing_cycle
|
||||
|
||||
# 5. 构建结果(续费和新购价格完全一致)
|
||||
result = {
|
||||
'is_upgrade': True,
|
||||
'upgrade_type': upgrade_type,
|
||||
'current_plan': current_sub.subscription_type,
|
||||
'current_cycle': current_sub.billing_cycle,
|
||||
'current_end_date': current_sub.end_date.isoformat() if current_sub.end_date else None,
|
||||
'new_plan_price': new_price,
|
||||
'remaining_value': remaining_value,
|
||||
'upgrade_amount': upgrade_amount,
|
||||
'original_amount': upgrade_amount,
|
||||
'is_renewal': is_renewal,
|
||||
'subscription_type': subscription_type,
|
||||
'current_plan': current_plan,
|
||||
'current_cycle': current_cycle,
|
||||
'new_plan_price': price,
|
||||
'original_amount': price,
|
||||
'discount_amount': 0,
|
||||
'final_amount': upgrade_amount,
|
||||
'promo_code': None
|
||||
'final_amount': price,
|
||||
'promo_code': None,
|
||||
'promo_error': None
|
||||
}
|
||||
|
||||
# 8. 应用优惠码
|
||||
if promo_code and upgrade_amount > 0:
|
||||
promo, error = validate_promo_code(promo_code, to_plan_name, to_cycle, upgrade_amount, user_id)
|
||||
# 6. 应用优惠码
|
||||
if promo_code and promo_code.strip():
|
||||
promo, error = validate_promo_code(promo_code, to_plan_name, to_cycle, price, user_id)
|
||||
if promo:
|
||||
discount = calculate_discount(promo, upgrade_amount)
|
||||
result['discount_amount'] = discount
|
||||
result['final_amount'] = upgrade_amount - discount
|
||||
discount = calculate_discount(promo, price)
|
||||
result['discount_amount'] = float(discount)
|
||||
result['final_amount'] = price - float(discount)
|
||||
result['promo_code'] = promo.code
|
||||
elif error:
|
||||
result['promo_error'] = error
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
return {'error': str(e)}
|
||||
return {'error': f'价格计算失败: {str(e)}'}
|
||||
|
||||
|
||||
# 保留旧函数以兼容(标记为废弃)
|
||||
def calculate_upgrade_price(user_id, to_plan_name, to_cycle, promo_code=None):
|
||||
"""
|
||||
【已废弃】旧版升级价格计算函数,保留以兼容旧代码
|
||||
新代码请使用 calculate_subscription_price_simple
|
||||
"""
|
||||
# 直接调用新函数
|
||||
return calculate_subscription_price_simple(user_id, to_plan_name, to_cycle, promo_code)
|
||||
|
||||
|
||||
def initialize_subscription_plans_safe():
|
||||
@@ -1594,7 +1622,33 @@ def validate_promo_code_api():
|
||||
|
||||
@app.route('/api/subscription/calculate-price', methods=['POST'])
|
||||
def calculate_subscription_price():
|
||||
"""计算订阅价格(支持升级和优惠码)"""
|
||||
"""
|
||||
计算订阅价格(新版:续费和新购价格一致)
|
||||
|
||||
Request Body:
|
||||
{
|
||||
"to_plan": "pro",
|
||||
"to_cycle": "yearly",
|
||||
"promo_code": "WELCOME2025" // 可选
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"is_renewal": true, // 是否为续费
|
||||
"subscription_type": "renew", // new 或 renew
|
||||
"current_plan": "pro", // 当前套餐(如果有)
|
||||
"current_cycle": "monthly", // 当前周期(如果有)
|
||||
"new_plan_price": 2699.00,
|
||||
"original_amount": 2699.00,
|
||||
"discount_amount": 0,
|
||||
"final_amount": 2699.00,
|
||||
"promo_code": null,
|
||||
"promo_error": null
|
||||
}
|
||||
}
|
||||
"""
|
||||
try:
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'success': False, 'error': '未登录'}), 401
|
||||
@@ -1607,8 +1661,8 @@ def calculate_subscription_price():
|
||||
if not to_plan or not to_cycle:
|
||||
return jsonify({'success': False, 'error': '参数不完整'}), 400
|
||||
|
||||
# 计算价格
|
||||
result = calculate_upgrade_price(session['user_id'], to_plan, to_cycle, promo_code)
|
||||
# 使用新的简化价格计算函数
|
||||
result = calculate_subscription_price_simple(session['user_id'], to_plan, to_cycle, promo_code)
|
||||
|
||||
if 'error' in result:
|
||||
return jsonify({
|
||||
@@ -1630,7 +1684,16 @@ def calculate_subscription_price():
|
||||
|
||||
@app.route('/api/payment/create-order', methods=['POST'])
|
||||
def create_payment_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
|
||||
@@ -1643,16 +1706,14 @@ def create_payment_order():
|
||||
if not plan_name or not billing_cycle:
|
||||
return jsonify({'success': False, 'error': '参数不完整'}), 400
|
||||
|
||||
# 计算价格(包括升级和优惠码)
|
||||
price_result = calculate_upgrade_price(session['user_id'], plan_name, billing_cycle, promo_code)
|
||||
# 使用新的简化价格计算
|
||||
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']
|
||||
original_amount = price_result['original_amount']
|
||||
discount_amount = price_result['discount_amount']
|
||||
is_upgrade = price_result.get('is_upgrade', False)
|
||||
subscription_type = price_result.get('subscription_type', 'new') # new 或 renew
|
||||
|
||||
# 创建订单
|
||||
try:
|
||||
@@ -1663,48 +1724,23 @@ def create_payment_order():
|
||||
amount=amount
|
||||
)
|
||||
|
||||
# 添加扩展字段(使用动态属性)
|
||||
if hasattr(order, 'original_amount') or True: # 兼容性检查
|
||||
order.original_amount = original_amount
|
||||
order.discount_amount = discount_amount
|
||||
order.is_upgrade = is_upgrade
|
||||
# 添加订阅类型标记(用于前端展示)
|
||||
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:
|
||||
# 如果使用了优惠码,关联优惠码
|
||||
if promo_code and price_result.get('promo_code'):
|
||||
promo_obj = PromoCode.query.filter_by(code=promo_code.upper()).first()
|
||||
if promo_obj:
|
||||
# 注意:需要在 PaymentOrder 表中添加 promo_code_id 字段
|
||||
# 如果没有该字段,这行会报错,可以注释掉
|
||||
try:
|
||||
order.promo_code_id = promo_obj.id
|
||||
|
||||
# 如果是升级,记录原套餐信息
|
||||
if is_upgrade:
|
||||
order.upgrade_from_plan = price_result.get('current_plan')
|
||||
except:
|
||||
pass # 如果表中没有该字段,跳过
|
||||
|
||||
db.session.add(order)
|
||||
db.session.commit()
|
||||
|
||||
# 如果是升级订单,创建升级记录
|
||||
if is_upgrade and price_result.get('upgrade_type'):
|
||||
try:
|
||||
upgrade_record = SubscriptionUpgrade(
|
||||
user_id=session['user_id'],
|
||||
order_id=order.id,
|
||||
from_plan=price_result['current_plan'],
|
||||
from_cycle=price_result['current_cycle'],
|
||||
from_end_date=datetime.fromisoformat(price_result['current_end_date']) if price_result.get('current_end_date') else None,
|
||||
to_plan=plan_name,
|
||||
to_cycle=billing_cycle,
|
||||
to_end_date=beijing_now() + timedelta(days=365 if billing_cycle == 'yearly' else 30),
|
||||
remaining_value=price_result['remaining_value'],
|
||||
upgrade_amount=price_result['upgrade_amount'],
|
||||
actual_amount=amount,
|
||||
upgrade_type=price_result['upgrade_type']
|
||||
)
|
||||
db.session.add(upgrade_record)
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
print(f"创建升级记录失败: {e}")
|
||||
# 不影响主流程
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'success': False, 'error': f'订单创建失败: {str(e)}'}), 500
|
||||
|
||||
Reference in New Issue
Block a user