当前位置: 首页 > news >正文

CRMEB Pro 优惠券过期处理:定时任务、活动关联和历史订单怎么兼容

摘要

优惠券过期不是简单把状态改成“已过期”。在 CRMEB Pro 里,优惠券可能来自手动领取、系统发放、新人礼、会员卡激活、下单赠送、直播间领取,也可能已经被订单使用,或者在退款时需要回退。二开时如果只写一个定时任务批量更新状态,很容易误伤历史订单、赠券记录和直播间统计。

这篇文章围绕 CRMEB Pro 优惠券过期、使用、回退、订单赠券几个关键点做源码拆解,并给出适合二开的任务设计建议。

1. 用户券状态有三类

用户领取后的优惠券模型在:

app/model/activity/coupon/StoreCouponUser.php

状态映射很清楚:

protected$statusType=[0=>'未使用',1=>'已使用',2=>'已过期'];

获取方式也不止一种:

protectedarray$gainType=['send'=>'系统发放','get'=>'手动领取','newcomer'=>'新人礼赠送','activate_level'=>'会员卡激活赠送','user_first'=>'用户注册赠送','order'=>'下单赠送','luck_lottery'=>'抽奖赠送','live'=>'直播间领取'];

这就是为什么“过期处理”不能只看一张表的end_time。不同来源的券,后续关联动作不一样。

2. CRMEB Pro 已经有按用户检查过期的方法

StoreCouponUserServices里有一个很关键的方法:

/** * 过期优惠卷失效 */publicfunctioncheckInvalidCoupon($uid=0){$this->dao->update([['uid','=',$uid],['end_time','<',time()],['status','=','0']],['status'=>2]);}

获取用户有效券数量时,会先检查过期:

publicfunctiongetUserValidCouponCount(int$uid){$this->checkInvalidCoupon($uid);return$this->dao->getCount(['uid'=>$uid,'status'=>0]);}

这个逻辑适合“用户访问时顺手刷新自己的券状态”。但如果后台要做全量过期扫描,不能直接传uid = 0,否则只会处理uid = 0的数据。

3. 二开全量过期任务,建议单独封装 Dao 查询

不要在 Job 里直接写Db::name('store_coupon_user')。按照项目分层,建议在StoreCouponUserDao增加方法:

/** * 分页获取已过期但未处理的用户券ID * @param int $limit 每批处理数量 * @return array */publicfunctiongetExpiredUnusedIds(int$limit=500):array{return$this->getModel()->where('status',0)->where('end_time','<',time())->limit($limit)->column('id');}

再加一个批量更新方法:

/** * 批量标记用户券过期 * @param array $ids 用户券ID * @return bool */publicfunctionmarkExpiredByIds(array$ids):bool{$ids=array_values(array_filter(array_map('intval',$ids)));if(!$ids){returntrue;}return$this->getModel()->whereIn('id',$ids)->where('status',0)->update(['status'=>2])!==false;}

Service 层负责编排:

/** * 批量处理过期用户券 * @param int $limit 每批数量 * @return int 本批处理数量 */publicfunctionbatchInvalidExpiredCoupon(int$limit=500):int{$ids=$this->dao->getExpiredUnusedIds($limit);if(!$ids){return0;}$this->dao->markExpiredByIds($ids);returncount($ids);}

Job 只调用 Service:

classCouponExpireJobextendsBaseJobs{useQueueTrait;publicfunctiondoJob(int$limit=500){try{$services=app()->make(StoreCouponUserServices::class);do{$count=$services->batchInvalidExpiredCoupon($limit);}while($count===$limit);}catch(\Throwable$e){response_log_write(['message'=>'优惠券过期处理失败:'.$e->getMessage(),'file'=>$e->getFile(),'line'=>$e->getLine(),]);}returntrue;}}

这样做有三个好处:

Job 不直接查库 Dao 负责查询和批量更新 Service 负责循环和业务编排

4. 订单使用后,不能被过期任务改掉

用户下单使用优惠券时,会调用用户券服务:

publicfunctionuseCoupon(int$couponId,int$uid,array$cartInfo,array$promotions=[],int$liveRoomId=0){if(!$couponId||!$uid||!$cartInfo){returntrue;}$promotionsServices=app()->make(StorePromotionsServices::class);[$couponInfo,$couponPrice]=$promotionsServices->useCoupon($couponId,$uid,$cartInfo,$promotions,$liveRoomId);if($couponInfo){$this->dao->useCoupon($couponId);}returntrue;}

Dao 里会把状态改为已使用:

publicfunctionuseCoupon(int$id){return$this->getModel()->where('id',$id)->update(['status'=>1,'use_time'=>time()]);}

因此全量过期任务必须只处理:

status = 0 end_time < 当前时间

