refactor: extract grantTrialIfEligible, fix race condition + hardcoded days
- New grantTrialIfEligible() in subscription.dao.js with SELECT FOR UPDATE to prevent concurrent bind race condition granting double trials - confirmBind + mockBind now call the shared function (was duplicated) - Trial days no longer hardcoded to 7 in binding — respects settings
这个提交包含在:
@@ -95,23 +95,8 @@ async function confirmBind(userId, deviceId, bindToken) {
|
|||||||
'UPDATE bindings SET bind_status = 1, bind_time = NOW() WHERE binding_id = ?',
|
'UPDATE bindings SET bind_status = 1, bind_time = NOW() WHERE binding_id = ?',
|
||||||
[rows[0].binding_id]
|
[rows[0].binding_id]
|
||||||
)
|
)
|
||||||
// Grant trial only if user has never had one before
|
const subscriptionDao = require('./subscription.dao')
|
||||||
const [activeSubs] = await conn.execute(
|
await subscriptionDao.grantTrialIfEligible(userId, conn)
|
||||||
'SELECT subscription_id FROM subscriptions WHERE user_id = ? AND status = 1 AND expire_time > NOW() LIMIT 1',
|
|
||||||
[userId]
|
|
||||||
)
|
|
||||||
if (activeSubs.length === 0) {
|
|
||||||
const [trialHistory] = await conn.execute(
|
|
||||||
"SELECT subscription_id FROM subscriptions WHERE user_id = ? AND plan = 'trial' LIMIT 1",
|
|
||||||
[userId]
|
|
||||||
)
|
|
||||||
if (trialHistory.length === 0) {
|
|
||||||
await conn.execute(
|
|
||||||
"INSERT INTO subscriptions (user_id, plan, status, amount, start_time, expire_time) VALUES (?, 'trial', 1, 0, NOW(), DATE_ADD(NOW(), INTERVAL 7 DAY))",
|
|
||||||
[userId]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -142,23 +127,8 @@ async function mockBind(userId, deviceId) {
|
|||||||
"INSERT INTO bindings (user_id, device_id, bind_token, bind_expires, bind_status, bind_time) VALUES (?, ?, 'mock', NOW(), 1, NOW())",
|
"INSERT INTO bindings (user_id, device_id, bind_token, bind_expires, bind_status, bind_time) VALUES (?, ?, 'mock', NOW(), 1, NOW())",
|
||||||
[userId, deviceId]
|
[userId, deviceId]
|
||||||
)
|
)
|
||||||
// Grant trial only if user has never had one before
|
const subscriptionDao = require('./subscription.dao')
|
||||||
const [activeSubs] = await conn.execute(
|
await subscriptionDao.grantTrialIfEligible(userId, conn)
|
||||||
'SELECT subscription_id FROM subscriptions WHERE user_id = ? AND status = 1 AND expire_time > NOW() LIMIT 1',
|
|
||||||
[userId]
|
|
||||||
)
|
|
||||||
if (activeSubs.length === 0) {
|
|
||||||
const [trialHistory] = await conn.execute(
|
|
||||||
"SELECT subscription_id FROM subscriptions WHERE user_id = ? AND plan = 'trial' LIMIT 1",
|
|
||||||
[userId]
|
|
||||||
)
|
|
||||||
if (trialHistory.length === 0) {
|
|
||||||
await conn.execute(
|
|
||||||
"INSERT INTO subscriptions (user_id, plan, status, amount, start_time, expire_time) VALUES (?, 'trial', 1, 0, NOW(), DATE_ADD(NOW(), INTERVAL 7 DAY))",
|
|
||||||
[userId]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { success: true }
|
return { success: true }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,34 @@ async function createTrial(userId, orderId, days) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grant trial if user is eligible (never had trial before, no active sub).
|
||||||
|
* Designed to be called within an existing transaction with a connection.
|
||||||
|
* Uses SELECT FOR UPDATE to prevent race conditions.
|
||||||
|
* @param {number} userId
|
||||||
|
* @param {Object} conn - transaction connection
|
||||||
|
* @param {number} [days] - trial days (default 7)
|
||||||
|
* @returns {Promise<boolean>} true if trial was granted
|
||||||
|
*/
|
||||||
|
async function grantTrialIfEligible(userId, conn, days) {
|
||||||
|
const trialDays = Number(days) || 7
|
||||||
|
const [activeSubs] = await conn.execute(
|
||||||
|
'SELECT subscription_id FROM subscriptions WHERE user_id = ? AND status = 1 AND expire_time > NOW() LIMIT 1 FOR UPDATE',
|
||||||
|
[userId]
|
||||||
|
)
|
||||||
|
if (activeSubs.length > 0) return false
|
||||||
|
const [trialHistory] = await conn.execute(
|
||||||
|
"SELECT subscription_id FROM subscriptions WHERE user_id = ? AND plan = 'trial' LIMIT 1 FOR UPDATE",
|
||||||
|
[userId]
|
||||||
|
)
|
||||||
|
if (trialHistory.length > 0) return false
|
||||||
|
await conn.execute(
|
||||||
|
"INSERT INTO subscriptions (user_id, plan, status, amount, start_time, expire_time) VALUES (?, 'trial', 1, 0, NOW(), DATE_ADD(NOW(), INTERVAL ? DAY))",
|
||||||
|
[userId, trialDays]
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Purchase / activate a subscription: extend existing or create new
|
* Purchase / activate a subscription: extend existing or create new
|
||||||
* If user has an active subscription, add days to current expire_time.
|
* If user has an active subscription, add days to current expire_time.
|
||||||
@@ -221,6 +249,7 @@ module.exports = {
|
|||||||
findTrial,
|
findTrial,
|
||||||
findAnyActive,
|
findAnyActive,
|
||||||
createTrial,
|
createTrial,
|
||||||
|
grantTrialIfEligible,
|
||||||
purchase,
|
purchase,
|
||||||
adminCreate,
|
adminCreate,
|
||||||
cancel,
|
cancel,
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户