fix(billing): add setImageQuota for admin absolute remaining/total
Memind CI / Test, build, and release guards (push) Has been cancelled
Memind CI / Test, build, and release guards (push) Has been cancelled
Grant-only bonus adjustments cannot lower capacity below the stored plan limit; admin set now updates period_images_limit when needed. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -704,6 +704,91 @@ export function createSubscriptionService(pool, { getPlanAsync = null } = {}) {
|
||||
}
|
||||
};
|
||||
|
||||
const setImageQuota = async (userId, { remaining = null, total = null } = {}, { operatorId = null, note = '' } = {}) => {
|
||||
const hasRemaining = remaining !== null && remaining !== undefined;
|
||||
const hasTotal = total !== null && total !== undefined;
|
||||
if (hasRemaining === hasTotal) {
|
||||
return { ok: false, message: '请指定 remaining 或 total 其中之一' };
|
||||
}
|
||||
const target = Math.floor(Number(hasRemaining ? remaining : total));
|
||||
if (!Number.isFinite(target) || target < 0) {
|
||||
return { ok: false, message: '额度必须是非负整数' };
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const conn = await pool.getConnection();
|
||||
try {
|
||||
await conn.beginTransaction();
|
||||
const [rows] = await conn.query(
|
||||
`SELECT * FROM h5_subscriptions
|
||||
WHERE user_id = ? AND status = 'active' AND expires_at > ?
|
||||
ORDER BY expires_at DESC LIMIT 1 FOR UPDATE`,
|
||||
[userId, now],
|
||||
);
|
||||
const sub = mapSubRow(rows[0]);
|
||||
if (!sub) {
|
||||
await conn.rollback();
|
||||
return { ok: false, message: '用户没有有效订阅,无法设置图片额度' };
|
||||
}
|
||||
if (sub.periodImagesLimit === 0) {
|
||||
await conn.rollback();
|
||||
return { ok: false, message: '无限额度套餐无法调整' };
|
||||
}
|
||||
|
||||
const used = sub.periodImagesUsed;
|
||||
const oldCapacity = sub.periodImagesLimit + sub.periodImagesBonus;
|
||||
const oldRemaining = Math.max(0, oldCapacity - used);
|
||||
const targetCapacity = hasRemaining ? used + target : target;
|
||||
if (targetCapacity < used) {
|
||||
await conn.rollback();
|
||||
return { ok: false, message: `额度不能低于已用量(${used} 张)` };
|
||||
}
|
||||
|
||||
let newLimit = sub.periodImagesLimit;
|
||||
let newBonus = sub.periodImagesBonus;
|
||||
if (targetCapacity >= sub.periodImagesLimit) {
|
||||
newBonus = targetCapacity - sub.periodImagesLimit;
|
||||
} else {
|
||||
newLimit = targetCapacity;
|
||||
newBonus = 0;
|
||||
}
|
||||
|
||||
if (newLimit === sub.periodImagesLimit && newBonus === sub.periodImagesBonus) {
|
||||
await conn.rollback();
|
||||
return { ok: true, subscription: sub, quota: computeImageQuotaView(sub), unchanged: true };
|
||||
}
|
||||
|
||||
await conn.query(
|
||||
`UPDATE h5_subscriptions
|
||||
SET period_images_limit = ?, period_images_bonus = ?, updated_at = ?
|
||||
WHERE id = ?`,
|
||||
[newLimit, newBonus, now, sub.id],
|
||||
);
|
||||
const updatedSub = {
|
||||
...sub,
|
||||
periodImagesLimit: newLimit,
|
||||
periodImagesBonus: newBonus,
|
||||
};
|
||||
const quota = computeImageQuotaView(updatedSub);
|
||||
const ledgerDelta = hasRemaining ? target - oldRemaining : targetCapacity - oldCapacity;
|
||||
await appendImageQuotaLedgerTx(conn, {
|
||||
userId,
|
||||
delta: ledgerDelta,
|
||||
balanceAfter: quota.unlimited ? null : quota.remaining,
|
||||
reason: 'admin_set',
|
||||
operatorId,
|
||||
note,
|
||||
});
|
||||
await conn.commit();
|
||||
return { ok: true, subscription: updatedSub, quota };
|
||||
} catch (err) {
|
||||
await conn.rollback();
|
||||
throw err;
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
};
|
||||
|
||||
const grantImageQuota = async (userId, delta, { operatorId = null, note = '' } = {}) => {
|
||||
const safeDelta = Math.floor(Number(delta));
|
||||
if (!Number.isFinite(safeDelta) || safeDelta === 0) {
|
||||
@@ -820,6 +905,7 @@ export function createSubscriptionService(pool, { getPlanAsync = null } = {}) {
|
||||
consumeImageQuota,
|
||||
getImageQuota,
|
||||
checkImageQuota,
|
||||
setImageQuota,
|
||||
grantImageQuota,
|
||||
listImageQuotaLedger,
|
||||
renewSubscription,
|
||||
|
||||
Reference in New Issue
Block a user