From 1017fb5dae106b1779e560458d49ea8aaa281d80 Mon Sep 17 00:00:00 2001 From: Guoguo Date: Wed, 29 Apr 2026 08:35:58 -0700 Subject: [PATCH] fix: subscription extend instead of overwrite, improve placeholder pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Subscription purchase now extends expire_time when user has active subscription, instead of cancelling and replacing - Admin subscription creation uses same extend logic - Subscribe page shows "续费" button and extend message for renewals - Help and contact pages: add pink header, centered icon + text --- miniprogram/pages/contact/contact.wxml | 8 ++- miniprogram/pages/contact/contact.wxss | 26 ++++++-- miniprogram/pages/help/help.wxml | 8 ++- miniprogram/pages/help/help.wxss | 26 ++++++-- .../pages/subscribe-plans/subscribe-plans.js | 11 +++- .../subscribe-plans/subscribe-plans.wxml | 2 +- server/src/dao/subscription.dao.js | 59 ++++++++++++++----- server/src/routes/subscription.js | 6 +- 8 files changed, 114 insertions(+), 32 deletions(-) diff --git a/miniprogram/pages/contact/contact.wxml b/miniprogram/pages/contact/contact.wxml index 0ee6448..46fea90 100644 --- a/miniprogram/pages/contact/contact.wxml +++ b/miniprogram/pages/contact/contact.wxml @@ -1,9 +1,13 @@ - + ‹ 返回 联系我们 - 内容建设中... + + 📬 + 内容建设中 + 联系方式正在整理,敬请期待 + diff --git a/miniprogram/pages/contact/contact.wxss b/miniprogram/pages/contact/contact.wxss index 0aa46be..7e32010 100644 --- a/miniprogram/pages/contact/contact.wxss +++ b/miniprogram/pages/contact/contact.wxss @@ -1,6 +1,24 @@ -.placeholder { - text-align: center; +.placeholder-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 120rpx 40rpx; +} + +.placeholder-icon { + font-size: 100rpx; + margin-bottom: 32rpx; +} + +.placeholder-text { + font-size: 32rpx; + font-weight: 600; + color: #333333; + margin-bottom: 12rpx; +} + +.placeholder-sub { + font-size: 26rpx; color: #999999; - font-size: 28rpx; - padding: 80rpx 0; } diff --git a/miniprogram/pages/help/help.wxml b/miniprogram/pages/help/help.wxml index 5c359db..114bda9 100644 --- a/miniprogram/pages/help/help.wxml +++ b/miniprogram/pages/help/help.wxml @@ -1,9 +1,13 @@ - + ‹ 返回 使用帮助 - 内容建设中... + + 📖 + 内容建设中 + 帮助文档正在编写,敬请期待 + diff --git a/miniprogram/pages/help/help.wxss b/miniprogram/pages/help/help.wxss index 0aa46be..7e32010 100644 --- a/miniprogram/pages/help/help.wxss +++ b/miniprogram/pages/help/help.wxss @@ -1,6 +1,24 @@ -.placeholder { - text-align: center; +.placeholder-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 120rpx 40rpx; +} + +.placeholder-icon { + font-size: 100rpx; + margin-bottom: 32rpx; +} + +.placeholder-text { + font-size: 32rpx; + font-weight: 600; + color: #333333; + margin-bottom: 12rpx; +} + +.placeholder-sub { + font-size: 26rpx; color: #999999; - font-size: 28rpx; - padding: 80rpx 0; } diff --git a/miniprogram/pages/subscribe-plans/subscribe-plans.js b/miniprogram/pages/subscribe-plans/subscribe-plans.js index b75c801..e626ad2 100644 --- a/miniprogram/pages/subscribe-plans/subscribe-plans.js +++ b/miniprogram/pages/subscribe-plans/subscribe-plans.js @@ -118,9 +118,16 @@ Page({ return } + var isRenew = self.data.subscription && self.data.subscription.status === 'active' && self.data.subRemaining > 0 + var planDays = plan.key === 'yearly' ? 365 : 30 + var title = isRenew ? '确认续费' : '确认支付' + var content = isRenew + ? '在现有订阅基础上延长' + planDays + '天,模拟支付 ¥' + plan.price + '?(测试环境)' + : '模拟支付 ¥' + plan.price + '?(测试环境)' + wx.showModal({ - title: '确认支付', - content: '模拟支付 ¥' + plan.price + '?(测试环境)', + title: title, + content: content, success: function (res) { if (res.confirm) { self.doPurchase(plan) diff --git a/miniprogram/pages/subscribe-plans/subscribe-plans.wxml b/miniprogram/pages/subscribe-plans/subscribe-plans.wxml index 7e33f32..7af3250 100644 --- a/miniprogram/pages/subscribe-plans/subscribe-plans.wxml +++ b/miniprogram/pages/subscribe-plans/subscribe-plans.wxml @@ -38,7 +38,7 @@ 支付即表示同意《订阅协议》 diff --git a/server/src/dao/subscription.dao.js b/server/src/dao/subscription.dao.js index e94fc47..45e2caf 100644 --- a/server/src/dao/subscription.dao.js +++ b/server/src/dao/subscription.dao.js @@ -62,7 +62,9 @@ async function createTrial(userId, orderId) { } /** - * Purchase / activate a subscription: expire old active subs, insert new one + * Purchase / activate a subscription: extend existing or create new + * If user has an active subscription, add days to current expire_time. + * If no active subscription, insert a new one starting NOW(). * @param {number} userId * @param {string} plan - 'monthly' | 'yearly' | 'trial' * @param {number} amount @@ -72,20 +74,34 @@ async function createTrial(userId, orderId) { */ async function purchase(userId, plan, amount, orderId, days) { return transaction(async conn => { - await conn.execute( - 'UPDATE subscriptions SET status = 2 WHERE user_id = ? AND status = 1', + const [rows] = await conn.execute( + 'SELECT subscription_id FROM subscriptions WHERE user_id = ? AND status = 1 AND expire_time > NOW() LIMIT 1', [userId] ) - await conn.execute( - 'INSERT INTO subscriptions (user_id, plan, status, amount, order_id, start_time, expire_time) VALUES (?, ?, 1, ?, ?, NOW(), DATE_ADD(NOW(), INTERVAL ? DAY))', - [userId, plan, amount, orderId, days] - ) + if (rows.length > 0) { + // Extend existing active subscription + await conn.execute( + 'UPDATE subscriptions SET expire_time = DATE_ADD(expire_time, INTERVAL ? DAY), plan = ?, amount = amount + ? WHERE subscription_id = ?', + [days, plan, amount, rows[0].subscription_id] + ) + } else { + // Expire any stale rows (status=1 but already past expire_time), then insert new + await conn.execute( + 'UPDATE subscriptions SET status = 2 WHERE user_id = ? AND status = 1', + [userId] + ) + await conn.execute( + 'INSERT INTO subscriptions (user_id, plan, status, amount, order_id, start_time, expire_time) VALUES (?, ?, 1, ?, ?, NOW(), DATE_ADD(NOW(), INTERVAL ? DAY))', + [userId, plan, amount, orderId, days] + ) + } }) } /** - * Admin-create a subscription (expire old, insert new) without transaction - * Used by admin subscription creation endpoint + * Admin-create a subscription: extend existing or create new + * If user has an active subscription, add days to current expire_time. + * If no active subscription, insert a new one starting NOW(). * @param {number} userId * @param {string} plan * @param {number} amount @@ -94,14 +110,27 @@ async function purchase(userId, plan, amount, orderId, days) { * @returns {Promise} */ async function adminCreate(userId, plan, amount, orderId, days) { - await query( - 'UPDATE subscriptions SET status = 2 WHERE user_id = :user_id AND status = 1', + const active = await one( + 'SELECT subscription_id FROM subscriptions WHERE user_id = :user_id AND status = 1 AND expire_time > NOW() LIMIT 1', { user_id: userId } ) - await query( - 'INSERT INTO subscriptions (user_id, plan, status, amount, order_id, start_time, expire_time) VALUES (:user_id, :plan, 1, :amount, :order_id, NOW(), DATE_ADD(NOW(), INTERVAL :days DAY))', - { user_id: userId, plan, amount, order_id: orderId, days } - ) + if (active) { + // Extend existing active subscription + await query( + 'UPDATE subscriptions SET expire_time = DATE_ADD(expire_time, INTERVAL :days DAY), plan = :plan, amount = amount + :amount WHERE subscription_id = :sid', + { days, plan, amount, sid: active.subscription_id } + ) + } else { + // Expire any stale rows, then insert new + await query( + 'UPDATE subscriptions SET status = 2 WHERE user_id = :user_id AND status = 1', + { user_id: userId } + ) + await query( + 'INSERT INTO subscriptions (user_id, plan, status, amount, order_id, start_time, expire_time) VALUES (:user_id, :plan, 1, :amount, :order_id, NOW(), DATE_ADD(NOW(), INTERVAL :days DAY))', + { user_id: userId, plan, amount, order_id: orderId, days } + ) + } } /** diff --git a/server/src/routes/subscription.js b/server/src/routes/subscription.js index 3d1bc16..2951bc7 100644 --- a/server/src/routes/subscription.js +++ b/server/src/routes/subscription.js @@ -55,7 +55,8 @@ router.post('/subscription/mock-purchase', requireUser, wrap(async (req, res) => const orderId = 'MOCK' + Date.now() await subscriptionDao.purchase(req.user.user_id, plan, p.amount, orderId, p.days) await logDao.write({ user_id: req.user.user_id, action: 'subscription_mock_purchase', detail: '模拟购买: ' + plan, ip: req.ip }) - res.json(ok({ status: 'active', plan, remaining_days: p.days })) + const sub = await subscriptionDao.findActive(req.user.user_id) + res.json(ok({ status: 'active', plan, remaining_days: sub ? sub.remaining_days : p.days })) })) router.post('/subscription/trial', requireUser, wrap(async (req, res) => { @@ -76,7 +77,8 @@ router.post('/subscription/verify', requireAdmin, wrap(async (req, res) => { const p = PLANS[plan] await subscriptionDao.purchase(userId, plan, p.amount, req.body.order_id || 'ORD' + Date.now(), p.days) await logDao.write({ admin_id: req.admin.admin_id, action: 'subscription_verify', detail: '订阅生效: ' + plan + ' user:' + userId, ip: req.ip }) - res.json(ok({ status: 'active', plan, remaining_days: p.days })) + const sub = await subscriptionDao.findActive(userId) + res.json(ok({ status: 'active', plan, remaining_days: sub ? sub.remaining_days : p.days })) })) module.exports = router