diff --git a/backend/app/api/controller/v1/order/CenterOrderController.php b/backend/app/api/controller/v1/order/CenterOrderController.php index a6565b723..7a8bf2582 100644 --- a/backend/app/api/controller/v1/order/CenterOrderController.php +++ b/backend/app/api/controller/v1/order/CenterOrderController.php @@ -2101,6 +2101,8 @@ class CenterOrderController 'store_name' => $item['productInfo']['store_name'] ?? '', 'price' => $item['productInfo']['price'] ?? '0.00', 'true_price' => $item['truePrice'] ?? '0.00', + // 商品级购买送积分快照(eb_product_center.give_integral),收货时按数量聚合发放 + 'give_integral' => (float)($item['productInfo']['give_integral'] ?? 0), 'cart_num' => $item['cart_num'] ?? 1, 'product_type' => $item['productInfo']['product_type'] ?? '', 'attr_info' => $item['productInfo']['attrInfo'] ?? [], diff --git a/backend/app/dao/user/UserBillDao.php b/backend/app/dao/user/UserBillDao.php index db66ab2ba..b76dddfd6 100644 --- a/backend/app/dao/user/UserBillDao.php +++ b/backend/app/dao/user/UserBillDao.php @@ -32,7 +32,7 @@ class UserBillDao extends BaseDao { return $this->getModel()->where('link_id', $orderId)->where('uid', $uid) ->where('category', 'integral')->where('status', 1) - ->whereIn('type', ['gain', 'product_gain', 'order_give_integral', 'pay_give_integral', 'order_deduction', 'integral_refund']) + ->whereIn('type', ['gain', 'product_gain', 'order_give_integral', 'pay_give_integral', 'order_deduction', 'integral_refund', 'order_gain_refund', 'product_gain_refund']) ->field('type, pm, SUM(number) AS number')->group('type, pm')->select()->toArray(); } diff --git a/backend/app/services/order/OrderRefundRewardServices.php b/backend/app/services/order/OrderRefundRewardServices.php index 6b62fd445..751e3cb97 100644 --- a/backend/app/services/order/OrderRefundRewardServices.php +++ b/backend/app/services/order/OrderRefundRewardServices.php @@ -68,10 +68,16 @@ class OrderRefundRewardServices extends BaseService if (!$user) { throw new \RuntimeException('退款权益关联用户不存在'); } - $earned = $returned = '0'; + $earned = $returned = $earnedOrder = $earnedProduct = '0'; foreach ($this->billDao->getOrderIntegralRewards((int)$order['id'], $uid) as $bill) { if ((int)$bill['pm'] === 1) { $earned = bcadd($earned, (string)$bill['number'], 0); + // 下单赠送族 gain/order_give_integral,购买赠送族 product_gain/pay_give_integral + if (in_array($bill['type'], ['gain', 'order_give_integral'], true)) { + $earnedOrder = bcadd($earnedOrder, (string)$bill['number'], 0); + } else { + $earnedProduct = bcadd($earnedProduct, (string)$bill['number'], 0); + } } else { $returned = bcadd($returned, (string)$bill['number'], 0); } @@ -79,9 +85,22 @@ class OrderRefundRewardServices extends BaseService $integralTarget = $this->target($earned, $total, $paid, 0); $processed = max((int)$returned, (int)($accounted['integral'] ?? 0)); $due = max(0, (int)bcsub($integralTarget, (string)$processed, 0)); - $deduct = min($due, max(0, (int)$user['integral'])); - if ($deduct > 0 && !$this->app->make(UserBillServices::class)->billIntegralIncome( - 'integral_refund', $uid, $deduct, (int)$order['id'], '订单退款回收赠积分:' . $order['order_no'] + // 按两族赠送占比拆分本次冲正额,分族落库(order_gain_refund / product_gain_refund) + $dueOrder = $earned > 0 ? (int)bcdiv(bcmul((string)$due, $earnedOrder, 2), $earned, 0) : 0; + $dueProduct = max(0, $due - $dueOrder); + $balance = max(0, (int)$user['integral']); + $deductOrder = min($dueOrder, $balance); + $balance -= $deductOrder; + $deductProduct = min($dueProduct, $balance); + $deduct = $deductOrder + $deductProduct; + $userBillServices = $this->app->make(UserBillServices::class); + if ($deductOrder > 0 && !$userBillServices->billIntegralIncome( + 'order_gain_refund', $uid, $deductOrder, (int)$order['id'], '订单退款回收下单赠送积分:' . $order['order_no'] + )) { + throw new \RuntimeException('退款赠积分冲正失败'); + } + if ($deductProduct > 0 && !$userBillServices->billIntegralIncome( + 'product_gain_refund', $uid, $deductProduct, (int)$order['id'], '订单退款回收购买商品赠送积分:' . $order['order_no'] )) { throw new \RuntimeException('退款赠积分冲正失败'); } diff --git a/backend/app/services/order/OrderTakeServices.php b/backend/app/services/order/OrderTakeServices.php index c41e6293c..145fa58ca 100644 --- a/backend/app/services/order/OrderTakeServices.php +++ b/backend/app/services/order/OrderTakeServices.php @@ -13,6 +13,7 @@ namespace app\services\order; use app\dao\order\OrderCenterDao; +use app\dao\product\CenterProductDao; use crmeb\basic\BaseService; use app\services\user\member\MemberCardServices; use app\services\user\UserBillServices; @@ -266,10 +267,39 @@ class OrderTakeServices extends BaseService } /** @var UserBillServices $userBillServices */ $userBillServices = app()->make(UserBillServices::class); - // 检查是否有赠送积分可送(中台订单无 gain_integral 实体列,缺省 0——商品级赠送快照聚合待后续补全) - if ((int)($order['gain_integral'] ?? 0) > 0) { + // 购买商品送积分:中台订单无 gain_integral 实体列,从 extra_info.items 的商品级 + // give_integral 快照按数量聚合(下单时由 buildOrderItemsForExtra 写入) + $extraInfo = $order['extra_info'] ?? []; + if (is_string($extraInfo)) { + $extraInfo = json_decode($extraInfo, true) ?: []; + } + $items = $extraInfo['items'] ?? []; + // 修复前下的旧订单快照无 give_integral 字段,回查中台商品当前配置兜底 + $missingIds = []; + foreach ($items as $item) { + if (!array_key_exists('give_integral', $item) && (int)($item['product_id'] ?? 0) > 0) { + $missingIds[$item['product_id']] = true; + } + } + $productGiveMap = []; + if ($missingIds) { + /** @var CenterProductDao $centerProductDao */ + $centerProductDao = app()->make(CenterProductDao::class); + $productGiveMap = $centerProductDao->getColumn([['id', 'in', array_keys($missingIds)]], 'give_integral', 'id') ?: []; + } + $gainIntegral = '0'; + foreach ($items as $item) { + $give = array_key_exists('give_integral', $item) + ? (float)($item['give_integral'] ?? 0) + : (float)($productGiveMap[$item['product_id'] ?? 0] ?? 0); + if ($give > 0) { + $num = max(1, (int)($item['cart_num'] ?? 1)); + $gainIntegral = bcadd($gainIntegral, bcmul((string)$give, (string)$num, 0), 0); + } + } + if ((float)$gainIntegral > 0) { // 购买商品送积分 - $res2 = $userBillServices->billIntegralIncome('pay_give_integral', $order['uid'], $order['gain_integral'], $order['id']); + $res2 = $userBillServices->billIntegralIncome('pay_give_integral', $order['uid'], $gainIntegral, $order['id']); } else { $res2 = true; } @@ -293,7 +323,7 @@ class OrderTakeServices extends BaseService } else { $res3 = true; } - $give_integral = $order_integral + (int)($order['gain_integral'] ?? 0); + $give_integral = (int)$order_integral + (int)$gainIntegral; if ($give_integral > 0 && $res2 && $res3) { //触发积分变动事件(消息发送已移至 UserIntegralChangeListener;中台无 gain_integral 列,累计值以 bill 流水为准不再回写) event('UserIntegralChange', [(int)$order['uid'], $order, $storeTitle, $give_integral, $order_integral]); diff --git a/backend/app/services/order/RefundExecutorServices.php b/backend/app/services/order/RefundExecutorServices.php index f54e6cdeb..c37986ea5 100644 --- a/backend/app/services/order/RefundExecutorServices.php +++ b/backend/app/services/order/RefundExecutorServices.php @@ -137,35 +137,61 @@ class RefundExecutorServices extends BaseService /** @var UserBillServices $userBillServices */ $userBillServices = app()->make(UserBillServices::class); - // 操作1:收回订单赠送积分(下单赠送 gain + 商品赠送 product_gain,integral_refund 已回退过则幂等跳过) + // 操作1:分族收回订单赠送积分(下单赠送族 gain/order_give_integral、购买赠送族 product_gain/pay_give_integral) + // 发放链路经 billIntegralIncome 落库类型为 order_give_integral / pay_give_integral,须按实际落库类型统计 $orderGain = (int)$userBillServices->sum([ + 'category' => 'integral', + 'type' => 'order_give_integral', + 'link_id' => $orderId, + 'uid' => $uid, + ], 'number') + (int)$userBillServices->sum([ 'category' => 'integral', 'type' => 'gain', 'link_id' => $orderId, 'uid' => $uid, ], 'number'); $productGain = (int)$userBillServices->sum([ + 'category' => 'integral', + 'type' => 'pay_give_integral', + 'link_id' => $orderId, + 'uid' => $uid, + ], 'number') + (int)$userBillServices->sum([ 'category' => 'integral', 'type' => 'product_gain', 'link_id' => $orderId, 'uid' => $uid, ], 'number'); - $giveIntegral = $orderGain + $productGain; - if ($giveIntegral > 0) { - // 幂等标记查实际落库 type:模板 'integral_refund' 落库 type 为 order_deduction - // (type=integral_refund 是模板 'order_integral_refund' 返还使用积分的流水,语义不同) - $count = (int)$userBillServices->count([ + if ($orderGain > 0 || $productGain > 0) { + // 幂等标记按分族落库类型分别查重 + $orderRefunded = (int)$userBillServices->count([ 'category' => 'integral', - 'type' => 'order_deduction', + 'type' => 'order_gain_refund', 'link_id' => $orderId, ]); - if (!$count) { - // 当前积分不足时扣减全部,差额不追回 - $deductAmount = min($currentIntegral, $giveIntegral); + $productRefunded = (int)$userBillServices->count([ + 'category' => 'integral', + 'type' => 'product_gain_refund', + 'link_id' => $orderId, + ]); + $hasRefund = false; + // 顺序扣减:当前积分不足时扣减全部,差额不追回(总量与原合并扣减口径一致) + if (!$orderRefunded && $orderGain > 0) { + $deductAmount = min($currentIntegral, $orderGain); if ($deductAmount > 0) { - $userBillServices->billIntegralIncome('integral_refund', $uid, $deductAmount, $orderId); - $currentIntegral = bcsub((string)$currentIntegral, (string)$deductAmount, 2); + $userBillServices->billIntegralIncome('order_gain_refund', $uid, $deductAmount, $orderId); + $currentIntegral = (int)bcsub((string)$currentIntegral, (string)$deductAmount, 0); + $hasRefund = true; } + } + if (!$productRefunded && $productGain > 0) { + $deductAmount = min($currentIntegral, $productGain); + if ($deductAmount > 0) { + $userBillServices->billIntegralIncome('product_gain_refund', $uid, $deductAmount, $orderId); + $currentIntegral = (int)bcsub((string)$currentIntegral, (string)$deductAmount, 0); + $hasRefund = true; + } + } + if ($hasRefund) { // 清除积分冻结 $userBillServices->update(['link_id' => $orderId], ['frozen_time' => 0]); } diff --git a/backend/app/services/user/UserBillServices.php b/backend/app/services/user/UserBillServices.php index 002c63648..333cf7c84 100644 --- a/backend/app/services/user/UserBillServices.php +++ b/backend/app/services/user/UserBillServices.php @@ -97,6 +97,22 @@ class UserBillServices extends BaseService 'status' => 1, 'pm' => 1 ], + 'order_gain_refund' => [ + 'title' => '退款返还下单赠送积分', + 'category' => self::TYPE_INTEGRAL, + 'type' => 'order_gain_refund', + 'mark' => '订单退款,返还下单赠送积分{%num%}', + 'status' => 1, + 'pm' => 0 + ], + 'product_gain_refund' => [ + 'title' => '退款返还购买商品赠送积分', + 'category' => self::TYPE_INTEGRAL, + 'type' => 'product_gain_refund', + 'mark' => '订单退款,返还购买商品赠送积分{%num%}', + 'status' => 1, + 'pm' => 0 + ], 'pay_product_integral_back' => [ 'title' => '商品退积分', 'category' => self::TYPE_INTEGRAL,