feat: 完善商品计奖隔离、积分商城兑换券抵扣与下线售后退款入口

这个提交包含在:
chen
2026-09-17 16:55:08 +08:00
父节点 f5b804fb57
当前提交 c3f5b3d4b5
共有 22 个文件被更改,包括 720 次插入 和 183 次删除

查看文件

@@ -14,9 +14,6 @@ class Partner
public function orderPaidAfter($params)
{
$order = $params['order'];
if (($order['type'] ?? '') === 'score') {
return;
}
$orderModel = is_object($order) ? $order : OrderModel::where('id', $order['id'] ?? 0)->find();
if ($orderModel) {
@@ -26,6 +23,12 @@ class Partner
format_log_error($e, 'Partner.consumeByOrder', '兑换券核销失败: ' . $e->getMessage());
}
}
// 积分商品订单不参与收益中心奖励结算
if (($order['type'] ?? '') === 'score') {
return;
}
if (!PartnerConfig::isEnabled()) {
return;
}

查看文件

@@ -607,11 +607,11 @@ class OrderCreate
public function calcVoucher()
{
$available = 0;
if ($this->user && $this->user->id && $this->order_type !== 'score') {
if ($this->user && $this->user->id) {
$available = (new \addons\shopro\service\partner\VoucherService())->available($this->user->id);
}
$this->orderData['voucher_available'] = $available;
$want = $this->order_type === 'score' ? 0 : max(0, round((float) $this->voucher_fee, 2));
$want = max(0, round((float) $this->voucher_fee, 2));
$apply = min($want, (float) $available, (float) $this->orderData['pay_fee']);
$apply = $apply > 0 ? $apply : 0;
$this->orderData['voucher_fee'] = $apply;

查看文件

@@ -38,12 +38,9 @@ class ActiveService
public function hasQualifiedOrder($userId, $start, $end)
{
$order = OrderModel::where('user_id', $userId)
->where('type', '<>', 'score')
->where('pay_fee', '>', 0)
->paid()
->where('paid_time', '>=', $start)
->where('paid_time', '<=', $end)
$rateService = new RateService();
$order = $rateService->qualifiedOrderQuery($start, $end)
->where('o.user_id', $userId)
->find();
return (bool) $order;
}
@@ -72,15 +69,11 @@ class ActiveService
})->column('user_id');
if (!empty($nullUserIds)) {
$orderHits = \think\Db::name('shopro_order')
->whereIn('user_id', $nullUserIds)
->where('type', '<>', 'score')
->where('pay_fee', '>', 0)
->whereIn('status', ['paid', 'completed'])
->where('paid_time', '>=', $start)
->where('paid_time', '<=', $end)
->group('user_id')
->column('max(paid_time)', 'user_id');
$rateService = new RateService();
$orderHits = $rateService->qualifiedOrderQuery($start, $end)
->whereIn('o.user_id', $nullUserIds)
->group('o.user_id')
->column('max(o.paid_time)', 'o.user_id');
foreach ($orderHits as $uId => $lastTime) {
\think\Db::name('shopro_commission_agent')->where('user_id', $uId)->update(['last_self_order_time' => $lastTime]);

查看文件

@@ -64,10 +64,7 @@ class DailySettleService
$orders = $rateService->qualifiedOrderQuery($start, $end)->field('o.id,o.pay_fee')->select();
$orderCount = count($orders);
$gmv = 0;
foreach ($orders as $order) {
$gmv = bcadd((string) $gmv, (string) $order['pay_fee'], 2);
}
$gmv = $rateService->qualifiedGmv($start, $end);
$windowStart = strtotime($settleDate . ' 00:00:00') - (PartnerConfig::activeDays() - 1) * 86400;
$tasterIds = $active->activeUserIds($windowStart, $end);
@@ -214,10 +211,7 @@ class DailySettleService
$orders = $rateService->qualifiedOrderQuery($start, $end)->field('o.id,o.pay_fee')->select();
$orderCount = count($orders);
$gmv = 0;
foreach ($orders as $order) {
$gmv = bcadd((string) $gmv, (string) $order['pay_fee'], 2);
}
$gmv = $rateService->qualifiedGmv($start, $end);
$windowStart = strtotime($settleDate . ' 00:00:00') - (PartnerConfig::activeDays() - 1) * 86400;
$tasterIds = $active->activeUserIds($windowStart, $end);

查看文件

@@ -101,9 +101,23 @@ class PartnerRewardService
return Db::transaction(function () use ($order, $item) {
$orderId = intval(is_array($order) ? ($order['id'] ?? 0) : ($order->id ?? 0));
$itemPayFee = floatval(is_array($item) ? ($item['pay_fee'] ?? 0) : ($item->pay_fee ?? 0));
$goodsId = intval(is_array($item) ? ($item['goods_id'] ?? 0) : ($item->goods_id ?? 0));
$ext = is_array($item['ext'] ?? null) ? $item['ext'] : (json_decode($item['ext'] ?? '', true) ?: []);
$isCommissionItem = true;
if (isset($ext['is_commission']) && !$ext['is_commission']) {
$isCommissionItem = false;
} elseif ($goodsId > 0) {
$cg = CommissionGoods::where('goods_id', $goodsId)->where('status', CommissionGoods::GOODS_COMMISSION_STATUS_ON)->find();
if (!$cg) {
$isCommissionItem = false;
}
} else {
$isCommissionItem = false;
}
$voucherService = new VoucherService();
if ($itemPayFee > 0) {
if ($itemPayFee > 0 && $isCommissionItem) {
$voucherService->reduceByRefund($orderId, $itemPayFee);
}
@@ -116,7 +130,7 @@ class PartnerRewardService
'amount' => 0,
'base_amount' => $itemPayFee,
'ext' => [
'formula' => "子商品退款 {$itemPayFee},缩减兑换券总额",
'formula' => $isCommissionItem ? "计奖商品退款 {$itemPayFee},缩减兑换券总额" : "非计奖商品退款 {$itemPayFee},无需缩减兑换券",
'item_id' => $item['id'] ?? 0,
'partial_refund' => 1
],
@@ -127,6 +141,42 @@ class PartnerRewardService
});
}
/**
* 获取订单中参与计奖的商品实付金额之和(排除非计奖商品及活动不参与项)
*/
public function getQualifiedPayFee($order)
{
if (!$order || ($order['type'] ?? '') === 'score') {
return 0.0;
}
$items = $order['items'] ?? [];
if (empty($items) && !empty($order['id'])) {
$fresh = OrderModel::with(['items'])->where('id', $order['id'])->find();
if ($fresh) {
$order = $fresh;
$items = $fresh['items'] ?? [];
}
}
$total = 0.0;
foreach ($items as $item) {
$ext = is_array($item['ext'] ?? null) ? $item['ext'] : (json_decode($item['ext'] ?? '', true) ?: []);
if (isset($ext['is_commission']) && !$ext['is_commission']) {
continue;
}
$goods = CommissionGoods::where('goods_id', $item['goods_id'])
->where('status', CommissionGoods::GOODS_COMMISSION_STATUS_ON)
->find();
if ($goods) {
$itemPay = floatval($item['pay_fee'] ?? 0);
$total = function_exists('bcadd') ? (float) bcadd((string) $total, (string) $itemPay, 2) : round($total + $itemPay, 2);
}
}
// 计奖实付金额上限不得超过整单实际现金支付金额(防止使用兑换券/整单折扣抵扣后对券抵扣部分重复计奖)
$orderPayFee = max(0, floatval($order['pay_fee'] ?? 0));
$total = min($total, $orderPayFee);
return (float) $total;
}
public function isQualified($order)
{
if (!$order || ($order['type'] ?? '') === 'score') {
@@ -139,26 +189,12 @@ class PartnerRewardService
if (!in_array($status, [OrderModel::STATUS_PAID, OrderModel::STATUS_COMPLETED])) {
return false;
}
$items = $order['items'] ?? [];
if (empty($items) && !empty($order['id'])) {
$fresh = OrderModel::with(['items'])->where('id', $order['id'])->find();
if ($fresh) {
$order = $fresh;
$items = $fresh['items'] ?? [];
}
$qualifiedPayFee = $this->getQualifiedPayFee($order);
$minAmount = (float) PartnerConfig::get('partner_order_min_amount', 0);
if ($minAmount > 0) {
return $qualifiedPayFee >= $minAmount;
}
foreach ($items as $item) {
if (isset($item['ext']['is_commission']) && !$item['ext']['is_commission']) {
continue;
}
$goods = CommissionGoods::where('goods_id', $item['goods_id'])
->where('status', CommissionGoods::GOODS_COMMISSION_STATUS_ON)
->find();
if ($goods) {
return true;
}
}
return false;
return $qualifiedPayFee > 0;
}
public function touchSelfOrder($userId, $time = null)

查看文件

@@ -88,4 +88,40 @@ class RateService
}
return $query;
}
/**
* 汇总指定时间区间内所有合格订单中“计奖商品”的实付总额(排除非计奖商品及活动不参与项)
*/
public function qualifiedGmv($start = null, $end = null)
{
$goodsTable = (new CommissionGoods())->getQuery()->getTable();
$orderTable = (new OrderModel())->getQuery()->getTable();
$itemTable = \think\Db::name('shopro_order_item')->getTable();
$query = \think\Db::table($itemTable)->alias('oi')
->join($orderTable . ' o', 'o.id = oi.order_id')
->join($goodsTable . ' cg', 'cg.goods_id = oi.goods_id')
->where('o.type', '<>', 'score')
->where('o.pay_fee', '>', 0)
->whereIn('o.status', [OrderModel::STATUS_PAID, OrderModel::STATUS_COMPLETED])
->where('cg.status', CommissionGoods::GOODS_COMMISSION_STATUS_ON)
->where(function ($q) {
$q->whereNull('oi.ext')
->whereOr('oi.ext', '')
->whereOr(function ($sq) {
$sq->where('oi.ext', 'not like', '%"is_commission":false%')
->where('oi.ext', 'not like', '%"is_commission":0%');
});
});
if ($start !== null) {
$query->where('o.paid_time', '>=', $start);
}
if ($end !== null) {
$query->where('o.paid_time', '<=', $end);
}
$gmv = $query->sum('oi.pay_fee');
return round((float) $gmv, 2);
}
}

查看文件

@@ -60,12 +60,23 @@ class VoucherService
if ($totalVoucher <= 0 || empty($ext['voucher_consumed'])) {
return;
}
$fee = $refundFee !== null ? min($totalVoucher, round((float) $refundFee, 2)) : $totalVoucher;
$userId = intval($order['user_id'] ?? 0);
$orderId = intval($order['id'] ?? 0);
// 幂等防重:检查是否已退回过兑换券,避免多次退款事件重复返还额度
$alreadyRefunded = (float) RewardModel::where('order_id', $orderId)
->where('reward_type', 'voucher_consume_refund')
->sum('amount');
$remainVoucher = max(0, round((float) $totalVoucher - $alreadyRefunded, 2));
if ($remainVoucher <= 0) {
return;
}
$fee = $refundFee !== null ? min($remainVoucher, round((float) $refundFee, 2)) : $remainVoucher;
if ($fee <= 0) {
return;
}
$userId = intval($order['user_id'] ?? 0);
$orderId = intval($order['id'] ?? 0);
$agent = AgentModel::where('user_id', $userId)->lock(true)->find();
if ($agent) {
$agent->voucher_used = max(0, round((float) $agent->voucher_used - $fee, 2));
@@ -82,6 +93,15 @@ class VoucherService
'ext' => ['formula' => "订单退款退回兑换券 {$fee}", 'order_id' => $orderId],
'status' => 1,
]);
if (is_object($order) && method_exists($order, 'save')) {
$ext['voucher_refunded'] = round($alreadyRefunded + $fee, 2);
if (bccomp((string) $ext['voucher_refunded'], (string) $totalVoucher, 2) >= 0) {
$ext['voucher_consumed'] = 0;
}
$order->ext = $ext;
$order->save();
}
}
}
@@ -113,7 +133,9 @@ class VoucherService
$times = (float) PartnerConfig::get('partner_voucher_times', 1.5);
$days = intval(PartnerConfig::get('partner_voucher_days', 365));
$days = $days > 0 ? $days : 365;
$total = round((float) $order['pay_fee'] * $times, 2);
$rewardService = new PartnerRewardService();
$qualifiedPayFee = $rewardService->getQualifiedPayFee($order);
$total = round((float) $qualifiedPayFee * $times, 2);
if ($total <= 0) {
return null;
}
@@ -146,7 +168,11 @@ class VoucherService
'reward_type' => 'voucher_grant',
'amount' => 0,
'base_amount' => $total,
'ext' => ['formula' => "实付{$order['pay_fee']} × {$times} = {$total},{$days}天释放"],
'ext' => [
'formula' => "计奖实付{$qualifiedPayFee} × {$times} = {$total},{$days}天释放",
'qualified_pay_fee' => $qualifiedPayFee,
'order_pay_fee' => $order['pay_fee'] ?? 0
],
'status' => 1,
]);