feat: 完善商品计奖隔离、积分商城兑换券抵扣与下线售后退款入口
这个提交包含在:
@@ -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,
|
||||
]);
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户