fix: subscription extend instead of overwrite, improve placeholder pages
- 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
这个提交包含在:
@@ -1,9 +1,13 @@
|
|||||||
<view class="page">
|
<view class="page">
|
||||||
<view class="page-header" style="padding-top: {{statusBarHeight + 24}}px;">
|
<view class="page-header page-header-pink" style="padding-top: {{statusBarHeight + 24}}px;">
|
||||||
<view class="nav-back" bindtap="onBack">‹ 返回</view>
|
<view class="nav-back" bindtap="onBack">‹ 返回</view>
|
||||||
<view class="page-header-title">联系我们</view>
|
<view class="page-header-title">联系我们</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="page-content">
|
<view class="page-content">
|
||||||
<view class="placeholder">内容建设中...</view>
|
<view class="placeholder-container">
|
||||||
|
<view class="placeholder-icon">📬</view>
|
||||||
|
<view class="placeholder-text">内容建设中</view>
|
||||||
|
<view class="placeholder-sub">联系方式正在整理,敬请期待</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -1,6 +1,24 @@
|
|||||||
.placeholder {
|
.placeholder-container {
|
||||||
text-align: center;
|
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;
|
color: #999999;
|
||||||
font-size: 28rpx;
|
|
||||||
padding: 80rpx 0;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
<view class="page">
|
<view class="page">
|
||||||
<view class="page-header" style="padding-top: {{statusBarHeight + 24}}px;">
|
<view class="page-header page-header-pink" style="padding-top: {{statusBarHeight + 24}}px;">
|
||||||
<view class="nav-back" bindtap="onBack">‹ 返回</view>
|
<view class="nav-back" bindtap="onBack">‹ 返回</view>
|
||||||
<view class="page-header-title">使用帮助</view>
|
<view class="page-header-title">使用帮助</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="page-content">
|
<view class="page-content">
|
||||||
<view class="placeholder">内容建设中...</view>
|
<view class="placeholder-container">
|
||||||
|
<view class="placeholder-icon">📖</view>
|
||||||
|
<view class="placeholder-text">内容建设中</view>
|
||||||
|
<view class="placeholder-sub">帮助文档正在编写,敬请期待</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -1,6 +1,24 @@
|
|||||||
.placeholder {
|
.placeholder-container {
|
||||||
text-align: center;
|
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;
|
color: #999999;
|
||||||
font-size: 28rpx;
|
|
||||||
padding: 80rpx 0;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,9 +118,16 @@ Page({
|
|||||||
return
|
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({
|
wx.showModal({
|
||||||
title: '确认支付',
|
title: title,
|
||||||
content: '模拟支付 ¥' + plan.price + '?(测试环境)',
|
content: content,
|
||||||
success: function (res) {
|
success: function (res) {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
self.doPurchase(plan)
|
self.doPurchase(plan)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<button class="btn-primary btn-primary-gold" bindtap="onPurchase" disabled="{{purchasing}}" loading="{{purchasing}}">
|
<button class="btn-primary btn-primary-gold" bindtap="onPurchase" disabled="{{purchasing}}" loading="{{purchasing}}">
|
||||||
确认支付
|
{{subscription && subscription.status === 'active' && subRemaining > 0 ? '续费' : '确认支付'}}
|
||||||
</button>
|
</button>
|
||||||
<view class="agreement">支付即表示同意《订阅协议》</view>
|
<view class="agreement">支付即表示同意《订阅协议》</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -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 {number} userId
|
||||||
* @param {string} plan - 'monthly' | 'yearly' | 'trial'
|
* @param {string} plan - 'monthly' | 'yearly' | 'trial'
|
||||||
* @param {number} amount
|
* @param {number} amount
|
||||||
@@ -72,6 +74,18 @@ async function createTrial(userId, orderId) {
|
|||||||
*/
|
*/
|
||||||
async function purchase(userId, plan, amount, orderId, days) {
|
async function purchase(userId, plan, amount, orderId, days) {
|
||||||
return transaction(async conn => {
|
return transaction(async conn => {
|
||||||
|
const [rows] = await conn.execute(
|
||||||
|
'SELECT subscription_id FROM subscriptions WHERE user_id = ? AND status = 1 AND expire_time > NOW() LIMIT 1',
|
||||||
|
[userId]
|
||||||
|
)
|
||||||
|
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(
|
await conn.execute(
|
||||||
'UPDATE subscriptions SET status = 2 WHERE user_id = ? AND status = 1',
|
'UPDATE subscriptions SET status = 2 WHERE user_id = ? AND status = 1',
|
||||||
[userId]
|
[userId]
|
||||||
@@ -80,12 +94,14 @@ async function purchase(userId, plan, amount, orderId, days) {
|
|||||||
'INSERT INTO subscriptions (user_id, plan, status, amount, order_id, start_time, expire_time) VALUES (?, ?, 1, ?, ?, NOW(), DATE_ADD(NOW(), INTERVAL ? DAY))',
|
'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]
|
[userId, plan, amount, orderId, days]
|
||||||
)
|
)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Admin-create a subscription (expire old, insert new) without transaction
|
* Admin-create a subscription: extend existing or create new
|
||||||
* Used by admin subscription creation endpoint
|
* 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 {number} userId
|
||||||
* @param {string} plan
|
* @param {string} plan
|
||||||
* @param {number} amount
|
* @param {number} amount
|
||||||
@@ -94,6 +110,18 @@ async function purchase(userId, plan, amount, orderId, days) {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async function adminCreate(userId, plan, amount, orderId, days) {
|
async function adminCreate(userId, plan, amount, orderId, days) {
|
||||||
|
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 }
|
||||||
|
)
|
||||||
|
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(
|
await query(
|
||||||
'UPDATE subscriptions SET status = 2 WHERE user_id = :user_id AND status = 1',
|
'UPDATE subscriptions SET status = 2 WHERE user_id = :user_id AND status = 1',
|
||||||
{ user_id: userId }
|
{ user_id: userId }
|
||||||
@@ -102,6 +130,7 @@ async function adminCreate(userId, plan, amount, orderId, days) {
|
|||||||
'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))',
|
'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 }
|
{ user_id: userId, plan, amount, order_id: orderId, days }
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ router.post('/subscription/mock-purchase', requireUser, wrap(async (req, res) =>
|
|||||||
const orderId = 'MOCK' + Date.now()
|
const orderId = 'MOCK' + Date.now()
|
||||||
await subscriptionDao.purchase(req.user.user_id, plan, p.amount, orderId, p.days)
|
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 })
|
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) => {
|
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]
|
const p = PLANS[plan]
|
||||||
await subscriptionDao.purchase(userId, plan, p.amount, req.body.order_id || 'ORD' + Date.now(), p.days)
|
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 })
|
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
|
module.exports = router
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户