This commit is contained in:
张成
2026-03-24 16:07:02 +08:00
commit aa8eaa6ccd
121 changed files with 34042 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
/**
* 订阅开通 / 升级 / 续费 / 取消 / 到期扫描 / 支付确认
*/
const baseModel = require("../../middleware/baseModel");
const { op } = baseModel;
async function assertUserActive(userId) {
const u = await baseModel.biz_user.findByPk(userId);
if (!u) throw new Error("用户不存在");
if (u.status !== "active") throw new Error("用户已禁用");
return u;
}
async function assertPlanActive(planId) {
const p = await baseModel.biz_plan.findByPk(planId);
if (!p) throw new Error("套餐不存在");
if (p.status !== "active") throw new Error("套餐未上线");
return p;
}
async function openSubscription(body) {
const {
user_id,
plan_id,
start_time,
end_time,
status = "pending",
renew_mode = "manual",
payment_channel,
payment_ref,
} = body;
await assertUserActive(user_id);
await assertPlanActive(plan_id);
const row = await baseModel.biz_subscription.create({
user_id,
plan_id,
status,
start_time,
end_time,
renew_mode,
payment_channel: payment_channel || null,
payment_ref: payment_ref || null,
});
return row;
}
async function upgradeSubscription(body) {
const { subscription_id, new_plan_id, start_time, end_time } = body;
const sub = await baseModel.biz_subscription.findByPk(subscription_id);
if (!sub) throw new Error("订阅不存在");
await assertPlanActive(new_plan_id);
await sub.update({
plan_id: new_plan_id,
start_time: start_time || sub.start_time,
end_time: end_time || sub.end_time,
});
return sub;
}
async function renewSubscription(body) {
const { subscription_id, end_time } = body;
const sub = await baseModel.biz_subscription.findByPk(subscription_id);
if (!sub) throw new Error("订阅不存在");
await sub.update({
end_time,
status: "active",
});
return sub;
}
async function cancelSubscription(body) {
const { subscription_id } = body;
const sub = await baseModel.biz_subscription.findByPk(subscription_id);
if (!sub) throw new Error("订阅不存在");
await sub.update({ status: "cancelled" });
return sub;
}
/** 每天扫描:将已过期且仍为 active 的订阅置为 expired */
async function expireDueSubscriptions() {
const now = new Date();
const [n] = await baseModel.biz_subscription.update(
{ status: "expired" },
{
where: {
status: "active",
end_time: { [op.lt]: now },
},
}
);
return n;
}
/** 线下确认pending -> active写入 payment_ref */
async function confirmOfflinePayment(body) {
const { subscription_id, payment_ref } = body;
if (!subscription_id) throw new Error("缺少 subscription_id");
const sub = await baseModel.biz_subscription.findByPk(subscription_id);
if (!sub) throw new Error("订阅不存在");
await sub.update({
status: "active",
payment_channel: "offline",
payment_ref: payment_ref || sub.payment_ref,
});
return sub;
}
/** 链接支付确认MVP与线下类似仅标记渠道 */
async function confirmLinkPayment(body) {
const { subscription_id, payment_ref } = body;
if (!subscription_id) throw new Error("缺少 subscription_id");
const sub = await baseModel.biz_subscription.findByPk(subscription_id);
if (!sub) throw new Error("订阅不存在");
await sub.update({
status: "active",
payment_channel: "pay_link",
payment_ref: payment_ref || sub.payment_ref,
});
return sub;
}
module.exports = {
openSubscription,
upgradeSubscription,
renewSubscription,
cancelSubscription,
expireDueSubscriptions,
confirmOfflinePayment,
confirmLinkPayment,
};