feat(coupon): 优惠券中心(领取)+ 我的券 + 下单核销/回退
- 新表 user_coupons(快照面额/门槛/类目/有效期)+ orders.user_coupon_id - 仓储:getCouponsWithClaimed/claimCoupon/getUserCoupons/resolveUserCoupon/consumeUserCoupon/releaseUserCouponByOrder - 接口:POST /api/marketing/coupons/:id/claim(同券仅一次、受总量限制)、GET /api/marketing/my-coupons(自动标记过期)、券中心带 claimed 标识 - 下单:createOrder 支持 userCouponId(校验归属/门槛/类目/有效期,服务端算抵扣,与订单同事务占用) - 回退:买家取消/大神拒单/超时退款/仲裁退款 均把券退回为 UNUSED - 小程序:新增 pages/user/coupons(券中心+我的券)、个人中心入口(可用张数)、下单改为选择"我的券"并传 userCouponId - 回归 94 套件/666 用例全绿;冒烟 9/9(领取/重复领取409/不存在404/下单抵扣100-10=90/取消回退)
这个提交包含在:
@@ -1728,13 +1728,51 @@ app.delete('/api/admin/recharge-tiers/:id', async (req: Request, res: Response)
|
||||
app.get('/api/marketing/coupons', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const category = req.query.category ? String(req.query.category) : undefined;
|
||||
const list = await repo.getCoupons(category);
|
||||
const uid = Number(req.query.userId || req.headers['x-user-id'] || 0) || 0;
|
||||
const list = await repo.getCouponsWithClaimed(uid, category);
|
||||
res.json({ code: 20000, data: list });
|
||||
} catch (err: any) {
|
||||
res.status(500).json({ code: 50000, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 领取优惠券(同一用户同一券仅可领一次)
|
||||
app.post('/api/marketing/coupons/:id/claim', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const couponId = Number(req.params.id);
|
||||
const uid = resolveSecureUserId(
|
||||
req,
|
||||
Number(req.body?.userId || req.query.userId || req.headers['x-user-id'] || 0),
|
||||
'领取优惠券'
|
||||
);
|
||||
const claimed = await repo.claimCoupon(uid, couponId);
|
||||
res.json({ code: 20000, data: claimed, message: '优惠券领取成功' });
|
||||
} catch (err: any) {
|
||||
const statusCode = err.status || 400;
|
||||
const errCode = err.code || 40000;
|
||||
res.status(statusCode).json({ code: errCode, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 我的优惠券(status: UNUSED | USED | EXPIRED,缺省返回全部)
|
||||
app.get('/api/marketing/my-coupons', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const uid = resolveSecureUserId(
|
||||
req,
|
||||
Number(req.query.userId || req.headers['x-user-id'] || 0),
|
||||
'查看我的优惠券'
|
||||
);
|
||||
const status = req.query.status ? String(req.query.status) : undefined;
|
||||
const list = await repo.getUserCoupons(uid, status);
|
||||
const usable = list.filter((c: any) => c.status === 'UNUSED').length;
|
||||
res.json({ code: 20000, data: { list, usable } });
|
||||
} catch (err: any) {
|
||||
const statusCode = err.status || 400;
|
||||
const errCode = err.code || 40000;
|
||||
res.status(statusCode).json({ code: errCode, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 管理后台:获取全部优惠券
|
||||
app.get('/api/admin/coupons', async (_req: Request, res: Response) => {
|
||||
try {
|
||||
|
||||
在新工单中引用
屏蔽一个用户