日常平台在运营的过程中,为了促进用户消费,提高平台的营收,通常会提供优惠券的功能。后台可以配置优惠券的发放规则,用户进入小程序可以自动发放优惠券。结合线下的拉新推广活动,用户可以提供优惠券的兑换码进行线上领取。本篇我们介绍一下优惠券的具体的开发过程。
1 创建数据源
|
|
|
|
|
|---|---|---|---|
id |
|
|
|
coupon_name |
|
|
|
coupon_type |
|
|
|
discount_value |
|
|
|
min_order_amount |
|
|
|
start_time |
|
|
|
end_time |
|
|
|
total_quantity |
|
|
|
redeemed_quantity |
|
|
|
per_user_limit |
|
|
|
is_stackable |
|
|
|
status |
|
|
|
created_at |
|
|
|
updated_at |
|
|
|
|
|
|
|
|
|---|---|---|---|
id |
|
|
|
user_id |
|
|
|
coupon_id |
|
|
coupons表) |
receive_time |
|
|
|
expire_time |
|
|
|
status |
|
|
|
order_id |
|
|
|
used_time |
|
|
|
created_at |
|
|
|
updated_at |
|
|
|
2 创建API
2.1 getAvailableUserCoupons (获取用户可用优惠券列表)
具体代码如下:
// API 名称: getAvailableUserCoupons
// 描述: 获取当前用户可用于新订单的优惠券列表
// 参数:
// - userId: 用户ID (必填)
// - orderAmount: 当前订单总金额 (用于判断满减条件)(可选,但在实际场景中非常有用)
// - applicableServiceIds: 订单中包含的服务/商品ID列表 (可选,用于判断适用范围)
const ErrorCode ={
SUCCESS: 0,
PARAM_ERROR: 1001,
NOT_FOUND: 1002,
SYSTEM_ERROR: 1003,
USER_NOT_EXISTS: 1005,
MEMBER_NOT_EXISTS: 1006, // 沿用,但可能实际是 user_not_exists
INVALID_AMOUNT: 1007,
INSUFFICIENT_BALANCE: 1008,
ORDER_NOT_EXISTS: 1009,
ORDER_ALREADY_PAID: 1010,
// 新增优惠券相关错误码
COUPON_NOT_EXISTS: 2001,
COUPON_EXPIRED: 2002,
COUPON_UNAVAILABLE: 2003, // 优惠券未到生效时间或已发完
COUPON_ALREADY_RECEIVED: 2004,
COUPON_NOT_BELONGS_TO_USER: 2005,
COUPON_ALREADY_USED: 2006,
COUPON_APPLICABILITY_ERROR: 2007, // 优惠券不适用于当前订单
USER_COUPON_NOT_EXISTS: 2008, // 用户未持有该优惠券实例
MAX_RECEIVE_LIMIT_REACHED: 2009, // 达到领取上限
};
module.exports = async function(params, context){
console.log('获取用户可用优惠券API入参:', params);
const { userId, orderAmount =0}= params;
if(!userId){
return{ code: ErrorCode.PARAM_ERROR, message: '用户ID不能为空'};
}
try {
// 1. 查询用户持有的未使用的、未过期的优惠券实例
const userCouponsResult = await context.callModel({
name: "user_coupons",
methodName: "wedaGetRecordsV2",
params: {
filter: {
where: {
user_id: {$eq: userId },
status: {$eq:'1'}, // 未使用
expire_time: {$gt: Date.now()} // 未过期
}
},
select: {$master:true}
}
});
if(!userCouponsResult ||!userCouponsResult.records || userCouponsResult.records.length ===0){
return{ code: ErrorCode.SUCCESS, data: [], message: '暂无可用优惠券,本身没领'};
}
const userCouponIds = userCouponsResult.records.map(uc => uc.coupon_id);
// 2. 查询这些优惠券的定义信息,确保优惠券本身处于活跃状态且在有效期内
const couponDefinitions = await context.callModel({
name: "coupons",
methodName: "wedaGetRecordsV2",
params: {
filter: {
where: {
_id: {$in: userCouponIds },
status: {$eq:'1'}, // 优惠券定义本身必须是活跃的
start_time: {$lte: Date.now()}, // 优惠券已生效
end_time: {$gt: Date.now()} // 优惠券未过期
}
},
select: {$master:true}
}
});
if(!couponDefinitions ||!couponDefinitions.records || couponDefinitions.records.length ===0){
return{ code: ErrorCode.SUCCESS, data: [], message: '暂无可用优惠券,状态不符合'};
}
const validCouponDefinitionsMap = new Map(couponDefinitions.records.map(c =>[c._id, c]));
const availableCoupons =[];
for(const uc of userCouponsResult.records){
const couponDef = validCouponDefinitionsMap.get(uc.coupon_id);
// 检查优惠券定义是否存在且有效
if(!couponDef){
continue;
}
// 检查订单金额是否满足满减条件 (如果有传入 orderAmount)
if(orderAmount >0&& couponDef.min_order_amount >0&& orderAmount < couponDef.min_order_amount){
continue; // 不满足满减条件,跳过
}
// 这里可以添加更复杂的适用性判断,例如:
// - 优惠券是否适用于 `applicableServiceIds` 中的服务/商品
// 简化版本假设优惠券适用所有服务,或适用性判断在前端完成
availableCoupons.push({
userCouponId: uc._id, // 用户持有的优惠券实例ID
couponId: couponDef._id,
couponName: couponDef.coupon_name,
couponType: couponDef.coupon_type,
discountValue: couponDef.discount_value,
minOrderAmount: couponDef.min_order_amount,
isStackable: couponDef.is_stackable,
expireTime: uc.expire_time // 使用用户持有的具体过期时间
});
}
return{
code: ErrorCode.SUCCESS,
data: availableCoupons,
message: '获取用户可用优惠券成功'
};
} catch (error){
console.error('获取用户可用优惠券API错误:', error);
return{
code: ErrorCode.SYSTEM_ERROR,
message: `系统错误: ${error.message}`
};
}
};
2.2 receiveCoupon (领取优惠券)
代码:
// API 名称: receiveCoupon
// 描述: 用户领取优惠券

