|
@@ -3,6 +3,7 @@
|
|
|
* 用途:商城顾客售后申请服务
|
|
* 用途:商城顾客售后申请服务
|
|
|
* 谁用:app-mall 提交/撤销/重新申请、app-hd 审核通过/驳回
|
|
* 谁用:app-mall 提交/撤销/重新申请、app-hd 审核通过/驳回
|
|
|
* 解决:申请只落待审核记录;撤销后可把已取消记录改回待审核;审核通过时复用 HdRefundService 执行真实退款
|
|
* 解决:申请只落待审核记录;撤销后可把已取消记录改回待审核;审核通过时复用 HdRefundService 执行真实退款
|
|
|
|
|
+ * 退货并退款金额由服务端重算:秒杀/团购按活动价,普通商品分摊会员折/满减/红包;不信任前端 price
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
namespace bizHd\refund\services;
|
|
namespace bizHd\refund\services;
|
|
@@ -34,17 +35,6 @@ class MallRefundApplyService extends BaseService
|
|
|
util::fail('退款方式不正确');
|
|
util::fail('退款方式不正确');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- $refundPrice = $post['price'] ?? 0;
|
|
|
|
|
- if (!is_numeric($refundPrice) || bccomp((string)$refundPrice, '0', 2) <= 0) {
|
|
|
|
|
- util::fail('退款金额必须大于0');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 可退上限:订单金额 - 已退累计
|
|
|
|
|
- $couldRefund = bcsub((string)($order->orderPrice ?? 0), (string)($order->tkPrice ?? 0), 2);
|
|
|
|
|
- if (bccomp((string)$refundPrice, $couldRefund, 2) === 1) {
|
|
|
|
|
- util::fail('退款金额超过可退金额');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
$productList = [];
|
|
$productList = [];
|
|
|
if ($refundType === MallRefundApplyClass::REFUND_TYPE_MONEY_GOOD) {
|
|
if ($refundType === MallRefundApplyClass::REFUND_TYPE_MONEY_GOOD) {
|
|
|
$productJson = $post['product'] ?? '';
|
|
$productJson = $post['product'] ?? '';
|
|
@@ -57,6 +47,19 @@ class MallRefundApplyService extends BaseService
|
|
|
util::fail('请选择要退的商品');
|
|
util::fail('请选择要退的商品');
|
|
|
}
|
|
}
|
|
|
$productList = self::validateAndEnrichProduct($productList, $order);
|
|
$productList = self::validateAndEnrichProduct($productList, $order);
|
|
|
|
|
+ // 退货并退款由服务端按秒杀/团购活动价 + 普通商品分摊优惠重算,不信任前端 price
|
|
|
|
|
+ $refundPrice = self::calcReturnRefundPrice($productList, $order);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $refundPrice = $post['price'] ?? 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!is_numeric($refundPrice) || bccomp((string)$refundPrice, '0', 2) <= 0) {
|
|
|
|
|
+ util::fail('退款金额必须大于0');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 可退上限:订单金额 - 已退累计
|
|
|
|
|
+ $couldRefund = bcsub((string)($order->orderPrice ?? 0), (string)($order->tkPrice ?? 0), 2);
|
|
|
|
|
+ if (bccomp((string)$refundPrice, $couldRefund, 2) === 1) {
|
|
|
|
|
+ util::fail('退款金额超过可退金额');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
$data = [
|
|
$data = [
|
|
@@ -134,10 +137,10 @@ class MallRefundApplyService extends BaseService
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 校验退货数量不超过可退数量,并补齐商品名快照
|
|
|
|
|
|
|
+ * 校验退货数量不超过可退数量,并补齐商品名、订单行单价快照
|
|
|
* @param array $productList [{productId,num,unitPrice,property,name?}] property 0商品 1花材
|
|
* @param array $productList [{productId,num,unitPrice,property,name?}] property 0商品 1花材
|
|
|
* @param object $order
|
|
* @param object $order
|
|
|
- * @return array 补齐 name 后的列表
|
|
|
|
|
|
|
+ * @return array 补齐 name/unitPrice 后的列表
|
|
|
*/
|
|
*/
|
|
|
protected static function validateAndEnrichProduct($productList, $order)
|
|
protected static function validateAndEnrichProduct($productList, $order)
|
|
|
{
|
|
{
|
|
@@ -176,6 +179,9 @@ class MallRefundApplyService extends BaseService
|
|
|
util::fail("{$name}超过可退数量");
|
|
util::fail("{$name}超过可退数量");
|
|
|
}
|
|
}
|
|
|
$row['name'] = $row['name'] ?? ($line->name ?? '');
|
|
$row['name'] = $row['name'] ?? ($line->name ?? '');
|
|
|
|
|
+ // 单价以订单行为准:秒杀/团购行即活动价,普通行再分摊整单优惠
|
|
|
|
|
+ $row['unitPrice'] = $line->unitPrice ?? ($row['unitPrice'] ?? 0);
|
|
|
|
|
+ $row['seckillGoodsId'] = (int)($line->seckillGoodsId ?? 0);
|
|
|
// 封面写入快照,售后列表不必再回查订单行
|
|
// 封面写入快照,售后列表不必再回查订单行
|
|
|
if (empty($row['cover'])) {
|
|
if (empty($row['cover'])) {
|
|
|
$row['cover'] = $line->cover ?? '';
|
|
$row['cover'] = $line->cover ?? '';
|
|
@@ -194,6 +200,7 @@ class MallRefundApplyService extends BaseService
|
|
|
util::fail("{$name}超过可退数量");
|
|
util::fail("{$name}超过可退数量");
|
|
|
}
|
|
}
|
|
|
$row['name'] = $row['name'] ?? ($line->name ?? '');
|
|
$row['name'] = $row['name'] ?? ($line->name ?? '');
|
|
|
|
|
+ $row['unitPrice'] = $line->unitPrice ?? ($row['unitPrice'] ?? 0);
|
|
|
if (empty($row['unitName'])) {
|
|
if (empty($row['unitName'])) {
|
|
|
$row['unitName'] = $line->unitName ?? '';
|
|
$row['unitName'] = $line->unitName ?? '';
|
|
|
}
|
|
}
|
|
@@ -206,6 +213,122 @@ class MallRefundApplyService extends BaseService
|
|
|
return $enriched;
|
|
return $enriched;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 退货并退款应退金额:秒杀/团购按行上活动单价;普通商品再按整单优惠占比扣除会员折、满减、红包
|
|
|
|
|
+ * 会员折/满减/红包下单时打在非秒杀应付上,分摊基数要剔除整单活动商品原价
|
|
|
|
|
+ * @param array $productList 已用订单行单价、seckillGoodsId 补齐的勾选商品
|
|
|
|
|
+ * @param object $order xhOrder
|
|
|
|
|
+ * @return float 不超过可退上限的应退金额
|
|
|
|
|
+ */
|
|
|
|
|
+ protected static function calcReturnRefundPrice($productList, $order)
|
|
|
|
|
+ {
|
|
|
|
|
+ $isGroupBuy = (int)($order->groupBuyId ?? 0) > 0;
|
|
|
|
|
+ $selectedActivity = 0.0;
|
|
|
|
|
+ $selectedNormal = 0.0;
|
|
|
|
|
+ foreach ($productList as $row) {
|
|
|
|
|
+ $num = $row['num'] ?? 0;
|
|
|
|
|
+ $unitPrice = $row['unitPrice'] ?? 0;
|
|
|
|
|
+ if (!is_numeric($num) || !is_numeric($unitPrice)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $line = (float)$num * (float)$unitPrice;
|
|
|
|
|
+ // 拼团整单,或花束行带 seckillGoodsId:按活动价、不摊会员折/满减
|
|
|
|
|
+ if ($isGroupBuy || (int)($row['seckillGoodsId'] ?? 0) > 0) {
|
|
|
|
|
+ $selectedActivity += $line;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $selectedNormal += $line;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $hbAmount = self::toMoney($order->hbAmount ?? 0);
|
|
|
|
|
+ $prePrice = self::toMoney($order->prePrice ?? 0);
|
|
|
|
|
+ if (bccomp($prePrice, '0', 2) > 0) {
|
|
|
|
|
+ $allocBase = bcadd($prePrice, $hbAmount, 2);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $allocBase = self::toMoney($order->goodsPrice ?? 0);
|
|
|
|
|
+ $allocBase = bcadd($allocBase, self::toMoney($order->sendCost ?? 0), 2);
|
|
|
|
|
+ $allocBase = bcadd($allocBase, self::toMoney($order->packingFee ?? 0), 2);
|
|
|
|
|
+ $allocBase = bcadd($allocBase, self::toMoney($order->labourCost ?? 0), 2);
|
|
|
|
|
+ $allocBase = bcadd($allocBase, self::toMoney($order->serviceFee ?? 0), 2);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $activityOriginal = self::sumOrderActivityOriginal($order, $isGroupBuy);
|
|
|
|
|
+ $normalBase = (float)$allocBase - (float)$activityOriginal;
|
|
|
|
|
+ if ($normalBase < 0) {
|
|
|
|
|
+ $normalBase = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $discountTotal = self::toMoney($order->discountAmount ?? 0);
|
|
|
|
|
+ $discountTotal = bcadd($discountTotal, self::toMoney($order->orderReachDiscountPrice ?? 0), 2);
|
|
|
|
|
+ $discountTotal = bcadd($discountTotal, $hbAmount, 2);
|
|
|
|
|
+
|
|
|
|
|
+ $normalPrice = $selectedNormal;
|
|
|
|
|
+ if ($selectedNormal > 0 && $normalBase > 0 && (float)$discountTotal > 0) {
|
|
|
|
|
+ // 与前端 toFixed(2) 同一套四舍五入
|
|
|
|
|
+ $normalPrice = $selectedNormal - ($selectedNormal * (float)$discountTotal / $normalBase);
|
|
|
|
|
+ }
|
|
|
|
|
+ $price = number_format(round($selectedActivity + $normalPrice, 2), 2, '.', '');
|
|
|
|
|
+ if (bccomp($price, '0', 2) < 0) {
|
|
|
|
|
+ $price = '0.00';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $couldRefund = bcsub(self::toMoney($order->orderPrice ?? 0), self::toMoney($order->tkPrice ?? 0), 2);
|
|
|
|
|
+ if (bccomp($couldRefund, '0', 2) < 0) {
|
|
|
|
|
+ $couldRefund = '0.00';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (bccomp($price, $couldRefund, 2) === 1) {
|
|
|
|
|
+ $price = $couldRefund;
|
|
|
|
|
+ }
|
|
|
|
|
+ return round((float)$price, 2);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 整单秒杀/团购商品原价合计,供从优惠分摊基数中剔除
|
|
|
|
|
+ * @param object $order
|
|
|
|
|
+ * @param bool $isGroupBuy 拼团整单则商品总额都是活动价
|
|
|
|
|
+ * @return float
|
|
|
|
|
+ */
|
|
|
|
|
+ protected static function sumOrderActivityOriginal($order, $isGroupBuy)
|
|
|
|
|
+ {
|
|
|
|
|
+ if ($isGroupBuy) {
|
|
|
|
|
+ return (float)self::toMoney($order->goodsPrice ?? 0);
|
|
|
|
|
+ }
|
|
|
|
|
+ $orderSn = $order->orderSn ?? '';
|
|
|
|
|
+ if ($orderSn === '') {
|
|
|
|
|
+ return 0.0;
|
|
|
|
|
+ }
|
|
|
|
|
+ $goodsRows = \bizHd\order\classes\OrderGoodsClass::getAllByCondition(['orderSn' => $orderSn]) ?: [];
|
|
|
|
|
+ $total = 0.0;
|
|
|
|
|
+ foreach ($goodsRows as $goods) {
|
|
|
|
|
+ $seckillGoodsId = is_array($goods)
|
|
|
|
|
+ ? (int)($goods['seckillGoodsId'] ?? 0)
|
|
|
|
|
+ : (int)($goods->seckillGoodsId ?? 0);
|
|
|
|
|
+ if ($seckillGoodsId <= 0) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $num = is_array($goods) ? ($goods['num'] ?? 0) : ($goods->num ?? 0);
|
|
|
|
|
+ $unitPrice = is_array($goods) ? ($goods['unitPrice'] ?? 0) : ($goods->unitPrice ?? 0);
|
|
|
|
|
+ if (!is_numeric($num) || !is_numeric($unitPrice)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $total += (float)$num * (float)$unitPrice;
|
|
|
|
|
+ }
|
|
|
|
|
+ return round($total, 2);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 金额转两位小数字符串,供 bcmath 分摊,避免浮点误差
|
|
|
|
|
+ * @param mixed $value
|
|
|
|
|
+ * @return string
|
|
|
|
|
+ */
|
|
|
|
|
+ protected static function toMoney($value)
|
|
|
|
|
+ {
|
|
|
|
|
+ if ($value === null || $value === '' || !is_numeric($value)) {
|
|
|
|
|
+ return '0.00';
|
|
|
|
|
+ }
|
|
|
|
|
+ return number_format((float)$value, 2, '.', '');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 商家审核通过:执行真实退款 + 红包按过期退回 + 消费回退 + 分销重算
|
|
* 商家审核通过:执行真实退款 + 红包按过期退回 + 消费回退 + 分销重算
|
|
|
* @param object $apply MallRefundApply
|
|
* @param object $apply MallRefundApply
|