131 lines
3.5 KiB
JavaScript
131 lines
3.5 KiB
JavaScript
/**
|
||
* 订阅开通 / 升级 / 续费 / 取消 / 到期扫描 / 支付确认
|
||
*/
|
||
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,
|
||
};
|