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-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="page-header-title">联系我们</view>
|
||||
</view>
|
||||
<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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<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="page-header-title">使用帮助</view>
|
||||
</view>
|
||||
<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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</view>
|
||||
|
||||
<button class="btn-primary btn-primary-gold" bindtap="onPurchase" disabled="{{purchasing}}" loading="{{purchasing}}">
|
||||
确认支付
|
||||
{{subscription && subscription.status === 'active' && subRemaining > 0 ? '续费' : '确认支付'}}
|
||||
</button>
|
||||
<view class="agreement">支付即表示同意《订阅协议》</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 {string} plan - 'monthly' | 'yearly' | 'trial'
|
||||
* @param {number} amount
|
||||
@@ -72,6 +74,18 @@ async function createTrial(userId, orderId) {
|
||||
*/
|
||||
async function purchase(userId, plan, amount, orderId, days) {
|
||||
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(
|
||||
'UPDATE subscriptions SET status = 2 WHERE user_id = ? AND status = 1',
|
||||
[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))',
|
||||
[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,6 +110,18 @@ async function purchase(userId, plan, amount, orderId, days) {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
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(
|
||||
'UPDATE subscriptions SET status = 2 WHERE user_id = :user_id AND status = 1',
|
||||
{ user_id: userId }
|
||||
@@ -103,6 +131,7 @@ async function adminCreate(userId, plan, amount, orderId, days) {
|
||||
{ user_id: userId, plan, amount, order_id: orderId, days }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a subscription (set status=3)
|
||||
|
||||
@@ -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
|
||||
|
||||
在新工单中引用
屏蔽一个用户