Files
wechatWeb/api/service/biz_subscription_logic.js
张成 aa8eaa6ccd init
2026-03-24 16:07:02 +08:00

131 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 订阅开通 / 升级 / 续费 / 取消 / 到期扫描 / 支付确认
*/
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,
};