mirror of
https://github.com/crmeb/CRMEB.git
synced 2026-10-28 13:31:04 +00:00
fix(优惠券): 确认页自动领取最优券并统一券列表口径
- confirm/cartConfirm 共用 buildConfirmCouponList,无可用券时自动领取整单最优券 - computed 重算补齐已选券,避免入口显示已用券但弹窗列表为空 - 券查询时间边界改为 start_time <= now,覆盖领取当秒的自动领券 - 同步用户端下单优惠券文档
This commit is contained in:
parent
d5e2f6066a
commit
581fe81df7
@ -233,40 +233,8 @@ class CenterOrderController
|
||||
// 活动订单不走优惠券自动匹配,标记前置(优惠券列表构建时使用)
|
||||
$isActivityOrder = $activityType !== 'normal' && $activityId > 0;
|
||||
|
||||
// 5. 获取用户可用优惠券
|
||||
$couponList = [];
|
||||
try {
|
||||
/** @var StoreCouponUserServices $couponServices */
|
||||
$couponServices = app()->make(StoreCouponUserServices::class);
|
||||
$cartGroup = ['valid' => $cartInfo];
|
||||
$couponList = $couponServices->getUsableCouponList($uid, $cartGroup);
|
||||
} catch (\Throwable $e) {
|
||||
// 优惠券列表获取失败不影响主流程
|
||||
}
|
||||
// 5.1 用户尚无可用券时,自动领取整单最优优惠券并入列表(预览即真实领取,与 computed/create 自动用券同口径)
|
||||
if (!$isActivityOrder && empty($couponList)) {
|
||||
try {
|
||||
$autoCoupon = app()->make(OrderComputedServices::class)->autoMatchCoupon($uid, $cartInfo, true);
|
||||
if (!empty($autoCoupon['coupon_user_id'])) {
|
||||
$autoRow = $couponServices->get((int)$autoCoupon['coupon_user_id'], ['*'], ['issue']);
|
||||
if ($autoRow) {
|
||||
$autoRow = $autoRow->toArray();
|
||||
// 行结构与 getUsableCouponList 对齐(前端优惠券选择器字段)
|
||||
$autoRow['title'] = $autoRow['coupon_title'];
|
||||
$autoRow['type'] = $autoRow['applicable_type'] ?? 0;
|
||||
$autoRow['use_min_price'] = floatval($autoRow['use_min_price'] ?? 0);
|
||||
$autoRow['coupon_price'] = floatval($autoRow['coupon_price'] ?? 0);
|
||||
$autoRow['start_time'] = $autoRow['start_time'] ? date('Y/m/d', $autoRow['start_time']) : date('Y/m/d', $autoRow['add_time']);
|
||||
$autoRow['add_time'] = date('Y/m/d', (int)$autoRow['add_time']);
|
||||
$autoRow['end_time'] = date('Y/m/d', (int)$autoRow['end_time']);
|
||||
$autoRow['is_use'] = 0;
|
||||
$couponList[] = $autoRow;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 自动领券失败不影响确认页主流程
|
||||
}
|
||||
}
|
||||
// 5. 获取用户可用优惠券(无可用券时自动领取整单最优券并入列表,与 computed/create 自动用券同口径)
|
||||
$couponList = $this->buildConfirmCouponList($uid, ['valid' => $cartInfo], $isActivityOrder, $pricingPolicy);
|
||||
|
||||
// 6. 获取商品详情扩展数据(自定义表单、商品参数)
|
||||
$detailExtend = $handler->getDetailExtend($productId);
|
||||
@ -492,13 +460,13 @@ class CenterOrderController
|
||||
$integralRatio = sys_config('integral_ratio', 0);
|
||||
$integralOpen = (float)$integralRatio > 0 || $usableIntegral > 0;
|
||||
|
||||
// 可用优惠券
|
||||
$couponList = [];
|
||||
try {
|
||||
$couponList = app()->make(StoreCouponUserServices::class)->getUsableCouponList($uid, $cartGroup);
|
||||
} catch (\Throwable $e) {
|
||||
// 优惠券列表获取失败不影响主流程
|
||||
}
|
||||
// 可用优惠券(无可用券时自动领取整单最优券并入列表,保证与 computed 的自动选券结果一致)
|
||||
$couponList = $this->buildConfirmCouponList(
|
||||
$uid,
|
||||
$cartGroup,
|
||||
false,
|
||||
method_exists($handler, 'getPricingPolicy') ? $handler->getPricingPolicy() : []
|
||||
);
|
||||
|
||||
// 支付渠道状态
|
||||
/** @var PayChannelServices $payChannelServices */
|
||||
@ -647,6 +615,49 @@ class CenterOrderController
|
||||
return app('json')->success($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建确认页可用优惠券列表
|
||||
* 用户已有可用券时直接返回;没有可用券时自动领取整单最优券并补入列表(预览即真实领取)。
|
||||
* 与 computed/create 的自动用券同口径,保证 features.coupon、coupon_list 与后续自动选券结果一致,
|
||||
* 避免确认页显示已用券但优惠券弹窗列表为空。
|
||||
* @param int $uid 用户ID
|
||||
* @param array $cartGroup 购物车分组(须含 valid 商品条目)
|
||||
* @param bool $isActivityOrder 活动订单不参与优惠券
|
||||
* @param array $pricingPolicy 产品类型计价策略,coupon=false 时不参与优惠券
|
||||
* @return array
|
||||
*/
|
||||
protected function buildConfirmCouponList(int $uid, array $cartGroup, bool $isActivityOrder, array $pricingPolicy = []): array
|
||||
{
|
||||
if ($isActivityOrder || ($pricingPolicy['coupon'] ?? true) !== true) {
|
||||
return [];
|
||||
}
|
||||
/** @var StoreCouponUserServices $couponServices */
|
||||
$couponServices = app()->make(StoreCouponUserServices::class);
|
||||
$couponList = [];
|
||||
try {
|
||||
$couponList = $couponServices->getUsableCouponList($uid, $cartGroup);
|
||||
} catch (\Throwable $e) {
|
||||
// 优惠券列表获取失败不影响确认页主流程
|
||||
$couponList = [];
|
||||
}
|
||||
if (!empty($couponList)) {
|
||||
return $couponList;
|
||||
}
|
||||
// 用户尚无可用券:自动领取整单最优优惠券,行结构与 getUsableCouponList 保持一致
|
||||
try {
|
||||
/** @var OrderComputedServices $computedServices */
|
||||
$computedServices = app()->make(OrderComputedServices::class);
|
||||
$autoCoupon = $computedServices->autoMatchCoupon($uid, $cartGroup['valid'] ?? [], true);
|
||||
$autoRow = $couponServices->getUsableCouponRow($uid, (int)($autoCoupon['coupon_user_id'] ?? 0));
|
||||
if ($autoRow) {
|
||||
$couponList[] = $autoRow;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 自动领券失败不影响确认页主流程
|
||||
}
|
||||
return $couponList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算订单金额(切换优惠券/积分时重算)
|
||||
*/
|
||||
@ -901,16 +912,28 @@ class CenterOrderController
|
||||
$result['coupon_id'] = $couponId;
|
||||
$result['coupon_title'] = $couponTitle;
|
||||
|
||||
// 自动领取发生在确认接口返回之后时,重新查询可用券并返回给前端。
|
||||
// 否则金额和标题已经更新,但优惠券选择弹窗仍使用确认阶段的旧列表。
|
||||
// 自动领取发生在确认接口返回之后,或被自动选中并用于计价的券不在列表查询窗口内时,
|
||||
// 重新查询可用券并返回给前端,按已选中的券补齐,保证金额、入口文案与弹窗列表口径一致。
|
||||
$result['coupon_list'] = [];
|
||||
if (!$isActivityOrder && ($pricingPolicy['coupon'] ?? true)) {
|
||||
/** @var StoreCouponUserServices $couponServices */
|
||||
$couponServices = app()->make(StoreCouponUserServices::class);
|
||||
try {
|
||||
/** @var StoreCouponUserServices $couponServices */
|
||||
$couponServices = app()->make(StoreCouponUserServices::class);
|
||||
$result['coupon_list'] = $couponServices->getUsableCouponList($uid, ['valid' => $cartInfo]);
|
||||
} catch (\Throwable $e) {
|
||||
// 优惠券列表刷新失败不影响金额计算和下单流程
|
||||
$result['coupon_list'] = [];
|
||||
}
|
||||
$usableCouponIds = array_map('intval', array_column($result['coupon_list'], 'id'));
|
||||
if ($couponId > 0 && !in_array($couponId, $usableCouponIds, true)) {
|
||||
try {
|
||||
$usedCouponRow = $couponServices->getUsableCouponRow($uid, $couponId);
|
||||
if ($usedCouponRow) {
|
||||
$result['coupon_list'][] = $usedCouponRow;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 已选券回显失败不影响金额计算
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -132,8 +132,12 @@ class StoreCouponUserDao extends BaseDao
|
||||
*/
|
||||
public function getUserAllCoupon(int $uid)
|
||||
{
|
||||
// start_time 用 <=:领取后 N 天可用的券在领取当秒 start_time 就等于 time(),
|
||||
// 用 < 会把“确认页刚自动领取并已用于计价”的券漏查,导致优惠券弹窗列表为空。
|
||||
// 该口径与 OrderComputedServices::autoMatchCoupon 的候选券查询保持一致。
|
||||
$now = time();
|
||||
return $this->getModel()->where(['uid' => $uid, 'status' => 0, 'is_fail' => 0])
|
||||
->where('start_time', '<', time())->where('end_time', '>', time())
|
||||
->where('start_time', '<=', $now)->where('end_time', '>', $now)
|
||||
->order('coupon_price DESC')->with('issue')->select()->hidden(['status', 'is_fail'])->toArray();
|
||||
}
|
||||
|
||||
|
||||
@ -172,22 +172,60 @@ class StoreCouponUserServices extends BaseService
|
||||
break;
|
||||
}
|
||||
if ($count && $coupon['use_min_price'] <= $price) {
|
||||
$coupon['start_time'] = $coupon['start_time'] ? date('Y/m/d', $coupon['start_time']) : date('Y/m/d', $coupon['add_time']);
|
||||
$coupon['add_time'] = date('Y/m/d', $coupon['add_time']);
|
||||
$coupon['end_time'] = date('Y/m/d', $coupon['end_time']);
|
||||
$coupon['start_use_time'] = $coupon['start_use_time'] > 0 ? date('Y/m/d', $coupon['start_use_time']) : 0;
|
||||
$coupon['end_use_time'] = $coupon['start_use_time'] > 0 ? date('Y/m/d', $coupon['end_use_time']) : 0;
|
||||
$coupon['title'] = $coupon['coupon_title'];
|
||||
$coupon['type'] = $coupon['applicable_type'];
|
||||
$coupon['use_min_price'] = floatval($coupon['use_min_price']);
|
||||
$coupon['coupon_price'] = floatval($coupon['coupon_price']);
|
||||
$result[] = $coupon;
|
||||
$result[] = $this->formatUsableCouponRow($coupon);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化用户优惠券为下单页可选券行(前端优惠券选择器字段契约)
|
||||
* @param array $coupon 用户券行,须已绑定 issue 的 applicable_type/start_use_time/end_use_time 等字段
|
||||
* @return array
|
||||
*/
|
||||
public function formatUsableCouponRow(array $coupon): array
|
||||
{
|
||||
$startUseTime = (int)($coupon['start_use_time'] ?? 0);
|
||||
$endUseTime = (int)($coupon['end_use_time'] ?? 0);
|
||||
$coupon['start_time'] = $coupon['start_time'] ? date('Y/m/d', (int)$coupon['start_time']) : date('Y/m/d', (int)$coupon['add_time']);
|
||||
$coupon['add_time'] = date('Y/m/d', (int)$coupon['add_time']);
|
||||
$coupon['end_time'] = date('Y/m/d', (int)$coupon['end_time']);
|
||||
$coupon['start_use_time'] = $startUseTime > 0 ? date('Y/m/d', $startUseTime) : 0;
|
||||
$coupon['end_use_time'] = $startUseTime > 0 ? date('Y/m/d', $endUseTime) : 0;
|
||||
$coupon['title'] = $coupon['coupon_title'];
|
||||
$coupon['type'] = $coupon['applicable_type'] ?? 0;
|
||||
$coupon['use_min_price'] = floatval($coupon['use_min_price']);
|
||||
$coupon['coupon_price'] = floatval($coupon['coupon_price']);
|
||||
return $coupon;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定用户券的下单页券行(自动领取后回显/补齐弹窗列表用)
|
||||
* @param int $uid 用户ID
|
||||
* @param int $couponUserId 用户券记录ID(eb_store_coupon_user.id)
|
||||
* @return array 券不属于当前用户或不存在时返回空数组
|
||||
*/
|
||||
public function getUsableCouponRow(int $uid, int $couponUserId): array
|
||||
{
|
||||
if ($couponUserId <= 0) {
|
||||
return [];
|
||||
}
|
||||
$coupon = $this->dao->get($couponUserId, ['*'], ['issue']);
|
||||
if (!$coupon) {
|
||||
return [];
|
||||
}
|
||||
if (is_object($coupon)) {
|
||||
$coupon = $coupon->toArray();
|
||||
}
|
||||
// 仅回显当前用户未使用的券,避免越权读取他人券
|
||||
if ((int)($coupon['uid'] ?? 0) !== $uid || (int)($coupon['status'] ?? 0) !== 0) {
|
||||
return [];
|
||||
}
|
||||
unset($coupon['status'], $coupon['is_fail']);
|
||||
return $this->formatUsableCouponRow($coupon);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下单页面显示可用优惠券
|
||||
* @param $uid
|
||||
|
||||
@ -593,6 +593,19 @@ function ChangCoupons(e) {
|
||||
computedPrice();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断优惠券列表是否已包含指定用户券
|
||||
* @param {Array} list 优惠券列表
|
||||
* @param {Number|String} id 用户券ID
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function hasCouponRow(list, id) {
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
if (list[i].id == id) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算价格(调用computed接口)
|
||||
*/
|
||||
@ -644,8 +657,19 @@ async function computedPrice() {
|
||||
// 自动领券发生在确认接口之后,计算接口返回的列表才是最新可用券列表。
|
||||
// 同步列表和选中状态,避免入口显示优惠券但弹窗仍显示为空。
|
||||
if (Array.isArray(data.coupon_list)) {
|
||||
coupon.value.list = data.coupon_list;
|
||||
features.value.coupon = data.coupon_list.length > 0;
|
||||
var nextCouponList = data.coupon_list;
|
||||
// 兜底:已使用的券不在最新列表中时保留原行(如自动领券与列表查询落在同一秒),
|
||||
// 否则入口显示已用券、价格已抵扣,弹窗却渲染成空优惠券。
|
||||
if (couponId.value > 0 && !hasCouponRow(nextCouponList, couponId.value)) {
|
||||
for (var usedIndex = 0; usedIndex < coupon.value.list.length; usedIndex++) {
|
||||
if (coupon.value.list[usedIndex].id == couponId.value) {
|
||||
nextCouponList = nextCouponList.concat([coupon.value.list[usedIndex]]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
coupon.value.list = nextCouponList;
|
||||
features.value.coupon = nextCouponList.length > 0 || couponId.value > 0;
|
||||
for (var couponIndex = 0; couponIndex < coupon.value.list.length; couponIndex++) {
|
||||
coupon.value.list[couponIndex].is_use = coupon.value.list[couponIndex].id == couponId.value ? 1 : 0;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user