不要写成“所有 end_time 小于当前时间都改过期”。已使用的券是历史凭证,不能被覆盖成已过期,否则订单详情、售后、统计报表都会产生歧义。

5. 退款时优惠券可能要回退

订单退款时,CRMEB Pro 有回退优惠券逻辑:

publicfunctionintegralAndCouponBack($order){$res=true;// 回退优惠卷,拆分子订单不退优惠券if(!$order['pid']&&$order['coupon_id']&&$order['coupon_price']){$coumonUserServices=app()->make(StoreCouponUserServices::class);$res=$res&&$coumonUserServices->recoverCoupon((int)$order['coupon_id']);}[$order,$changeIntegral]=$this->regressionIntegral($order);return$res&&$order->save();}

recoverCoupon()会把已使用券恢复为未使用:

publicfunctionrecoverCoupon(int$id){$coupon=$this->dao->getOne(['id'=>$id],'id,status,live_room_coupon_id');if(!$coupon||!(int)$coupon['status']){returntrue;}$res=$this->dao->update($id,['status'=>0,'use_time'=>0]);if((int)$coupon['status']===1&&!empty($coupon['live_room_coupon_id'])){$liveRoomCouponDao=app()->make(LiveRoomCouponDao::class);$liveRoomCouponDao->decUseNum((int)$coupon['live_room_coupon_id']);}return$res;}

这里有一个细节:恢复成未使用后,如果优惠券本身已经超过end_time,下次用户进入优惠券列表或定时任务扫描时,应该再变成过期。不要在退款回退时直接判断过期并丢掉,因为回退动作的职责是恢复“这张券没有被订单消耗”。

6. 下单赠券也要考虑订单取消和退款

CRMEB Pro 支持商品关联优惠券,下单后赠送。相关 Job 在:

app/jobs/product/ProductCouponJob.php

下单后赠券:

publicfunctiondoJob($orderInfo){if(!$orderInfo)returntrue;try{$storeProductCouponServices=app()->make(StoreProductCouponServices::class);$storeProductCouponServices->giveOrderProductCoupon((int)$orderInfo['uid'],(int)$orderInfo['id']);}catch(\Throwable$e){response_log_write(['message'=>'赠送订单商品关联优惠券发生错误,错误原因:'.$e->getMessage(),'file'=>$e->getFile(),'line'=>$e->getLine()]);}returntrue;}

退款或订单失败时,会让赠券失效:

publicfunctionfailureProductCoupon($orderInfo){if(!$orderInfo)returntrue;try{$storeProductCouponServices=app()->make(StoreProductCouponServices::class);$storeProductCouponServices->failureOrderProductGiveCoupon($orderInfo['uid'],$orderInfo['id']);}catch(\Throwable$e){response_log_write(['message'=>'订单退款退还优惠券发生错误,错误原因:'.$e->getMessage(),'file'=>$e->getFile(),'line'=>$e->getLine()]);}returntrue;}

Service 中会按订单、用户、商品和状态筛选赠券:

$where=[['coupon_product_id','in',$productIds],['uid','=',$uid],['oid','in',$oidAll],['status','=',0],['type','=','order']];$res=$storeCouponUserServices->update($where,['status'=>2]);

这说明二开“赠券过期/失效”时,要区分两类状态:

自然过期:end_time 到期,未使用券 status 改为 2 业务失效:订单退款、活动失败、赠券来源失效,未使用券 status 改为 2

状态一样,但备注、日志、触发原因最好分开记录,方便售后排查。

7. 发布券本身过期和用户券过期不是一回事

发布券表store_coupon_issue用于控制“这张券还能不能被领取”,用户券表store_coupon_user用于控制“某个用户手里的券还能不能使用”。

发布券有效查询在 Dao 中:

publicfunctionvalidSearch(array$where=[]){returnparent::search($where)->where('status',1)->where('is_del',0)->where('remain_count > 0 OR is_permanent = 1')->where(function($query){$query->where(function($query){$query->where('start_time','<=',time())->where('end_time','>=',time());})->whereOr(function($query){$query->where('start_time',0)->where('end_time',0);});})->where(function($query4){$query4->where(function($query5){$query5->where('coupon_time',0)->where('end_use_time','>=',time());})->whereOr('coupon_time','>',0);});}

这里判断的是发布券是否还能展示、领取、发放。用户已经领取到手的券,要看store_coupon_user.start_time/end_time/status

二开时别把两个概念混到一个定时任务里:

发布券过期:影响后续能不能领 用户券过期:影响用户手里的券能不能用

8. 建议补一个过期处理日志

如果项目后续要做更细的售后排查,建议不要只改status,可以补一个轻量日志表或复用现有操作日志体系,记录:

coupon_user_id uid cid old_status new_status reason operator_type add_time

Service 示例:

/** * 标记用户券过期并记录原因 * @param array $ids 用户券ID * @param string $reason 过期原因 * @return bool */publicfunctionmarkCouponExpired(array$ids,string$reason='自然过期'):bool{return$this->transaction(function()use($ids,$reason){$res=$this->dao->markExpiredByIds($ids);// 如果项目已有日志服务,建议在这里批量写入日志。// 注意日志写入也要走对应 Services/Dao,不要在这里直接拼 SQL。return$res;});}

日志不是为了“好看”,而是为了回答售后经常问的三个问题:

这张券为什么不能用了? 是自己过期,还是退款导致失效? 什么时候被系统处理的?

9. 关键目录说明

app/model/activity/coupon/StoreCouponUser.php 用户券模型,包含状态和获取方式映射。 app/services/activity/coupon/StoreCouponUserServices.php 用户券过期检查、使用、回退、可用券筛选。 app/dao/activity/coupon/StoreCouponUserDao.php 用户券查询和状态更新,适合放批量过期查询。 app/services/order/StoreOrderRefundServices.php 退款时回退积分和优惠券。 app/jobs/product/ProductCouponJob.php 商品关联优惠券赠送、退款失效任务。 app/services/product/product/StoreProductCouponServices.php 下单赠券、订单失败后赠券失效。

10. 二开注意事项

  1. 全量过期任务只处理status = 0的用户券,不要覆盖已使用券。
  2. 用户券过期和发布券过期是两件事,分别对应不同表和不同业务含义。
  3. 退款回退优惠券时,先恢复“未使用”,后续再由过期检查处理是否已经到期。
  4. 下单赠券失效要按订单、用户、来源类型筛选,避免误伤用户自己领取的券。
  5. Job 里不要直接查库,批量查询和更新放 Dao,业务编排放 Services。
  6. 如果后续要做售后排查,建议记录过期原因或处理日志。

标签建议

CRMEB Pro 优惠券过期 定时任务 订单退款 二次开发 ThinkPHP 源码解析
http://www.jsqmd.com/news/1017326/

相关文章:

  • 徐州市天加中央空调维修师傅电话|各区金牌师傅,靠谱选欧米到家 - 欧米到家
  • 苏州晟雅泰电子:W25Q32JVSSIQ参数,规格及应用领域
  • 武汉装修设计施工一体化公司排名|2026 设计还原度最高的五大品牌 - 资讯纵览
  • 告别公式乱码!DeepSeek公式导出Word三步搞定 插件版零配置
  • 抖音无水印下载神器:2026年最全使用指南,批量下载创作者所有作品
  • 2026 学生免费网盘怎么选?别只看容量,学习资料同步与数字资产保护才是底层标准
  • NXP WCT1011B DAC配置实战:从5位VREF到12位通用DAC详解
  • 如何快速掌握ggplot2:R数据可视化终极教程
  • 人形机器人平衡控制:ZMP、MPC、WBC 实战详解
  • 10分钟掌握Kotlin Multiplatform跨平台开发:Fruitties实战教程
  • 华为昇腾让DeepSeek推理降价75%,Coding Agent军备赛也开打了
  • 石家庄汽车音响店亲测:2026年5月案例分享首推石家庄大苹果汽车音响 - 资讯纵览
  • 大连翡翠回收实测测评!2026高价变现靠谱渠道盘点 - 薛定谔的梨花猫
  • 为什么你的小程序图片裁剪功能需要we-cropper这个终极方案?
  • 2026 成都持证黄金回收门店汇总,仪器鉴定当场结算安心变现 - 奢侈品回收评测
  • 终极资源聚合方案:一站式搜索上百个平台的完整指南
  • 终极免费方案:OBS多平台同步直播插件完整指南
  • Mythos门控架构:大模型能力与策略解耦的工程实践
  • garde未来路线图:即将发布的5个令人期待的新功能
  • 靠谱的openclaw哪个最强
  • Visual C++运行库修复工具:5分钟快速解决Windows软件启动错误的完整方案
  • 深入解析FlexRay控制器:从协议原理到PXS20实战配置
  • 2026 佛山黄金回收实力榜单,全套设备持证回收,稳妥盘活黄金资产 - 奢侈品回收测评
  • 重庆二手钻石回收口碑榜,正规实体店权威排序 - 讯息早知道
  • FlexRay控制器内存错误注入与协议状态管理深度解析
  • Chatwoot 实测:免费部署一套全渠道客服系统,替代 Intercom 每年省几万
  • 【本地 AI 智能体】 OpenClaw 零基础 Windows 安装配置全流程(包含安装包)
  • 嵌入式实时调试:ColdFire2/2M硬件断点与调试中断实战解析
  • DeepLab_v3常见问题完全指南:训练不收敛、内存不足、精度低的终极解决方案
  • ImageGlass图像浏览器:支持90+格式的现代开源解决方案