优化复购功能:指定商品复购付款时直接立减(不返现),解耦分销买家佣金,纯后端实现
这个提交包含在:
@@ -128,11 +128,8 @@ class Commission
|
||||
|
||||
|
||||
|
||||
$hasRepurchaseSelf = $this->isRepurchase()
|
||||
&& $this->_config->isRepurchaseSelfEnable()
|
||||
&& $this->goods->isRepurchaseEnable();
|
||||
|
||||
|
||||
// 复购已在下单付款时直接减免,分销佣金奖池中不再发放买家本人复购返现
|
||||
$hasRepurchaseSelf = false;
|
||||
|
||||
if (!$hasChainCommission && !$hasTierCommission && !$hasRepurchaseSelf) {
|
||||
|
||||
@@ -677,135 +674,29 @@ class Commission
|
||||
|
||||
|
||||
protected function runRepurchaseRewards($commissionOrder)
|
||||
|
||||
{
|
||||
|
||||
if (!$this->isRepurchase() || !$this->_config->isRepurchaseSelfEnable() || !$this->goods->isRepurchaseEnable()) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
$commission = 0;
|
||||
|
||||
$rule = ['award_type' => 'repurchase_self'];
|
||||
|
||||
if ($this->_config->getRepurchaseSelfType() === 'fixed') {
|
||||
|
||||
$commission = round($this->_config->getRepurchaseSelfAmount(), 2);
|
||||
|
||||
$rule['type'] = 'fixed';
|
||||
|
||||
$rule['amount'] = $commission;
|
||||
|
||||
} else {
|
||||
|
||||
$rate = $this->_config->getRepurchaseSelfRate();
|
||||
|
||||
if ($rate <= 0) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
$commission = round($this->amount * $rate * 0.01, 2);
|
||||
|
||||
$rule['type'] = 'rate';
|
||||
|
||||
$rule['rate'] = $rate;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
if ($commission <= 0) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
$rewards = RewardModel::where('commission_order_id', $commissionOrder->id)
|
||||
|
||||
->where('agent_id', $this->buyer->user->id)
|
||||
|
||||
->where('commission_level', 0)
|
||||
|
||||
->select();
|
||||
|
||||
foreach ($rewards as $reward) {
|
||||
|
||||
$rewardRules = json_decode($reward->commission_rules, true);
|
||||
|
||||
if (($rewardRules['award_type'] ?? '') === 'repurchase_self') {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
$reward = RewardModel::create([
|
||||
|
||||
'order_id' => $commissionOrder->order_id,
|
||||
|
||||
'order_item_id' => $commissionOrder->order_item_id,
|
||||
|
||||
'buyer_id' => $commissionOrder->buyer_id,
|
||||
|
||||
'commission_order_id' => $commissionOrder->id,
|
||||
|
||||
'agent_id' => $this->buyer->user->id,
|
||||
|
||||
'type' => 'money',
|
||||
|
||||
'commission' => $commission,
|
||||
|
||||
'status' => 0,
|
||||
|
||||
'original_commission' => $commission,
|
||||
|
||||
'commission_level' => 0,
|
||||
|
||||
'commission_rules' => json_encode($rule),
|
||||
|
||||
'agent_level' => 0,
|
||||
|
||||
]);
|
||||
|
||||
|
||||
|
||||
Log::write($this->buyer->user->id, 'reward', [
|
||||
|
||||
'action' => 'repurchase_self',
|
||||
|
||||
'reward' => $reward,
|
||||
|
||||
], ['oper_type' => 'user', 'oper_id' => $commissionOrder->buyer_id]);
|
||||
|
||||
// 复购优惠已在商品下单付款时直接立减,此处不再发放买家本人复购返现佣金
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected function isRepurchase()
|
||||
|
||||
{
|
||||
$goodsId = $this->item['goods_id'] ?? 0;
|
||||
$userId = $this->buyer->user->id ?? 0;
|
||||
if (!$goodsId || !$userId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$count = \addons\shopro\model\Order::where('user_id', $this->buyer->user->id)
|
||||
|
||||
->where('status', 'in', [1, 2])
|
||||
|
||||
->where('id', '<', $this->item['order_id'])
|
||||
|
||||
$count = \addons\shopro\model\OrderItem::alias('oi')
|
||||
->join('shopro_order o', 'o.id = oi.order_id')
|
||||
->where('o.user_id', $userId)
|
||||
->where('o.status', 'in', [1, 2])
|
||||
->where('oi.goods_id', $goodsId)
|
||||
->where('o.id', '<', $this->item['order_id'])
|
||||
->where('oi.refund_status', '<=', 0)
|
||||
->count();
|
||||
|
||||
return $count > 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\commission;
|
||||
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 复购立减服务类
|
||||
* 针对指定商品(开启复购规则)且买家历史买过的商品,在付款时直接减免
|
||||
*/
|
||||
class RepurchaseService
|
||||
{
|
||||
/**
|
||||
* 获取全局复购立减配置
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getConfig()
|
||||
{
|
||||
$config = [];
|
||||
try {
|
||||
$configModel = new Config();
|
||||
$config = [
|
||||
'enable' => $configModel->isRepurchaseEnable(),
|
||||
'self_enable' => $configModel->isRepurchaseSelfEnable(),
|
||||
'self_type' => $configModel->getRepurchaseSelfType(),
|
||||
'self_amount' => $configModel->getRepurchaseSelfAmount(),
|
||||
'self_rate' => $configModel->getRepurchaseSelfRate(),
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
$config = [
|
||||
'enable' => false,
|
||||
'self_enable' => false,
|
||||
'self_type' => 'fixed',
|
||||
'self_amount' => 0.0,
|
||||
'self_rate' => 0.0,
|
||||
];
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查商品是否开启复购规则
|
||||
*
|
||||
* @param int $goodsId
|
||||
* @return bool
|
||||
*/
|
||||
public static function isGoodsRepurchaseEnable($goodsId)
|
||||
{
|
||||
if (!$goodsId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$row = Db::name('shopro_commission_goods')
|
||||
->where('goods_id', $goodsId)
|
||||
->where('status', 1)
|
||||
->field('repurchase_enable')
|
||||
->find();
|
||||
return !empty($row['repurchase_enable']);
|
||||
} catch (\Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否曾购买过该指定商品(有效付款订单)
|
||||
*
|
||||
* @param int $userId
|
||||
* @param int $goodsId
|
||||
* @return bool
|
||||
*/
|
||||
public static function isUserRepurchasedGoods($userId, $goodsId)
|
||||
{
|
||||
if (!$userId || !$goodsId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$count = Db::name('shopro_order_item')
|
||||
->alias('oi')
|
||||
->join('shopro_order o', 'o.id = oi.order_id')
|
||||
->where('o.user_id', $userId)
|
||||
->where('o.status', 'in', [1, 2])
|
||||
->where('oi.goods_id', $goodsId)
|
||||
->where('oi.refund_status', '<=', 0)
|
||||
->count();
|
||||
|
||||
return $count > 0;
|
||||
} catch (\Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算单项商品的复购立减金额
|
||||
*
|
||||
* @param array $item 商品信息 (goods_id, goods_num, goods_price, goods_amount)
|
||||
* @param array $config 复购配置
|
||||
* @param bool $isGoodsEnable 该商品是否开启复购
|
||||
* @param bool $isUserRepurchase 该用户是否复购该商品
|
||||
* @return float
|
||||
*/
|
||||
public static function calculateItemDiscount($item, $config, $isGoodsEnable, $isUserRepurchase)
|
||||
{
|
||||
if (empty($config['enable']) || empty($config['self_enable'])) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
if (!$isGoodsEnable || !$isUserRepurchase) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$goodsNum = isset($item['goods_num']) ? intval($item['goods_num']) : 1;
|
||||
$goodsAmount = isset($item['goods_amount']) ? floatval($item['goods_amount']) : 0.0;
|
||||
$goodsPrice = isset($item['goods_price']) ? floatval($item['goods_price']) : ($goodsNum > 0 ? $goodsAmount / $goodsNum : 0.0);
|
||||
|
||||
if ($goodsAmount <= 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$discount = 0.0;
|
||||
$selfType = $config['self_type'] ?? 'fixed';
|
||||
|
||||
if ($selfType === 'rate') {
|
||||
$rate = floatval($config['self_rate'] ?? 0);
|
||||
if ($rate > 0) {
|
||||
$discount = round($goodsAmount * $rate * 0.01, 2);
|
||||
}
|
||||
} else {
|
||||
// 固定金额模式:按件数累加立减
|
||||
$unitAmount = floatval($config['self_amount'] ?? 0);
|
||||
if ($unitAmount > 0) {
|
||||
// 单件立减不可超过单件售价
|
||||
$validUnitDiscount = min($unitAmount, $goodsPrice);
|
||||
$discount = round($validUnitDiscount * $goodsNum, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// 兜底保护:总立减金额不可超过商品总额
|
||||
if ($discount > $goodsAmount) {
|
||||
$discount = $goodsAmount;
|
||||
}
|
||||
|
||||
return max(0.0, $discount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量计算订单中各商品的复购立减金额
|
||||
*
|
||||
* @param array $goodsList 商品列表
|
||||
* @param int $userId 用户ID
|
||||
* @param array|null $config 配置
|
||||
* @param callable|null $goodsRuleChecker 自定义商品规则检查器 (用于单测或依赖注入)
|
||||
* @param callable|null $historyChecker 自定义历史购买检查器 (用于单测或依赖注入)
|
||||
* @return array ['goods_list' => ..., 'total_repurchase_discount' => float]
|
||||
*/
|
||||
public static function calculateOrderRepurchaseDiscounts(
|
||||
$goodsList,
|
||||
$userId,
|
||||
$config = null,
|
||||
$goodsRuleChecker = null,
|
||||
$historyChecker = null
|
||||
) {
|
||||
if ($config === null) {
|
||||
$config = self::getConfig();
|
||||
}
|
||||
|
||||
$totalDiscount = 0.0;
|
||||
|
||||
foreach ($goodsList as $key => $item) {
|
||||
$goodsId = $item['goods_id'] ?? 0;
|
||||
|
||||
// 检查商品是否参与复购
|
||||
$isGoodsEnable = false;
|
||||
if ($goodsRuleChecker !== null) {
|
||||
$isGoodsEnable = (bool) call_user_func($goodsRuleChecker, $goodsId);
|
||||
} else {
|
||||
$isGoodsEnable = self::isGoodsRepurchaseEnable($goodsId);
|
||||
}
|
||||
|
||||
// 检查用户是否复购该商品
|
||||
$isUserRepurchase = false;
|
||||
if ($isGoodsEnable) {
|
||||
if ($historyChecker !== null) {
|
||||
$isUserRepurchase = (bool) call_user_func($historyChecker, $userId, $goodsId);
|
||||
} else {
|
||||
$isUserRepurchase = self::isUserRepurchasedGoods($userId, $goodsId);
|
||||
}
|
||||
}
|
||||
|
||||
$discount = self::calculateItemDiscount($item, $config, $isGoodsEnable, $isUserRepurchase);
|
||||
|
||||
$goodsList[$key]['repurchase_discount_money'] = $discount;
|
||||
$goodsList[$key]['is_repurchase'] = ($isGoodsEnable && $isUserRepurchase && $discount > 0) ? 1 : 0;
|
||||
|
||||
$totalDiscount = bcadd((string) $totalDiscount, (string) $discount, 2);
|
||||
}
|
||||
|
||||
return [
|
||||
'goods_list' => $goodsList,
|
||||
'total_repurchase_discount' => floatval($totalDiscount)
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,41 @@ trait OrderOper
|
||||
$user_address
|
||||
);
|
||||
|
||||
// 计算满足复购规则的指定商品的复购立减
|
||||
$repurchase_discount_money = 0.0;
|
||||
try {
|
||||
$repurchaseResult = \addons\shopro\library\commission\RepurchaseService::calculateOrderRepurchaseDiscounts(
|
||||
$new_goods_list,
|
||||
$user ? $user->id : 0
|
||||
);
|
||||
$new_goods_list = $repurchaseResult['goods_list'];
|
||||
$repurchase_discount_money = $repurchaseResult['total_repurchase_discount'];
|
||||
|
||||
// 将复购立减注入活动优惠列表,前端零改动原生展示
|
||||
if ($repurchase_discount_money > 0) {
|
||||
$repurchaseGoodsIds = [];
|
||||
foreach ($new_goods_list as $g) {
|
||||
if (!empty($g['repurchase_discount_money']) && $g['repurchase_discount_money'] > 0) {
|
||||
$repurchaseGoodsIds[] = $g['goods_id'];
|
||||
}
|
||||
}
|
||||
$activity_discount_infos[] = [
|
||||
'activity_id' => 'repurchase',
|
||||
'activity_title' => '复购立减',
|
||||
'activity_type' => 'repurchase',
|
||||
'activity_discount_money' => $repurchase_discount_money,
|
||||
'rule_type' => 'money',
|
||||
'discount_rule' => [
|
||||
'discount' => $repurchase_discount_money,
|
||||
],
|
||||
'goods_ids' => implode(',', $repurchaseGoodsIds)
|
||||
];
|
||||
$activity_discount_money = bcadd((string)$activity_discount_money, (string)$repurchase_discount_money, 2);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$repurchase_discount_money = 0.0;
|
||||
}
|
||||
|
||||
// 计算订单总金额,需支付金额
|
||||
list(
|
||||
$new_goods_list,
|
||||
@@ -105,7 +140,8 @@ trait OrderOper
|
||||
$dispatch_discount_money,
|
||||
$free_shipping_goods_ids,
|
||||
$coupon_money,
|
||||
$deliveryFee
|
||||
$deliveryFee,
|
||||
$repurchase_discount_money
|
||||
);
|
||||
|
||||
// 获取发票金额
|
||||
@@ -150,7 +186,8 @@ trait OrderOper
|
||||
$invoice_amount,
|
||||
$deliveryFee,
|
||||
$expressList,
|
||||
$expressName
|
||||
$expressName,
|
||||
$repurchase_discount_money
|
||||
);
|
||||
}
|
||||
|
||||
@@ -242,7 +279,8 @@ trait OrderOper
|
||||
$invoice,
|
||||
$invoice_amount,
|
||||
$deliveryFee,
|
||||
$expressName
|
||||
$expressName,
|
||||
$repurchase_discount_money
|
||||
) {
|
||||
// 订单创建前
|
||||
$data = [
|
||||
@@ -260,6 +298,7 @@ trait OrderOper
|
||||
'coupon_fee' => $coupon_fee,
|
||||
'activity_discount_money' => $activity_discount_money,
|
||||
'dispatch_discount_money' => $dispatch_discount_money,
|
||||
'repurchase_discount_money' => $repurchase_discount_money,
|
||||
'activity_discount_infos' => $activity_discount_infos,
|
||||
'new_goods_list' => $new_goods_list,
|
||||
'activity_type' => $activity_type,
|
||||
@@ -348,6 +387,9 @@ trait OrderOper
|
||||
$orderData['platform'] = request()->header('platform');
|
||||
|
||||
$ext = $activity_discount_infos ? ['activity_discount_infos' => $activity_discount_infos] : []; // 促销活动信息
|
||||
if (!empty($repurchase_discount_money) && floatval($repurchase_discount_money) > 0) {
|
||||
$ext['repurchase_discount_money'] = $repurchase_discount_money;
|
||||
}
|
||||
$orderData['ext'] = json_encode($ext);
|
||||
$order = new Order();
|
||||
$order->allowField(true)->save($orderData);
|
||||
@@ -428,6 +470,9 @@ trait OrderOper
|
||||
if (isset($buyinfo['dispatch_phone'])) {
|
||||
$ext['dispatch_phone'] = $buyinfo['dispatch_phone'];
|
||||
}
|
||||
if (isset($buyinfo['repurchase_discount_money']) && floatval($buyinfo['repurchase_discount_money']) > 0) {
|
||||
$ext['repurchase_discount_money'] = $buyinfo['repurchase_discount_money'];
|
||||
}
|
||||
$orderItem->ext = json_encode($ext);
|
||||
$orderItem->save();
|
||||
}
|
||||
|
||||
@@ -717,7 +717,8 @@ trait OrderOperCreate
|
||||
$dispatch_discount_money,
|
||||
$free_shipping_goods_ids,
|
||||
$coupon_money,
|
||||
$deliveryFee
|
||||
$deliveryFee,
|
||||
$repurchase_discount_money = 0
|
||||
)
|
||||
{
|
||||
// (搜这个找注释)加上运费,dispatch_amount 没有重新计算,和 origin_dispatch_amount 相同
|
||||
@@ -737,7 +738,8 @@ trait OrderOperCreate
|
||||
$activity_discount_infos,
|
||||
$activity_discount_money,
|
||||
$dispatch_discount_money,
|
||||
$free_shipping_goods_ids
|
||||
$free_shipping_goods_ids,
|
||||
$repurchase_discount_money
|
||||
);
|
||||
|
||||
return [
|
||||
@@ -814,7 +816,8 @@ trait OrderOperCreate
|
||||
$invoice_amount,
|
||||
$deliveryFee,
|
||||
$expressList,
|
||||
$expressName
|
||||
$expressName,
|
||||
$repurchase_discount_money = 0
|
||||
)
|
||||
{
|
||||
// 需要处理小数点的数据
|
||||
@@ -829,6 +832,7 @@ trait OrderOperCreate
|
||||
"coupon_fee",
|
||||
"activity_discount_money",
|
||||
"dispatch_discount_money",
|
||||
"repurchase_discount_money",
|
||||
"invoice_amount",
|
||||
'deliveryFee'
|
||||
);
|
||||
@@ -882,7 +886,8 @@ trait OrderOperCreate
|
||||
$activity_discount_infos,
|
||||
$activity_discount_money,
|
||||
$dispatch_discount_money,
|
||||
$free_shipping_goods_ids = []
|
||||
$free_shipping_goods_ids = [],
|
||||
$repurchase_discount_money = 0
|
||||
)
|
||||
{
|
||||
// 如果邮费有优惠,分配邮费优惠的金额
|
||||
@@ -915,8 +920,8 @@ trait OrderOperCreate
|
||||
|
||||
if (floatval($activity_discount_money)) {
|
||||
foreach ($activity_discount_infos as $key => $info) {
|
||||
if ($info['activity_type'] == 'free_shipping') {
|
||||
// 上面已经单独处理过满包邮优惠
|
||||
if ($info['activity_type'] == 'free_shipping' || $info['activity_type'] == 'repurchase') {
|
||||
// 上面已经单独处理过满包邮优惠,复购立减优先按商品精准分配
|
||||
continue;
|
||||
}
|
||||
$current_discount_goods_ids = explode(',', $info['goods_ids']);
|
||||
@@ -946,8 +951,21 @@ trait OrderOperCreate
|
||||
}
|
||||
}
|
||||
|
||||
// 剩余要加权平均分配的优惠
|
||||
$last_discount_money = bcsub($discount_fee, bcadd($dispatch_discount_money, $activity_discount_money, 2), 2);
|
||||
// 如果存在复购立减,精准归属到满足复购的指定商品本身
|
||||
if (floatval($repurchase_discount_money)) {
|
||||
foreach ($new_goods_list as $key => $buyinfo) {
|
||||
$itemRepurchaseDiscount = $buyinfo['repurchase_discount_money'] ?? 0;
|
||||
if (floatval($itemRepurchaseDiscount) > 0) {
|
||||
$new_goods_list[$key]['discount_fee'] = bcadd($new_goods_list[$key]['discount_fee'], (string)$itemRepurchaseDiscount, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 剩余要加权平均分配的优惠(如优惠券),需剔除邮费优惠和全部活动优惠(含复购立减)
|
||||
$assigned_discount = bcadd($dispatch_discount_money, $activity_discount_money, 2);
|
||||
$last_discount_money = bcsub($discount_fee, $assigned_discount, 2);
|
||||
$last_discount_money = $last_discount_money > 0 ? $last_discount_money : 0;
|
||||
|
||||
// 计算每个商品分配到的优惠券的优惠金额,顺便计算出来 pay_price 的金额
|
||||
foreach ($new_goods_list as $key => $buyinfo) {
|
||||
$scale = 0; // 按照商品价格和总价计算每个 item 的比例
|
||||
@@ -959,7 +977,8 @@ trait OrderOperCreate
|
||||
$current_discount_fee = round(bcmul($last_discount_money, $scale, 3), 2);
|
||||
$new_goods_list[$key]['discount_fee'] = bcadd($new_goods_list[$key]['discount_fee'], $current_discount_fee, 2);
|
||||
// 每个商品除了运费之后分配的支付金额
|
||||
$new_goods_list[$key]['pay_price'] = bcsub($buyinfo['goods_amount'], $new_goods_list[$key]['discount_fee'], 2);
|
||||
$calc_pay_price = bcsub($buyinfo['goods_amount'], $new_goods_list[$key]['discount_fee'], 2);
|
||||
$new_goods_list[$key]['pay_price'] = $calc_pay_price < 0 ? '0.00' : $calc_pay_price;
|
||||
}
|
||||
|
||||
return $new_goods_list;
|
||||
|
||||
@@ -32,7 +32,7 @@ class Activity extends Model
|
||||
|
||||
public static function getTypeList()
|
||||
{
|
||||
return ['seckill' => '秒杀', 'timer' => '限时秒杀', 'robbuy' => '预约抢购', 'groupon' => '拼团', 'full_reduce' => '满额立减', 'full_discount' => '满额折扣', 'free_shipping' => '满额包邮'];
|
||||
return ['seckill' => '秒杀', 'timer' => '限时秒杀', 'robbuy' => '预约抢购', 'groupon' => '拼团', 'full_reduce' => '满额立减', 'full_discount' => '满额折扣', 'free_shipping' => '满额包邮', 'repurchase' => '复购立减'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,9 +49,11 @@ class Activity extends Model
|
||||
$tag .= $activity_type == 'full_reduce' ? '减' . $ruleData['discount'] : $ruleData['discount'] . '折';
|
||||
} else if ($activity_type == 'free_shipping') {
|
||||
$tag = '满' . $ruleData['full_num'] . ($ruleData['type'] == 'money' ? '元' : '件') . '包邮';
|
||||
} else if ($activity_type == 'repurchase') {
|
||||
$tag = '老客复购立减' . ($ruleData['discount'] ?? '') . '元';
|
||||
}
|
||||
|
||||
return $tag;
|
||||
return $tag ?? '';
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -55,10 +55,11 @@ class Order extends Model
|
||||
if ($ext && isset($ext['activity_discount_infos']) && $ext['activity_discount_infos']) {
|
||||
|
||||
foreach ($ext['activity_discount_infos'] as $key => $info) {
|
||||
$ext['activity_discount_infos'][$key]['activity_type_text'] = Activity::getTypeList()[$info['activity_type']];
|
||||
$typeList = Activity::getTypeList();
|
||||
$ext['activity_discount_infos'][$key]['activity_type_text'] = $typeList[$info['activity_type']] ?? ($info['activity_title'] ?? '优惠');
|
||||
$ext['activity_discount_infos'][$key]['format_text'] = Activity::formatDiscountTags($info['activity_type'], array_merge([
|
||||
'type' => $info['rule_type'],
|
||||
], $info['discount_rule']));
|
||||
'type' => $info['rule_type'] ?? 'money',
|
||||
], $info['discount_rule'] ?? []));
|
||||
}
|
||||
}
|
||||
return $ext;
|
||||
|
||||
@@ -72,22 +72,8 @@ class CommissionScenarioCalculator
|
||||
}
|
||||
}
|
||||
|
||||
if ($isRepurchase && $repurchaseSelfEnable && $productRepurchaseEnable) {
|
||||
$cashback = 0;
|
||||
if ($repurchaseSelfType === 'fixed') {
|
||||
$cashback = round($repurchaseSelfAmount, 2);
|
||||
} elseif ($repurchaseSelfRate > 0) {
|
||||
$cashback = round($amount * $repurchaseSelfRate * 0.01, 2);
|
||||
}
|
||||
if ($cashback > 0) {
|
||||
$rewards[] = [
|
||||
'name' => $buyerName,
|
||||
'award_type' => 'repurchase_self',
|
||||
'commission_level' => 0,
|
||||
'commission' => $cashback,
|
||||
];
|
||||
}
|
||||
}
|
||||
// 复购已优化为付款时直接减免,分销模块不再向买家发放事后返现佣金
|
||||
// if ($isRepurchase && $repurchaseSelfEnable && $productRepurchaseEnable) { ... }
|
||||
|
||||
$total = 0;
|
||||
foreach ($rewards as $row) {
|
||||
@@ -248,13 +234,13 @@ class CommissionScenarioCalculator
|
||||
],
|
||||
],
|
||||
'scenario_3' => [
|
||||
'title' => '场景3:复购 · D 第二次购买1000元 · 本人固定返现5元',
|
||||
'title' => '场景3:复购 · D 第二次购买1000元 · 付款时已立减5元(不返现)',
|
||||
'steps' => [
|
||||
'1. D 已有至少一笔 status in (1,2) 的历史订单',
|
||||
'2. 复购推荐人分佣=继续分佣;本人复购返现=开启,类型=固定5元',
|
||||
'3. 商品勾选参与复购返现',
|
||||
'4. D 再次下单 1000 元支付',
|
||||
'5. 推荐人仍拿关系链+级差+管理奖;D 本人额外拿 5 元复购返现',
|
||||
'1. D 历史已有至少一笔包含该指定商品的有效订单 (status in 1,2)',
|
||||
'2. 复购推荐人分佣=继续分佣;本人复购立减=开启,类型=固定5元',
|
||||
'3. 商品勾选参与复购立减',
|
||||
'4. D 再次下单时直接立减 5 元,实付 995 元',
|
||||
'5. 推荐人仍拿关系链+级差+管理奖;D 本人付款立减,不再产生返现佣金',
|
||||
],
|
||||
'options' => [
|
||||
'amount' => 1000,
|
||||
@@ -268,11 +254,11 @@ class CommissionScenarioCalculator
|
||||
],
|
||||
],
|
||||
'scenario_4' => [
|
||||
'title' => '场景4:复购 · 推荐人不再分佣 · 仅本人返现5元',
|
||||
'title' => '场景4:复购 · 推荐人不再分佣 · 买家付款立减5元(佣金奖池为0)',
|
||||
'steps' => [
|
||||
'1. 分销设置:复购推荐人分佣=不再分佣',
|
||||
'2. D 复购 1000 元',
|
||||
'3. 预期:C/B/A 无佣金,仅 D 得 5 元复购返现',
|
||||
'2. D 复购付款时已直接立减 5 元',
|
||||
'3. 预期:C/B/A 无分销佣金,买家已立减亦无返现佣金,总佣金为 0',
|
||||
],
|
||||
'options' => [
|
||||
'amount' => 1000,
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
/**
|
||||
* 复购立减(付款直接减免、不返现)单元测试
|
||||
*
|
||||
* 运行方式:
|
||||
* php server/addons/shopro/tests/repurchase_discount_test.php
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../library/commission/RepurchaseService.php';
|
||||
|
||||
use addons\shopro\library\commission\RepurchaseService;
|
||||
|
||||
class RepurchaseDiscountTest
|
||||
{
|
||||
private $passed = 0;
|
||||
private $failed = 0;
|
||||
|
||||
public function run()
|
||||
{
|
||||
echo "========================================\n";
|
||||
echo " 复购立减单元测试 (TDD)\n";
|
||||
echo "========================================\n\n";
|
||||
|
||||
$this->testNonRepurchaseGoods();
|
||||
$this->testFirstBuyNoDiscount();
|
||||
$this->testRepurchaseFixedDiscount();
|
||||
$this->testRepurchaseRateDiscount();
|
||||
$this->testDiscountCapProtection();
|
||||
$this->testMixedOrderDiscounts();
|
||||
|
||||
echo "\n----------------------------------------\n";
|
||||
echo "测试结果: {$this->passed} 通过, {$this->failed} 失败\n";
|
||||
echo "----------------------------------------\n";
|
||||
|
||||
if ($this->failed > 0) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private function assert($condition, $message)
|
||||
{
|
||||
if ($condition) {
|
||||
$this->passed++;
|
||||
echo " [PASS] {$message}\n";
|
||||
} else {
|
||||
$this->failed++;
|
||||
echo " [FAIL] {$message}\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试场景 1: 商品未开启复购规则,即使买家买过多次,也不享受复购立减
|
||||
*/
|
||||
public function testNonRepurchaseGoods()
|
||||
{
|
||||
echo "场景 1: 未开启复购的普通商品测试\n";
|
||||
|
||||
$item = [
|
||||
'goods_id' => 101,
|
||||
'goods_num' => 2,
|
||||
'goods_price' => 100.00,
|
||||
'goods_amount' => 200.00
|
||||
];
|
||||
$config = [
|
||||
'enable' => true,
|
||||
'self_enable' => true,
|
||||
'self_type' => 'fixed',
|
||||
'self_amount' => 10.00,
|
||||
'self_rate' => 5.0
|
||||
];
|
||||
|
||||
// 模拟商品没有开启复购规则 (repurchase_enable = 0)
|
||||
$discount = RepurchaseService::calculateItemDiscount(
|
||||
$item,
|
||||
$config,
|
||||
false, // 商品未参与复购
|
||||
true // 买家历史买过
|
||||
);
|
||||
|
||||
$this->assert($discount === 0.0, "未开启复购的商品立减金额应为 0.00 (实际: {$discount})");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试场景 2: 指定商品首次购买(历史无订单),不享受立减
|
||||
*/
|
||||
public function testFirstBuyNoDiscount()
|
||||
{
|
||||
echo "\n场景 2: 指定商品首购(历史无该商品购买记录)\n";
|
||||
|
||||
$item = [
|
||||
'goods_id' => 102,
|
||||
'goods_num' => 1,
|
||||
'goods_price' => 200.00,
|
||||
'goods_amount' => 200.00
|
||||
];
|
||||
$config = [
|
||||
'enable' => true,
|
||||
'self_enable' => true,
|
||||
'self_type' => 'fixed',
|
||||
'self_amount' => 20.00,
|
||||
'self_rate' => 10.0
|
||||
];
|
||||
|
||||
// 商品参与复购,但买家首次购买(isRepurchase = false)
|
||||
$discount = RepurchaseService::calculateItemDiscount(
|
||||
$item,
|
||||
$config,
|
||||
true, // 商品参与复购
|
||||
false // 买家首次购买
|
||||
);
|
||||
|
||||
$this->assert($discount === 0.0, "首购指定商品立减金额应为 0.00 (实际: {$discount})");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试场景 3: 指定商品复购(固定金额模式,按件累加)
|
||||
*/
|
||||
public function testRepurchaseFixedDiscount()
|
||||
{
|
||||
echo "\n场景 3: 指定商品复购 · 固定金额模式 (买 3 件,每件立减 5 元)\n";
|
||||
|
||||
$item = [
|
||||
'goods_id' => 103,
|
||||
'goods_num' => 3,
|
||||
'goods_price' => 50.00,
|
||||
'goods_amount' => 150.00
|
||||
];
|
||||
$config = [
|
||||
'enable' => true,
|
||||
'self_enable' => true,
|
||||
'self_type' => 'fixed',
|
||||
'self_amount' => 5.00,
|
||||
'self_rate' => 0
|
||||
];
|
||||
|
||||
$discount = RepurchaseService::calculateItemDiscount(
|
||||
$item,
|
||||
$config,
|
||||
true, // 商品参与复购
|
||||
true // 买家复购
|
||||
);
|
||||
|
||||
// 预期立减 5 * 3 = 15.00
|
||||
$this->assert($discount === 15.00, "复购 3 件每件立减 5 元,总立减应为 15.00 (实际: {$discount})");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试场景 4: 指定商品复购 · 百分比模式 (立减 10%)
|
||||
*/
|
||||
public function testRepurchaseRateDiscount()
|
||||
{
|
||||
echo "\n场景 4: 指定商品复购 · 百分比模式 (100 元 × 2 件,立减 10%)\n";
|
||||
|
||||
$item = [
|
||||
'goods_id' => 104,
|
||||
'goods_num' => 2,
|
||||
'goods_price' => 100.00,
|
||||
'goods_amount' => 200.00
|
||||
];
|
||||
$config = [
|
||||
'enable' => true,
|
||||
'self_enable' => true,
|
||||
'self_type' => 'rate',
|
||||
'self_amount' => 0,
|
||||
'self_rate' => 10.0
|
||||
];
|
||||
|
||||
$discount = RepurchaseService::calculateItemDiscount(
|
||||
$item,
|
||||
$config,
|
||||
true, // 商品参与复购
|
||||
true // 买家复购
|
||||
);
|
||||
|
||||
// 预期立减 200 * 10% = 20.00
|
||||
$this->assert($discount === 20.00, "复购 200 元立减 10%,总立减应为 20.00 (实际: {$discount})");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试场景 5: 边界兜底保护(立减金额不可超过商品总额)
|
||||
*/
|
||||
public function testDiscountCapProtection()
|
||||
{
|
||||
echo "\n场景 5: 边界兜底保护 (商品单价 5 元,配置立减 10 元)\n";
|
||||
|
||||
$item = [
|
||||
'goods_id' => 105,
|
||||
'goods_num' => 1,
|
||||
'goods_price' => 5.00,
|
||||
'goods_amount' => 5.00
|
||||
];
|
||||
$config = [
|
||||
'enable' => true,
|
||||
'self_enable' => true,
|
||||
'self_type' => 'fixed',
|
||||
'self_amount' => 10.00,
|
||||
'self_rate' => 0
|
||||
];
|
||||
|
||||
$discount = RepurchaseService::calculateItemDiscount(
|
||||
$item,
|
||||
$config,
|
||||
true, // 商品参与复购
|
||||
true // 买家复购
|
||||
);
|
||||
|
||||
// 预期立减不得超过 5.00
|
||||
$this->assert($discount === 5.00, "立减金额不得超过商品金额 5.00 (实际: {$discount})");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试场景 6: 混合商品订单结算 (包含复购商品与非复购商品)
|
||||
*/
|
||||
public function testMixedOrderDiscounts()
|
||||
{
|
||||
echo "\n场景 6: 混合商品订单结算 (商品A复购立减10元,商品B首购不减)\n";
|
||||
|
||||
$goodsList = [
|
||||
[
|
||||
'goods_id' => 201,
|
||||
'goods_num' => 1,
|
||||
'goods_price' => 100.00,
|
||||
'goods_amount' => 100.00,
|
||||
'discount_fee' => 0.00
|
||||
],
|
||||
[
|
||||
'goods_id' => 202,
|
||||
'goods_num' => 2,
|
||||
'goods_price' => 50.00,
|
||||
'goods_amount' => 100.00,
|
||||
'discount_fee' => 0.00
|
||||
]
|
||||
];
|
||||
|
||||
$config = [
|
||||
'enable' => true,
|
||||
'self_enable' => true,
|
||||
'self_type' => 'fixed',
|
||||
'self_amount' => 10.00,
|
||||
'self_rate' => 0
|
||||
];
|
||||
|
||||
// 模拟商品规则:201 参与复购,202 不参与
|
||||
$goodsRuleChecker = function ($goodsId) {
|
||||
return $goodsId === 201;
|
||||
};
|
||||
|
||||
// 模拟历史购买记录:买家买过 201,未买过 202
|
||||
$historyChecker = function ($userId, $goodsId) {
|
||||
return $goodsId === 201;
|
||||
};
|
||||
|
||||
$result = RepurchaseService::calculateOrderRepurchaseDiscounts(
|
||||
$goodsList,
|
||||
1, // userId
|
||||
$config,
|
||||
$goodsRuleChecker,
|
||||
$historyChecker
|
||||
);
|
||||
|
||||
$totalDiscount = $result['total_repurchase_discount'];
|
||||
$newGoodsList = $result['goods_list'];
|
||||
|
||||
$this->assert($totalDiscount === 10.00, "整单复购立减总额应为 10.00 (实际: {$totalDiscount})");
|
||||
$this->assert($newGoodsList[0]['repurchase_discount_money'] === 10.00, "商品 201 立减应为 10.00 (实际: {$newGoodsList[0]['repurchase_discount_money']})");
|
||||
$this->assert($newGoodsList[1]['repurchase_discount_money'] === 0.00, "商品 202 立减应为 0.00 (实际: {$newGoodsList[1]['repurchase_discount_money']})");
|
||||
}
|
||||
}
|
||||
|
||||
(new RepurchaseDiscountTest())->run();
|
||||
@@ -621,25 +621,25 @@
|
||||
</el-radio-group>
|
||||
<div class="title-tip">买家复购时,其上级推荐人是否仍按关系链/级差/管理奖规则获得差价;选「不再分佣」则复购订单不再给推荐人发任何推广差价</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="本人复购返现:">
|
||||
<el-form-item label="本人复购立减:">
|
||||
<el-radio-group v-model="configData.repurchase_self_enable">
|
||||
<el-radio label="1">开启</el-radio>
|
||||
<el-radio label="0">关闭</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="title-tip">买家复购时是否给买家本人发放复购返现;需在分销商品上勾选「参与复购返现」才会对该商品生效</div>
|
||||
<div class="title-tip">买家复购指定商品时是否在商品付款时直接减免(不返现);需在分销商品上勾选「参与复购立减」才会对该指定商品生效</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="返现金额类型:">
|
||||
<el-form-item label="减免金额类型:">
|
||||
<el-radio-group v-model="configData.repurchase_self_type">
|
||||
<el-radio label="fixed">固定金额</el-radio>
|
||||
<el-radio label="rate">订单百分比</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="固定返现金额:" v-if="configData.repurchase_self_type=='fixed'">
|
||||
<el-form-item label="固定减免金额:" v-if="configData.repurchase_self_type=='fixed'">
|
||||
<el-input v-model="configData.repurchase_self_amount" v-enter-number style="width: 200px;" size="small">
|
||||
<template slot="append">元</template>
|
||||
<template slot="append">元/件</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="返现比例:" v-if="configData.repurchase_self_type=='rate'">
|
||||
<el-form-item label="减免比例:" v-if="configData.repurchase_self_type=='rate'">
|
||||
<el-input v-model="configData.repurchase_self_rate" v-enter-number style="width: 200px;" size="small">
|
||||
<template slot="append">%</template>
|
||||
</el-input>
|
||||
|
||||
@@ -226,14 +226,14 @@
|
||||
<el-form-item label="参与管理奖:">
|
||||
<el-switch v-model="management_enable" :active-value="1" :inactive-value="0" active-color="#6E3DC8"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="参与复购返现:">
|
||||
<el-form-item label="参与复购立减:">
|
||||
<div class="display-flex">
|
||||
<el-switch v-model="repurchase_enable" :active-value="1" :inactive-value="0" active-color="#6E3DC8"></el-switch>
|
||||
<el-popover
|
||||
placement="top"
|
||||
trigger="hover"
|
||||
width="320"
|
||||
content="开启后,买家复购该商品时可获得本人复购返现(需全局开启「本人复购返现」);关闭则该商品不参与复购返现">
|
||||
content="开启后,买家再次购买该商品时可直接在付款时享受减免(需全局开启「本人复购立减」);关闭则该商品不参与复购减免">
|
||||
<i class="el-icon-question commission-order-status-icon" slot="reference"></i>
|
||||
</el-popover>
|
||||
</div>
|
||||
|
||||
在新工单中引用
屏蔽一个用户