// 参数:
// - userId: 用户ID (必填)
// - couponId: 要领取的优惠券定义ID (必填)
const ErrorCode ={
SUCCESS: 0,
PARAM_ERROR: 1001,
NOT_FOUND: 1002,
SYSTEM_ERROR: 1003,
USER_NOT_EXISTS: 1005,
MEMBER_NOT_EXISTS: 1006, // 沿用,但可能实际是 user_not_exists
INVALID_AMOUNT: 1007,
INSUFFICIENT_BALANCE: 1008,
ORDER_NOT_EXISTS: 1009,
ORDER_ALREADY_PAID: 1010,
// 新增优惠券相关错误码
COUPON_NOT_EXISTS: 2001,
COUPON_EXPIRED: 2002,
COUPON_UNAVAILABLE: 2003, // 优惠券未到生效时间或已发完
COUPON_ALREADY_RECEIVED: 2004,
COUPON_NOT_BELONGS_TO_USER: 2005,
COUPON_ALREADY_USED: 2006,
COUPON_APPLICABILITY_ERROR: 2007, // 优惠券不适用于当前订单
USER_COUPON_NOT_EXISTS: 2008, // 用户未持有该优惠券实例
MAX_RECEIVE_LIMIT_REACHED: 2009, // 达到领取上限
};
module.exports = async function(params, context){
console.log('领取优惠券API入参:', params);
const { userId, couponId }= params;
if(!userId ||!couponId){
return{ code: ErrorCode.PARAM_ERROR, message: '用户ID和优惠券ID不能为空'};
}
try {
// 1. 查询优惠券定义
const couponDef = await context.callModel({
name: "coupons",
methodName: "wedaGetItemV2",
params: {
filter: {
where: { _id: {$eq: couponId }}
},
select: {$master:true}
}
});
if(!couponDef ||!couponDef._id){
return{ code: ErrorCode.COUPON_NOT_EXISTS, message: '优惠券不存在'};
}
// 2. 检查优惠券状态和有效期
if(couponDef.status !=='1'){
return{ code: ErrorCode.COUPON_UNAVAILABLE, message: '优惠券当前不可领取'};
}
if(couponDef.start_time > Date.now()|| couponDef.end_time < Date.now()){
return{ code: ErrorCode.COUPON_EXPIRED, message: '优惠券未到领取时间或已过期'};
}
if(couponDef.total_quantity >0&& couponDef.redeemed_quantity >= couponDef.total_quantity){
return{ code: ErrorCode.COUPON_UNAVAILABLE, message: '优惠券已发完'};
}
// 3. 检查用户是否已达到领取上限
if(couponDef.per_user_limit >0){
const userReceivedCount = await context.callModel({
name: "user_coupons",
methodName: "wedaGetRecordsV2",
params: {
filter: {
where: {
user_id: {$eq: userId },
coupon_id: {$eq: couponId }
}
},
getCount:true,
pageSize:10,
pagepageNumber:1
}
});
if(userReceivedCount.total >0&& userReceivedCount.total >= couponDef.per_user_limit){
return{ code: ErrorCode.MAX_RECEIVE_LIMIT_REACHED, message: `您已达到此优惠券的领取上限(${couponDef.per_user_limit}张)`};
}
}
// 4. 创建用户优惠券实例
const userCouponData ={
user_id: { _id: userId }, // 假设user_id在模型中是关联类型
coupon_id: { _id: couponId }, // 假设coupon_id在模型中是关联类型
receive_time: Date.now(),
expire_time: couponDef.end_time, // 用户持有的优惠券过期时间继承自定义
status: '1'
};
const newUserCoupon = await context.callModel({
name: "user_coupons",
methodName: "wedaCreateV2",
params: {
data: userCouponData
}
});
// 5. 更新优惠券已发放数量 (可选,如果total_quantity需要精确控制)
// 注意:在高并发场景下,这里需要乐观锁或分布式锁来确保 `redeemed_quantity` 的准确性
await context.callModel({
name: "coupons",
methodName: "wedaUpdateV2",
params: {
data: { redeemed_quantity: couponDef.redeemed_quantity + 1},
filter: { where: { _id: {$eq: couponId }}}
}
});
return{
code: ErrorCode.SUCCESS,
data: {
userCouponId: newUserCoupon._id,
couponName: couponDef.coupon_name,
expireTime: userCouponData.expire_time
},
message: '优惠券领取成功'
};
} catch (error){
console.error('领取优惠券API错误:', error);
return{
code: ErrorCode.SYSTEM_ERROR,
message: `系统错误: ${error.message}`
};
}
};
3 后台功能
然后切换到页面布局,添加菜单
修改菜单的名称
配置筛选器
配置后的最终效果