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