From 082e644534cc63d59c4c188640f5f9daf9284928 Mon Sep 17 00:00:00 2001 From: zzlgreat Date: Thu, 20 Nov 2025 08:33:26 +0800 Subject: [PATCH] update pay function --- app.py | 96 +++++++++++++++++++ .../Subscription/SubscriptionContentNew.tsx | 48 +++++++++- .../Pages/Account/subscription-content.tsx | 2 +- 3 files changed, 143 insertions(+), 3 deletions(-) diff --git a/app.py b/app.py index fcbdcfbe..74635369 100755 --- a/app.py +++ b/app.py @@ -1427,6 +1427,10 @@ def calculate_subscription_price_simple(user_id, to_plan_name, to_cycle, promo_c remaining_value = current_price * (remaining_days / total_days) # 实付金额 = 新套餐价格 - 剩余价值 final_price = max(0, price - remaining_value) + + # 如果剩余价值 >= 新套餐价格,标记为免费升级 + if remaining_value >= price: + final_price = 0 elif current_plan == 'max' and to_plan_name == 'pro': # 降级:Max → Pro,到期后切换,全价购买 is_downgrade = True @@ -1786,6 +1790,89 @@ def calculate_subscription_price(): }), 500 +@app.route('/api/subscription/free-upgrade', methods=['POST']) +@login_required +def free_upgrade_subscription(): + """ + 免费升级订阅(当剩余价值 >= 新套餐价格时) + + Request Body: + { + "plan_name": "max", + "billing_cycle": "yearly" + } + """ + try: + data = request.get_json() + plan_name = data.get('plan_name') + billing_cycle = data.get('billing_cycle') + + if not plan_name or not billing_cycle: + return jsonify({'success': False, 'error': '参数不完整'}), 400 + + user_id = current_user.id + + # 计算价格,验证是否可以免费升级 + price_result = calculate_subscription_price_simple(user_id, plan_name, billing_cycle, None) + + if 'error' in price_result: + return jsonify({'success': False, 'error': price_result['error']}), 400 + + # 检查是否为升级且实付金额为0 + if not price_result.get('is_upgrade') or price_result.get('final_amount', 1) > 0: + return jsonify({'success': False, 'error': '当前情况不符合免费升级条件'}), 400 + + # 获取当前订阅 + subscription = UserSubscription.query.filter_by(user_id=user_id).first() + if not subscription: + return jsonify({'success': False, 'error': '未找到订阅记录'}), 404 + + # 计算新的到期时间(按剩余价值折算) + remaining_value = price_result.get('remaining_value', 0) + new_plan_price = price_result.get('new_plan_price', 0) + + if new_plan_price > 0: + # 计算可以兑换的新套餐天数 + value_ratio = remaining_value / new_plan_price + + cycle_days_map = { + 'monthly': 30, + 'quarterly': 90, + 'semiannual': 180, + 'yearly': 365 + } + new_cycle_days = cycle_days_map.get(billing_cycle, 365) + + # 新的到期天数 = 周期天数 × 价值比例 + new_days = int(new_cycle_days * value_ratio) + + # 更新订阅信息 + subscription.subscription_type = plan_name + subscription.billing_cycle = billing_cycle + subscription.start_date = datetime.utcnow() + subscription.end_date = datetime.utcnow() + timedelta(days=new_days) + subscription.subscription_status = 'active' + subscription.updated_at = datetime.utcnow() + + db.session.commit() + + return jsonify({ + 'success': True, + 'message': f'升级成功!您的{plan_name.upper()}版本将持续{new_days}天', + 'data': { + 'subscription_type': plan_name, + 'end_date': subscription.end_date.isoformat(), + 'days': new_days + } + }) + else: + return jsonify({'success': False, 'error': '价格计算异常'}), 500 + + except Exception as e: + db.session.rollback() + return jsonify({'success': False, 'error': f'升级失败: {str(e)}'}), 500 + + @app.route('/api/payment/create-order', methods=['POST']) def create_payment_order(): """ @@ -1819,6 +1906,15 @@ def create_payment_order(): amount = price_result['final_amount'] subscription_type = price_result.get('subscription_type', 'new') # new 或 renew + # 检查是否为免费升级(金额为0) + if amount <= 0 and price_result.get('is_upgrade'): + return jsonify({ + 'success': False, + 'error': '当前剩余价值可直接免费升级,请使用免费升级功能', + 'should_free_upgrade': True, + 'price_info': price_result + }), 400 + # 创建订单 try: order = PaymentOrder( diff --git a/src/components/Subscription/SubscriptionContentNew.tsx b/src/components/Subscription/SubscriptionContentNew.tsx index 37d9cde2..5517bccb 100644 --- a/src/components/Subscription/SubscriptionContentNew.tsx +++ b/src/components/Subscription/SubscriptionContentNew.tsx @@ -270,6 +270,46 @@ export default function SubscriptionContentNew() { try { const price = priceInfo?.final_amount || getCurrentPrice(selectedPlan); + // 检查是否为免费升级(剩余价值足够抵扣新套餐价格) + if (price === 0 && priceInfo?.is_upgrade) { + const response = await fetch('/api/subscription/free-upgrade', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ + plan_name: selectedPlan.name, + billing_cycle: selectedCycle, + }), + }); + + const data = await response.json(); + if (data.success) { + subscriptionEvents.trackPaymentSuccessful({ + planName: selectedPlan.name, + paymentMethod: 'free_upgrade', + amount: 0, + orderId: 'free_upgrade', + transactionId: 'free_upgrade', + }); + + toast({ + title: '升级成功!', + description: data.message, + status: 'success', + duration: 5000, + isClosable: true, + }); + + onClose(); + setTimeout(() => window.location.reload(), 2000); + return; + } else { + throw new Error(data.error || '免费升级失败'); + } + } + subscriptionEvents.trackPaymentInitiated({ planName: selectedPlan.name, paymentMethod: 'wechat_pay', @@ -1122,7 +1162,9 @@ export default function SubscriptionContentNew() { - 升级到{selectedPlan.displayName},立即生效!按差价补缴费用 + {priceInfo.final_amount === 0 + ? `恭喜!您的当前订阅剩余价值足够直接升级到${selectedPlan.displayName},无需支付额外费用!` + : `升级到${selectedPlan.displayName},立即生效!按差价补缴费用`} @@ -1293,7 +1335,9 @@ export default function SubscriptionContentNew() { bgGradient: 'linear-gradient(135deg, #F4E3A7, #D4AF37)', }} > - 创建微信支付订单 + {priceInfo?.is_upgrade && priceInfo?.final_amount === 0 + ? '立即免费升级' + : '创建微信支付订单'} ) : ( diff --git a/src/views/Pages/Account/subscription-content.tsx b/src/views/Pages/Account/subscription-content.tsx index 9cab1876..74c6819d 100644 --- a/src/views/Pages/Account/subscription-content.tsx +++ b/src/views/Pages/Account/subscription-content.tsx @@ -163,7 +163,7 @@ export const subscriptionConfig = { }, { question: 'Pro用户如何升级到Max?', - answer: '从Pro升级到Max需要补差价,升级后立即生效。系统会根据您Pro订阅的剩余价值计算需要补缴的费用。支付成功后,您将立即获得Max版本的所有功能。', + answer: '从Pro升级到Max需要补差价,升级后立即生效。系统会根据您Pro订阅的剩余价值计算需要补缴的费用。支付成功后,您将立即获得Max版本的所有功能。\n\n特别说明:如果您的Pro订阅剩余价值超过或等于Max套餐的价格,系统将自动为您免费升级到Max版本,无需支付额外费用。升级后的有效期将根据剩余价值按比例计算。例如:您的Pro年付版本剩余价值为1200元,选择Max月付版本(998元/月),系统将为您提供约36天的Max版本使用时长(1200÷998×30天)。', }, { question: 'Max用户可以切换到Pro吗?',