Просмотр исходного кода

Merge branch 'redesign‌-260706' into dev

shish 15 часов назад
Родитель
Сommit
9a65bbe270

+ 37 - 9
app-hd/controllers/CustomController.php

@@ -291,6 +291,9 @@ class CustomController extends BaseController
     public function actionDetail()
     {
         $userId = Yii::$app->request->get('userId');
+        if (empty($userId)) {
+            $userId = Yii::$app->request->get('id');
+        }
         $info = CustomClass::getCustomInfo($userId);
         CustomClass::valid($info, $this->shopId);
         util::success($info);
@@ -300,6 +303,9 @@ class CustomController extends BaseController
     public function actionUserDetail()
     {
         $userId = Yii::$app->request->get('userId');
+        if (empty($userId)) {
+            $userId = Yii::$app->request->get('id');
+        }
         $info = UserService::getFullUserInfo($userId);
         UserService::valid($info, $this->sjId);
 
@@ -661,6 +667,35 @@ class CustomController extends BaseController
         util::success($respond);
     }
 
+    /**
+     * 客户余额页小程序码(隔天/无原单返余额海报用)
+     * GET: id=customId
+     */
+    public function actionGetGatheringMiniCode()
+    {
+        $get = Yii::$app->request->get();
+        $id = intval($get['id'] ?? 0);
+        $custom = CustomClass::getById($id, true);
+        if (empty($custom)) {
+            util::fail('没有找到客户');
+        }
+        if (intval($custom->shopId ?? 0) !== intval($this->shopId)) {
+            util::fail('不是你的客户');
+        }
+        $merchant = \bizHd\wx\classes\WxOpenClass::getMallWxInfo();
+        $page = 'pages/home/user';
+        $ptStyle = \common\components\dict::getDict('ptStyle', 'mall');
+        $scene = 's=' . $this->shopId;
+        $envVersion = \common\components\miniUtil::normalizeMiniEnvVersion($get['env_version'] ?? 'release');
+        $miniCode = \common\components\miniUtil::generateUnlimitedMiniCode($merchant, $page, $scene, $ptStyle, $envVersion);
+        $imageUrl = \common\components\imgUtil::groupImg($miniCode);
+        util::success([
+            'imgUrl' => $imageUrl,
+            'shopId' => $this->shopId,
+            'balance' => bcadd((string)($custom->balance ?? '0'), '0', 2),
+        ]);
+    }
+
     //今日访客
     public function actionGetVisit()
     {
@@ -710,19 +745,12 @@ class CustomController extends BaseController
         $c = CustomClass::getLockById($customId);
 
         // 1.重新统计客户的消费总额
-        $orderList = OrderClass::getAllByCondition(['customId' => $customId], null, 'id, forward, actPrice');
+        $orderList = OrderClass::getAllByCondition(['customId' => $customId], null, 'id, actPrice');
 
         $buyAmount = 0;
         if (!empty($orderList)) {
             foreach ($orderList as $order) {
-                if ($order['forward'] == 0) { //0 常规订单
-                    $buyAmount = bcadd($buyAmount, $order['actPrice'], 2);
-                } elseif ($order['forward'] == 1) { //1 售后付款单(红冲)
-                    $buyAmount = bcsub($buyAmount, $order['actPrice'], 2);
-                } else {
-                    Yii::info('订单类型不存在');
-                    continue;
-                }
+                $buyAmount = bcadd($buyAmount, $order['actPrice'], 2);
             }
         }
         $c->buyAmount = $buyAmount;

+ 1 - 4
app-hd/controllers/IntraCityController.php

@@ -338,7 +338,7 @@ class IntraCityController extends BaseController
         util::checkRepeatCommit($adminId, 4);
 
         $orderId = intval($post['orderId']);
-        $field = 'mainId, status, sendStatus, repeat, forward, orderSn, userId, actPrice, sendNum, salt';
+        $field = 'mainId, status, sendStatus, repeat, orderSn, userId, actPrice, sendNum, salt';
         $order = OrderClass::getById($orderId, false, $field);
         if (empty($order)) {
             util::fail('没有找到订单');
@@ -358,9 +358,6 @@ class IntraCityController extends BaseController
         if ($order['repeat'] == 1) {
             util::fail('收款码订单不支持跑腿');
         }
-        if ($order['forward'] == 1) {
-            util::fail('售后付款单不支持跑腿');
-        }
 
         $packageNum = $post['packageNum'] ?? 0;
         if ($packageNum <= 0) {

+ 3 - 38
app-hd/controllers/OrderController.php

@@ -17,7 +17,6 @@ use bizHd\distribution\classes\DistributionCommissionClass;
 use bizHd\goods\classes\GoodsClass;
 use bizHd\merchant\services\ShopService;
 use bizHd\order\classes\OrderClass;
-use bizHd\order\classes\OrderForwardClass;
 use bizHd\order\classes\OrderItemClass;
 use bizHd\order\classes\OrderSendClass;
 use hd\models\order\CreateOrderForm;
@@ -389,9 +388,6 @@ class OrderController extends BaseController
         if (empty($order)) {
             util::fail('没有找到订单');
         }
-        if ($order->forward == 1) {
-            util::fail('红冲订单不能打印');
-        }
         OrderClass::valid($order, $this->mainId);
         OrderClass::onlinePrint($order, true);
         util::complete();
@@ -794,6 +790,8 @@ class OrderController extends BaseController
         if ($version < 3) {
             util::fail('请升级版本,或用小程序');
         }
+        // 不再支持红冲负单字段
+        $post['forward'] = 0;
 
         //默认情况开单员工和收款员工是同个人
         $staffId = $this->shopAdminId ?? 0;
@@ -843,18 +841,6 @@ class OrderController extends BaseController
 
         $hasPay = $post['hasPay'] ?? dict::getDict('hasPay', 'unPay');
         $post['sendType'] = $post['sendType'] ?? 1;
-        $forward = $post['forward'] ?? 0;
-        if ($forward == 1) {
-            if ($hasPay != 1) {
-                util::fail('红冲订单只能走线下转账');
-            }
-            $historyDate = $post['historyDate'] ?? '';
-            if (!empty($historyDate)) {
-                if ($historyDate != date("Y-m-d")) {
-                    util::fail('红冲订单不能修改账单日期,只能今天');
-                }
-            }
-        }
 
         $productJson = $post['product'] ?? '';
         $productList = [];
@@ -1047,10 +1033,6 @@ class OrderController extends BaseController
                 $hb->save();
             }
 
-            $staff = $this->shopAdmin;
-            //售后开付款单,库存要变化,关系要建立,多处需要同步修改 sh_pay_change ssh
-            OrderClass::shPayChange($return, $shop, $staff);
-
             //打印小票和语音播报等使用
             $id = $return->id ?? 0;
             $order = OrderClass::getById($id, true);
@@ -1443,25 +1425,8 @@ class OrderController extends BaseController
         $detail = OrderClass::getFullInfo($id);
         OrderService::valid($detail, $this->mainId);
 
-        $forwardMap = [];
-        if ($detail['forward'] == 1) {
-            //当前单如果已经是红冲订单,则取出其对应的原单
-            $map = OrderForwardClass::getByCondition(['shOrderId' => $id], true);
-            if (!empty($map)) {
-                $originOrderId = $map->orderId ?? 0;
-                $forwardMap['orderId'] = $originOrderId;
-            }
-        }
-        $detail['forwardMap'] = $forwardMap;
-
-        $shOrderMap = [];
-        if ($detail['hasForward'] == 1) {
-            $shOrderMap = OrderForwardClass::getAllByCondition(['orderId' => $id], null, '*');
-        }
-        $detail['shOrderMap'] = $shOrderMap;
-
         $main = $this->main;
-        //是否走红冲订单模式判断 ssh
+        // 是否须走隔天售后(前端提示)
         $ret = OrderClass::ifShPay($detail, $main, 0);
         $shAddPay = $ret['shAddPay'];
         $shAddPayReason = $ret['shAddPayReason'];

+ 2 - 10
app-hd/controllers/RechargeController.php

@@ -151,16 +151,8 @@ class RechargeController extends BaseController
 
             $shOrderSn = '';
             if (!empty($shOrderId)) {
-                $shOrder = OrderClass::getById($shOrderId, true);
-                if ($shOrder->forward != 1) {
-                    util::fail('不是售后付款单');
-                }
-                if ($shOrder->mainId != $this->mainId) {
-                    util::fail('不是你的单');
-                }
-                //判断是否充值过了
-                \bizHd\recharge\classes\RechargeClass::checkShOrderRecharge($shOrder);
-                $shOrderSn = $shOrder->orderSn ?? '';
+                // 已停用红冲负单关联充值
+                util::fail('已停用红冲单充值,请使用售后/退款功能');
             }
 
             $refundOrderId = $post['refundOrderId'] ?? 0;

+ 177 - 140
app-hd/controllers/RefundController.php

@@ -9,7 +9,7 @@ use bizHd\distribution\classes\DistributionCommissionClass;
 use bizHd\hb\classes\HbClass;
 use bizHd\member\classes\MemberLevelClass;
 use bizHd\order\classes\OrderClass;
-use bizHd\order\services\OrderService;
+use bizHd\refund\classes\HdNextDayRefundClass;
 use bizHd\refund\classes\HdRefundClass;
 use bizHd\refund\classes\HdRefundGoodsClass;
 use bizHd\refund\classes\HdRefundItemClass;
@@ -47,7 +47,7 @@ class RefundController extends BaseController
         util::success(['status' => $status], $msg);
     }
 
-    //列表
+    //列表(含无原单 orderId=0;sameDay 筛选)
     public function actionList()
     {
         $get = Yii::$app->request->get();
@@ -57,6 +57,19 @@ class RefundController extends BaseController
         if (!empty($orderSn)) {
             $where['orderSn'] = $orderSn;
         }
+        $customId = intval($get['customId'] ?? 0);
+        if ($customId > 0) {
+            $where['customId'] = $customId;
+        }
+        // sameDay:-1全部 1当天 0隔天/无原单
+        $sameDay = isset($get['sameDay']) ? intval($get['sameDay']) : -1;
+        if ($sameDay === 0 || $sameDay === 1) {
+            $where['sameDay'] = $sameDay;
+        }
+        // freeOnly=1 仅无原单
+        if (intval($get['freeOnly'] ?? 0) === 1) {
+            $where['orderId'] = 0;
+        }
         $list = HdRefundClass::getOrderList($where);
         util::success($list);
     }
@@ -125,10 +138,6 @@ class RefundController extends BaseController
                 util::fail('历史订单,不能发起退款哦');
             }
 
-            if ($order->forward == 1) {
-                util::fail('负数订单不能发起售后');
-            }
-
             if ($refundType == 1) {
                 //商品花材结构 property 0花束 1花材
                 //[{productId:0,num:1,unitType:0,unitPrice:12,unitName:'扎',property:1}]
@@ -159,144 +168,51 @@ class RefundController extends BaseController
             $post['shopAdminName'] = $adminName;
 
             $main = $this->main;
-            //判断是否开负数订单
-            $ret = OrderClass::ifShPay($order, $main, $post['price']);
-            $shAddPay = $ret['shAddPay'] ?? 0;
-
-            if ($shAddPay == 1) {
-                $customId = $order->customId ?? 0;
-                $custom = CustomClass::getById($customId, true);
-                if (empty($custom)) {
-                    util::fail('没有客户哈!');
-                }
-                //线下付款
-                $hasPay = 1;
-                $shopId = $order->shopId;
-                $staffId = $this->shopAdminId;
-                $staffName = $this->shopAdminName;
-                $forwardStock = $refundType == 1 ? 0 : 1;
-                $refundPrice = $post['price'] ?? 0;
-
-                $hasItem = false;
-                $hasBouquet = false;
-                // property 0花束 1花材
-                $orderProductList = [];
-                foreach ($productList as $thisInfo) {
-                    $num = $thisInfo['num'] ?? 0;
-                    $unitType = $thisInfo['unitType'] ?? 0;
-                    $property = $thisInfo['property'] ?? 0;
-                    $unitPrice = $thisInfo['unitPrice'] ?? 0;
-                    $productId = $thisInfo['productId'] ?? 0;
-                    $orderProductList[] = [
-                        'productId' => $productId,
-                        'unitPrice' => $unitPrice,
-                        'remark' => '',
-                        'property' => $property,
-                        'unitType' => $unitType,
-                        'num' => $num,
-                    ];
-                    if ($property == 0) {
-                        $hasBouquet = true;
-                    }
-                    if ($property == 1) {
-                        $hasItem = true;
-                    }
-                }
-                $orderType = 0;
-                if ($hasBouquet) {
-                    $orderType = 1;
-                }
-                if ($hasItem) {
-                    $orderType = 2;
-                }
-                if ($hasItem && $hasBouquet) {
-                    $orderType = 3;
-                }
-
-                $sjId = $order->sjId;
-                $mainId = $order->mainId;
-                $deadline = time() + 8640000;
-                $hdId = $order->hdId;
-                $hdName = $order->hdName;
-                $customNamePy = $order->customNamePy;
-                $customName = $order->customName;
-                $orderData = [
-                    'hasPay' => 1,
-                    'payWay' => 0,
-                    'shopId' => $shopId,
-                    'customId' => $customId,
-                    'customName' => $customName,
-                    'customNamePy' => $customNamePy,
-                    'product' => $orderProductList,
-                    'needPrint' => 2,
-                    'getStaffId' => $staffId,
-                    'getStaffName' => $staffName,
-                    'shopAdminId' => $staffId,
-                    'shopAdminName' => $staffName,
-                    'dealPrice' => 1,
-                    'forward' => 1,
-                    'forwardStock' => $forwardStock,
-                    'modifyPrice' => $refundPrice,
-                    'orderType' => $orderType,
-                    'sjId' => $sjId,
-                    'mainId' => $mainId,
-                    'deadline' => $deadline,
-                    'staffId' => $staffId,
-                    'staffName' => $staffName,
-                    'flowerNum' => 0,
-                    'fromType' => 1,
-                    'hdId' => $hdId,
-                    'hdName' => $hdName,
-                    'sendType' => 1,
-                    'sendSide' => 2,
-                    'labourCost' => 0,
-                ];
-                //走负数订单,多处调用这个方法,注意同步修改,搜索关键词 createHdNewOrder
-                $returnOrder = OrderService::createHdOrder($orderData, $custom, $hasPay);
-                $shop = $this->shop;
-                $staff = $this->shopAdmin;
-                //售后开付款单,库存要变化,关系要建立,多处需要同步修改 sh_pay_change ssh
-                OrderClass::shPayChange($returnOrder, $shop, $staff, $order);
-                $transaction->commit();
-                // 负数售后单:原单金额未走 addRefund,仍尝试按原单重算分佣(无变化则内部忽略)
-                DistributionCommissionClass::tryCalcDistributionAfterPay((int)$id);
-                $orderId = $returnOrder->id;
-                util::success(['forward' => 1, 'orderId' => $orderId]);
+            // 非付款日 / 已结清等:强制隔天售后写入(sameDay=0)
+            if (HdNextDayRefundClass::mustUseNextDay($order, $main)) {
+                $post['sameDay'] = HdRefundClass::SAME_DAY_NO;
             } else {
-                //走正常售后
-                HdRefundService::addRefund($post, $order);
+                $ret = OrderClass::ifShPay($order, $main, $post['price']);
+                if (($ret['shAddPay'] ?? 0) == 1) {
+                    $post['sameDay'] = HdRefundClass::SAME_DAY_NO;
+                }
             }
 
-            //售后之后,累计消费、积分、成长值回退 -- 表:xhCustom -> buyAmount、integral、growth,xhHd -> expendAmount
-            $custom = CustomClass::getLockById($order->customId);
-            if (!empty($custom)) {
-                $custom->buyAmount = bcsub($custom->buyAmount, $post['price'], 2);
-                $customName = $custom->name ?? $order->customName ?? '';
-                $orderSnText = $order->orderSn ?? '';
-                $shopAdminName = $this->shopAdminName;
-                $eventPrefix = $customName;
-                if ($eventPrefix === '' && $orderSnText !== '') {
-                    $eventPrefix = '客户';
-                }
-                $custom->eventRemark = $eventPrefix . '售后' . $orderSnText;
-                $custom->relateId = $order->id;
-                $custom->relateType = 1; //关联零售订单
-                $custom->staffId = $this->shopAdminId;
-                $custom->staffName = $shopAdminName;
-                $custom->mainId = $this->mainId;
-                $custom->save();
-
-                $hd = HdClass::getLockById($custom->hdId);
-                if (!empty($hd)) {
-                    $hd->expendAmount = bcsub($hd->expendAmount, $post['price'], 2);
-                    $hd->save();
+            $refund = HdRefundService::addRefund($post, $order);
+            $sameDay = intval($refund->sameDay ?? HdRefundClass::SAME_DAY_YES);
+
+            // 当天售后:累计消费在此回退;隔天已在 applyHdAmountAndFund 处理
+            if ($sameDay === HdRefundClass::SAME_DAY_YES) {
+                $custom = CustomClass::getLockById($order->customId);
+                if (!empty($custom)) {
+                    $custom->buyAmount = bcsub($custom->buyAmount, $post['price'], 2);
+                    $customName = $custom->name ?? $order->customName ?? '';
+                    $orderSnText = $order->orderSn ?? '';
+                    $shopAdminName = $this->shopAdminName;
+                    $eventPrefix = $customName;
+                    if ($eventPrefix === '' && $orderSnText !== '') {
+                        $eventPrefix = '客户';
+                    }
+                    $custom->eventRemark = $eventPrefix . '售后' . $orderSnText;
+                    $custom->relateId = $order->id;
+                    $custom->relateType = 1;
+                    $custom->staffId = $this->shopAdminId;
+                    $custom->staffName = $shopAdminName;
+                    $custom->mainId = $this->mainId;
+                    $custom->save();
+
+                    $hd = HdClass::getLockById($custom->hdId);
+                    if (!empty($hd)) {
+                        $hd->expendAmount = bcsub($hd->expendAmount, $post['price'], 2);
+                        $hd->save();
+                    }
                 }
             }
 
             //红包退款
             $refundHb = $post['refundHb'] ?? 0;
             $hbId = $order->hbId;
-            if ($refundHb == 1 && $hbId > 0) { // 不使用 $hbId =! 0,因为要用负数来表示红包已取消
+            if ($refundHb == 1 && $hbId > 0) {
                 $updatedOrder = OrderClass::getById($id, true, 'id, actPrice, hbId, hbAmount');
                 if ($updatedOrder->actPrice <= 0.00) {
                     hbClass::hbBack($updatedOrder);
@@ -304,22 +220,143 @@ class RefundController extends BaseController
             }
             $transaction->commit();
 
-            // 售后落库、红包回退等均已提交后再重算分佣(与 tkPrice/actPrice 一致)
             DistributionCommissionClass::tryCalcDistributionAfterPay((int)$id);
 
-            //制作单取消通知
             $shopId = $this->shopId ?? 0;
             $shopExt = ShopExtClass::getByCondition(['shopId' => $shopId], true);
             ShopExtClass::orderCancelRemind($shopExt, $order);
 
-            util::complete();
+            $refund = HdRefundClass::getById($refund->id, true);
+            $payload = HdNextDayRefundClass::buildFundResultPayload($refund);
+            // 需选手选资金落地时返回详情,前端跳转 forwardResult
+            if (!empty($payload['needFundAction'])) {
+                util::success($payload);
+            }
+            util::success($payload);
 
         } catch (\Exception $exception) {
             $transaction->rollBack();
             Yii::info("出错了:" . $exception->getMessage());
-            util::fail('操作失败');
+            util::fail($exception->getMessage() ?: '操作失败');
+        }
+    }
+
+    /**
+     * 无原单退款(工作台「退款」)
+     */
+    public function actionCreateFreeRefund()
+    {
+        $shopAdmin = $this->shopAdmin;
+        if (!isset($shopAdmin['super']) || $shopAdmin['super'] != 1) {
+            util::fail('超管才能操作退款');
+        }
+        $post = Yii::$app->request->post();
+        $customId = intval($post['customId'] ?? 0);
+        util::checkRepeatCommit('hd_free_refund_' . $this->mainId . '_' . $customId, 5);
+
+        $connection = Yii::$app->db;
+        $transaction = $connection->beginTransaction();
+        try {
+            $returnWay = intval($post['returnWay'] ?? -1);
+            if ($returnWay === 1) {
+                $refundType = HdRefundClass::REFUND_TYPE_MONEY_GOOD;
+            } elseif ($returnWay === 0) {
+                $refundType = HdRefundClass::REFUND_TYPE_MONEY;
+            } else {
+                $refundType = intval($post['refundType'] ?? HdRefundClass::REFUND_TYPE_MONEY_GOOD);
+            }
+            $post['refundType'] = $refundType;
+            if ($refundType == HdRefundClass::REFUND_TYPE_MONEY_GOOD) {
+                $productJson = $post['product'] ?? '';
+                if (empty($productJson)) {
+                    util::fail('请选择花材');
+                }
+                $productList = is_array($productJson) ? $productJson : json_decode($productJson, true);
+                if (!is_array($productList) || empty($productList)) {
+                    util::fail('请选择花材哦');
+                }
+                $post['product'] = $productList;
+            } else {
+                $productJson = $post['product'] ?? '';
+                if (!empty($productJson)) {
+                    $productList = is_array($productJson) ? $productJson : json_decode($productJson, true);
+                    $post['product'] = is_array($productList) ? $productList : [];
+                } else {
+                    $post['product'] = [];
+                }
+            }
+            $post['price'] = $post['price'] ?? 0;
+            if ($post['price'] <= 0) {
+                util::fail('退款金额不能小于0');
+            }
+            $post['shopId'] = $this->shopId;
+            $post['sjId'] = $this->sjId;
+            $post['shopAdminId'] = $this->shopAdminId;
+            $post['mainId'] = $this->mainId;
+            $post['shopAdminName'] = $shopAdmin['name'] ?? '';
+            $result = HdRefundService::createFreeRefund($post);
+            $transaction->commit();
+            util::success($result);
+        } catch (\Exception $exception) {
+            if ($transaction->isActive) {
+                $transaction->rollBack();
+            }
+            Yii::info('无原单退款出错:' . $exception->getMessage());
+            util::fail($exception->getMessage() ?: '操作失败');
+        }
+    }
+
+    /**
+     * 确认已线下原路退回
+     */
+    public function actionMarkHasReturn()
+    {
+        $post = Yii::$app->request->post();
+        $id = intval($post['id'] ?? 0);
+        $refund = HdRefundClass::getById($id, true);
+        HdRefundClass::valid($refund, $this->mainId);
+        $connection = Yii::$app->db;
+        $transaction = $connection->beginTransaction();
+        try {
+            $refund = HdNextDayRefundClass::markHasReturn($refund);
+            $transaction->commit();
+            util::success(HdNextDayRefundClass::buildFundResultPayload($refund));
+        } catch (\Exception $exception) {
+            if ($transaction->isActive) {
+                $transaction->rollBack();
+            }
+            util::fail($exception->getMessage() ?: '操作失败');
+        }
+    }
+
+    /**
+     * 不想线下转账:返充到客户余额
+     */
+    public function actionReturnBalance()
+    {
+        $post = Yii::$app->request->post();
+        $id = intval($post['id'] ?? 0);
+        $refund = HdRefundClass::getById($id, true);
+        HdRefundClass::valid($refund, $this->mainId);
+        $connection = Yii::$app->db;
+        $transaction = $connection->beginTransaction();
+        try {
+            $order = null;
+            $orderId = intval($refund->orderId ?? 0);
+            if ($orderId > 0) {
+                $order = OrderClass::getById($orderId, true);
+            }
+            $ret = HdNextDayRefundClass::applyReturnBalance($refund, $order);
+            $transaction->commit();
+            $payload = HdNextDayRefundClass::buildFundResultPayload($ret['refund']);
+            $payload['customBalance'] = $ret['customBalance'];
+            util::success($payload);
+        } catch (\Exception $exception) {
+            if ($transaction->isActive) {
+                $transaction->rollBack();
+            }
+            util::fail($exception->getMessage() ?: '操作失败');
         }
-        util::complete();
     }
 
     public function actionAllRefund()

+ 1 - 18
app-hd/models/order/CreateOrderForm.php

@@ -21,7 +21,6 @@ class CreateOrderForm extends BaseForm
     public $bookMobile;
     public $hasPay;
     public $sendType;
-    public $forward;
     public $historyDate;
     public $modifyPrice;
     public $product;
@@ -62,12 +61,11 @@ class CreateOrderForm extends BaseForm
             
             [['fromType'], 'validateFromType'],
             [['receiveMobile', 'bookMobile'], 'validateMobile'],
-            [['forward'], 'validateForward'],
             [['zjGathering'], 'validateZjGathering'],
             [['unitGoodsPrice'], 'validateUnitGoodsPrice'],
             [['product'], 'validateProduct'],
 
-            [['flowerNum', 'version', 'shopAdminId', 'getStaffId', 'groupId', 'fromType', 'customId', 'hasPay', 'sendType', 'forward', 'zjGathering', 'reduceStock', 'needWork', 'isCashier', 'callErrand', 'payWay', 'anonymity',  'needPrint', 'hbId'], 'integer'],
+            [['flowerNum', 'version', 'shopAdminId', 'getStaffId', 'groupId', 'fromType', 'customId', 'hasPay', 'sendType', 'zjGathering', 'reduceStock', 'needWork', 'isCashier', 'callErrand', 'payWay', 'anonymity',  'needPrint', 'hbId'], 'integer'],
             [['modifyPrice', 'lsGoodsPrice', 'dealPrice', 'deliveryPrice', 'deliveryDistance', 'weight', 'hbAmount'], 'number'],
             [['shopAdminName', 'getStaffName', 'bookName', 'receiveMobile', 'bookMobile', 'historyDate', 'product', 'deliveryPlatform', 'deliveryBracketContent', 'pickupTime', 'remark', 'deliveryRemark', 'reachDate', 'reachPeriod', 'cardInfo'], 'string'],
             [['unitGoodsPrice', 'deliveryAllParams'], 'safe'],
@@ -107,21 +105,6 @@ class CreateOrderForm extends BaseForm
         }
     }
 
-    public function validateForward($attribute, $params)
-    {
-        if (!$this->hasErrors() && $this->forward == 1) {
-            if ($this->hasPay != 1) { // 1 is unPay? No, unPay is usually 0 or 1. Controller says: if ($hasPay != 1) fail('售后付款单只能走线下转账');
-                // Need to check what 1 means. Controller: if ($hasPay != 1).
-                // Let's assume 1 is the value expected.
-                 $this->addError('hasPay', '售后付款单只能走线下转账');
-            }
-            
-            if (!empty($this->historyDate) && $this->historyDate != date("Y-m-d")) {
-                $this->addError('historyDate', '售后付款单不能修改账单日期,只能今天');
-            }
-        }
-    }
-    
     public function validateZjGathering($attribute, $params)
     {
         if (!$this->hasErrors() && $this->zjGathering == 1) {

+ 67 - 0
biz-hd/custom/classes/CustomClass.php

@@ -618,6 +618,71 @@ class CustomClass extends BaseClass
         }
     }
 
+    /**
+     * 隔天/无原单售后返充余额(不依赖销售单 balanceTk 拆分)
+     * 用途:成功页「返充到客户余额」;无原单时 $order 可为空。
+     */
+    public static function nextDayRefundReturnBalance($custom, $amount, $refund, $order = null)
+    {
+        $amount = bcadd((string)$amount, '0', 2);
+        if (bccomp($amount, '0', 2) <= 0) {
+            util::fail('返充金额必须大于0');
+        }
+        $hdId = $custom->hdId ?? 0;
+        $hd = HdClass::getLockById($hdId);
+        if (empty($hd)) {
+            util::fail('花店信息缺失,编号5363');
+        }
+        $customBalance = bcadd((string)($custom->balance ?? '0'), $amount, 2);
+        $custom->balance = $customBalance;
+        $custom->isDebt = bccomp($customBalance, '0', 2) < 0 ? 1 : 0;
+        $custom->save();
+        $hdBalance = bcadd((string)($hd->balance ?? '0'), $amount, 2);
+        $hd->balance = $hdBalance;
+        if (bccomp($hdBalance, '0', 2) < 0) {
+            $hd->debt = 2;
+        }
+        $hd->save();
+        if (floatval($customBalance) !== floatval($hdBalance)) {
+            util::fail('余额错误,请联系管理员,编号' . $hdId);
+        }
+
+        $refundSn = $refund->refundSn ?? '';
+        $orderId = intval($refund->orderId ?? 0);
+        $saleOrderSn = !empty($order) ? ($order->orderSn ?? '') : '';
+        if ($saleOrderSn === '' && $orderId > 0) {
+            $saleOrderSn = $refund->orderSn ?? '';
+        }
+        // 无原单流水只挂售后单号,避免「原单:」空串误导
+        $event = ($orderId > 0 && $saleOrderSn !== '')
+            ? "隔天售后返充余额,售后单:{$refundSn},原单:{$saleOrderSn}"
+            : "无原单退款返充余额,售后单:{$refundSn}";
+        $capitalType = dict::getDict('capitalType', 'hdRefund', 'id');
+        $change = [
+            'customId' => $custom->id ?? 0,
+            'customName' => $custom->name ?? '',
+            'relateId' => $refund->id ?? 0,
+            'relateSn' => $refundSn,
+            'onlinePay' => 1,
+            'capitalType' => $capitalType,
+            'amount' => $amount,
+            'balance' => $customBalance,
+            'io' => 1,
+            'side' => 0,
+            'payWay' => 2,
+            'event' => $event,
+            'staffId' => $refund->shopAdminId ?? 0,
+            'staffName' => $refund->shopAdminName ?? '',
+            'shopId' => $refund->shopId ?? 0,
+            'mainId' => $refund->mainId ?? 0,
+            'refundOrderId' => $orderId,
+            'refundOrderSn' => $saleOrderSn,
+            'remark' => '',
+        ];
+        BalanceChangeClass::add($change, true);
+        return $customBalance;
+    }
+
     //欠款和余额支付的售后 ssh 20250803
     public static function skRefundDebtAmountReduce($custom, $amount, $refund, $payWay)
     {
@@ -822,6 +887,7 @@ class CustomClass extends BaseClass
         $name = $user->name ?? '';
         $mobile = $user->mobile ?? '';
         $shopId = $shop->id ?? 0;
+        $mainId = $
         $hdAvatar = $shop->avatar ?? '';
         $sjId = $shop->sjId ?? 0;
         $sjName = $shop->merchantName ?? '';
@@ -837,6 +903,7 @@ class CustomClass extends BaseClass
                 'userId' => $userId,
                 'sjId' => $sjId,
                 'shopId' => $shopId,
+                'mainId'=>$mainId,
                 'name' => $name,
                 'originName' => $name,
                 'py' => $py,

+ 15 - 129
biz-hd/order/classes/OrderClass.php

@@ -9,7 +9,6 @@ use biz\shop\classes\ShopClass;
 use biz\shop\classes\ShopExtClass;
 use biz\stat\classes\StatOrderCountClass;
 use biz\stock\classes\GoodsStockRecordClass;
-use bizGhs\order\classes\CheckOrderClass;
 use bizGhs\product\classes\ProductClass;
 use bizGhs\stock\classes\StockRecordClass;
 use bizHd\custom\classes\CustomClass;
@@ -82,101 +81,9 @@ class OrderClass extends BaseClass
         20000 => [90, 100],
     ];
 
-    //因为开负数订单时引导库存变化 ssh 20250731
-    public static function shPayChange($order, $shop, $staff, $originOrder = null)
-    {
-        $forward = $order['forward'] ?? 0;
-        if ($forward == 0) {
-            return false;
-        }
-        $forwardStock = $order['forwardStock'] ?? 0;
-        $orderSn = $order['orderSn'] ?? '';
-        $orderItemList = OrderItemClass::getAllByCondition(['orderSn' => $orderSn], null, '*');
-        $orderGoodsList = OrderGoodsClass::getAllByCondition(['orderSn' => $orderSn], null, '*');
-        if (empty($orderItemList) && empty($orderGoodsList)) {
-            return false;
-        }
-        $shopId = $shop->id ?? 0;
-        $sjId = $shop->sjId ?? 0;
-        $adminId = $staff->adminId;
-        $staffName = $staff->name ?? '';
-        $mainId = $shop->mainId;
-        if (!empty($orderItemList)) {
-            $itemInfo = [];
-            $ids = array_column($orderItemList, 'itemId');
-            $infoList = ProductClass::getAllByCondition(['id' => ['in', $ids]], null, '*', 'id');
-            foreach ($orderItemList as $orderItem) {
-                $unitType = $orderItem['unitType'] ?? 0;
-                $num = $orderItem['num'] ?? 0;
-                $productId = $orderItem['itemId'] ?? 0;
-                $info = $infoList[$productId] ?? [];
-                $stock = $info['stock'] ?? 0;
-                $ratio = $info['ratio'] ?? 20;
-                $map = ProductClass::formatStock($stock, $ratio);
-                $bigNum = $map['bigNum'] ?? 0;
-                $smallNum = $map['smallNum'] ?? 0;
-                $num = $forwardStock == 0 ? $num * 2 : $num;
-                if ($unitType == 0) {
-                    $bigNum = $bigNum + $num;
-                } else {
-                    $smallNum = $smallNum + $num;
-                }
-                $itemInfo[] = ['productId' => $productId, 'bigNum' => $bigNum, 'smallNum' => $smallNum];
-            }
-            $remark = $forwardStock == 0 ? '开售后付款单,退货并退款' : '开售后付款单,只退款';
-            $itemData = [
-                'remark' => $remark,
-                'itemInfo' => $itemInfo,
-                'sjId' => $sjId,
-                'shopId' => $shopId,
-                'adminId' => $adminId,
-                'staffName' => $staffName,
-            ];
-            CheckOrderClass::opOrder($itemData);
-        }
-        if (!empty($orderGoodsList)) {
-            foreach ($orderGoodsList as $orderGoods) {
-                $goodsId = $orderGoods['goodsId'] ?? 0;
-                $num = $orderGoods['num'] ?? 0;
-                $num = $forwardStock == 0 ? $num * 2 : $num;
-
-                $ret = \bizHd\goods\classes\GoodsClass::addStock($goodsId, $mainId, $num);
-                $oldStock = $ret['oldStock'] ?? 0;
-                $newStock = $ret['newStock'] ?? 0;
-                $recordData = [];
-                $recordData['sjId'] = $sjId;
-                $recordData['shopId'] = $shopId;
-                $recordData['mainId'] = $mainId;
-                $recordData['orderSn'] = '';
-                $recordData['goodsId'] = $goodsId;
-                $recordData['goodsNum'] = $num;
-                $recordData['oldStock'] = $oldStock;
-                $recordData['newStock'] = $newStock;
-                $recordData['relateName'] = $staffName;
-                GoodsStockRecordClass::pdGoods($recordData);
-
-            }
-        }
-        if (is_object($order) && !empty($originOrder)) {
-            //建立关系
-            $originOrder->hasForward = 1;
-            $originOrder->save();
-            $orderId = $originOrder->id ?? 0;
-            $orderSn = $originOrder->orderSn ?? '';
-            $shOrderId = $order->id ?? 0;
-            $shOrderSn = $order->orderSn ?? '';
-            $map = ['orderId' => $orderId, 'orderSn' => $orderSn, 'shOrderId' => $shOrderId, 'shOrderSn' => $shOrderSn];
-            OrderForwardClass::add($map, true);
-        }
-        return true;
-    }
-
     /**
-     * @param $order
-     * @param $mainId
-     * @return array
-     * $shAddPay 0走正常流程售后,1走售后付款单
-     * $shAddPayReason 走售后付款单的原因:0、售后金额大于剩余欠款(剩余欠款可能没有了,可能还有);1、可售后时间到了
+     * 是否必须走隔天售后(挂账已结清/超额、或可售后时限已过)
+     * @return array{shAddPay:int,shAddPayReason:int} shAddPay=1 表示隔天售后;reason:0欠款已结清/超额,1可售后时限已过
      */
     public static function ifShPay($order, $main, $refundAmount = 0)
     {
@@ -186,11 +93,11 @@ class OrderClass extends BaseClass
         if ($payStatus == 1) {
             if ($order['payWay'] == 3) {
                 if ($order['remainDebtPrice'] <= 0) {
-                    //不管是否要售后,没有剩余欠款了,只能走开负数订单模式
+                    // 挂账已结清:走隔天售后(写 nextRefundNum/nextDayTkPrice)
                     $shAddPay = 1;
                 } else {
                     if ($refundAmount > 0 && $refundAmount > $order['remainDebtPrice']) {
-                        //如果需要售后,并且售后金额大于剩余欠款,走开负数订单模式
+                        // 售后金额大于剩余欠款:走隔天售后
                         $shAddPay = 1;
                     }
                 }
@@ -198,7 +105,7 @@ class OrderClass extends BaseClass
             $cRet = OrderClass::couldRefund($order, $main);
             $could = $cRet['could'];
             if (!$could) {
-                //可售后时间到了
+                // 可售后时限已过:走隔天售后
                 $shAddPay = 1;
                 $shAddPayReason = 1;
             }
@@ -507,20 +414,13 @@ class OrderClass extends BaseClass
             if ($eventPrefix === '' && $orderSnText !== '') {
                 $eventPrefix = '客户';
             }
-            if ($order->forward == 0) { //0 常规订单
-                $myCustom->recentExpend = date("Y-m-d H:i:s");
-                $myCustom->visitTime = date("Y-m-d H:i:s");
-                if ($payWay == dict::getDict('payWay', 'balancePay')) {
-                    $myCustom->recordGrowthAndIntegral = false;
-                }
-                $myCustom->buyAmount = bcadd($myCustom->buyAmount, $order->realPrice, 2);
-                $myCustom->eventRemark = trim($eventPrefix . '下单' . $orderSnText);
-            } elseif ($order->forward == 1) { //1 售后付款单(红冲)
-                $myCustom->buyAmount = bcsub($myCustom->buyAmount, $order->realPrice, 2);
-                $myCustom->eventRemark = trim($eventPrefix . '红冲' . $orderSnText);
-            } else {
-                util::fail('订单类型不存在');
+            $myCustom->recentExpend = date("Y-m-d H:i:s");
+            $myCustom->visitTime = date("Y-m-d H:i:s");
+            if ($payWay == dict::getDict('payWay', 'balancePay')) {
+                $myCustom->recordGrowthAndIntegral = false;
             }
+            $myCustom->buyAmount = bcadd($myCustom->buyAmount, $order->realPrice, 2);
+            $myCustom->eventRemark = trim($eventPrefix . '下单' . $orderSnText);
             $myCustom->relateId = $orderId;
             $myCustom->relateType = 1; //关联的表或类别:0.无关联 1.零售订单
             $myCustom->staffId = $shopAdminId;
@@ -529,15 +429,8 @@ class OrderClass extends BaseClass
             $myCustom->save();
 
             $hd = HdClass::getLockById($myCustom->hdId);
-            //$hd = HdClass::getByCondition(['shopId'=>$order->shopId, 'userId'=>$order->userId], true, false, 'id,customId,expendAmount');
             if (!empty($hd)) {
-                if ($order->forward == 0) { //0 常规订单
-                    $hd->expendAmount = bcadd($hd->expendAmount, $order->realPrice, 2);
-                } elseif ($order->forward == 1) { //1 售后付款单(红冲)
-                    $hd->expendAmount = bcsub($hd->expendAmount, $order->realPrice, 2);
-                } else {
-                    util::fail('订单类型不存在');
-                }
+                $hd->expendAmount = bcadd($hd->expendAmount, $order->realPrice, 2);
                 $hd->save();
             }
         }
@@ -679,14 +572,11 @@ class OrderClass extends BaseClass
         if (intval($order->groupBuyId ?? 0) > 0) {
             GroupBuyClass::onOrderPaid($order);
         }
-        // 常规零售单支付成功后计分销提成(无分销关系等场景内部抛错,不阻断付款)
-        if ((int)($order->forward ?? 0) === 0) {
-            DistributionCommissionClass::tryCalcDistributionAfterPay((int)$orderId);
-        }
+        // 零售单支付成功后计分销提成(无分销关系等场景内部抛错,不阻断付款)
+        DistributionCommissionClass::tryCalcDistributionAfterPay((int)$orderId);
 
         // 商城订单支付成功:标记 xhPsMethod 未达门槛附加费当日决策(与 NoticeController / 余额支付同源)
-        if ((int)($order->forward ?? 0) === 0
-            && (int)($order->fromType ?? 0) === (int)dict::getDict('fromType', 'mall')) {
+        if ((int)($order->fromType ?? 0) === (int)dict::getDict('fromType', 'mall')) {
             PsMethodClass::markUnMeetFeePaidFromOrder($order);
         }
     }
@@ -926,10 +816,6 @@ class OrderClass extends BaseClass
                 return false;
             }
         }
-        if ($order->forward == 1) {
-            //售后付款单暂时不能打印
-            return false;
-        }
 
         $shopId = $order->shopId ?? 0;
         $shop = ShopClass::getById($shopId, true);

+ 0 - 14
biz-hd/order/classes/OrderForwardClass.php

@@ -1,14 +0,0 @@
-<?php
-
-namespace bizHd\order\classes;
-
-use common\components\util;
-use Yii;
-use bizHd\base\classes\BaseClass;
-
-class OrderForwardClass extends BaseClass
-{
-
-    public static $baseFile = '\bizHd\order\models\OrderForward';
-
-}

+ 0 - 15
biz-hd/order/models/OrderForward.php

@@ -1,15 +0,0 @@
-<?php
-
-namespace bizHd\order\models;
-
-use bizHd\base\models\Base;
-
-class OrderForward extends Base
-{
-
-	public static function tableName()
-	{
-		return 'xhOrderForward';
-	}
-
-}

+ 0 - 15
biz-hd/order/services/OrderForwardService.php

@@ -1,15 +0,0 @@
-<?php
-
-namespace bizHd\order\services;
-
-use bizHd\base\services\BaseService;
-use common\components\stringUtil;
-use common\components\util;
-use Yii;
-
-class OrderForwardService extends BaseService
-{
-
-    public static $baseFile = '\bizHd\order\classes\OrderForwardClass';
-
-}

+ 0 - 6
biz-hd/order/services/OrderService.php

@@ -35,12 +35,10 @@ class OrderService extends BaseService
         $sjId = $data['sjId'] ?? 0;
         $shopId = $data['shopId'] ?? 0;
         $mainId = $data['mainId'] ?? 0;
-        $forward = $data['forward'] ?? 0;
         $dj = $data['dj'] ?? 0;
         if ($dj > 0) {
             util::fail('请去掉订金,不再支持订金方式');
         }
-        $forwardStock = $data['forwardStock'] ?? 0;
 
         $product = $data['product'] ?? [];
         //组合
@@ -161,8 +159,6 @@ class OrderService extends BaseService
                     'mainId' => $mainId,
                     'kindId' => $kindId,
                     'kindName' => $kindName,
-                    'forward' => $forward,
-                    'forwardStock' => $forwardStock,
                 ];
                 //占用库存在这里
                 OrderGoodsClass::addData($currentGoodsData, $custom, $goodsInfo);
@@ -218,8 +214,6 @@ class OrderService extends BaseService
                     'classId' => $classId,
                     'price' => bcmul($unitPrice, $num, 2),
                     'belongCost' => $belongCost,
-                    'forward' => $forward,
-                    'forwardStock' => $forwardStock,
                 ];
 
                 //库存变化在这里

+ 0 - 12
biz-hd/recharge/classes/RechargeClass.php

@@ -838,18 +838,6 @@ class RechargeClass extends BaseClass
         return is_object($model) && method_exists($model, 'hasAttribute') && $model->hasAttribute($attr);
     }
 
-    /**
-     * 售后付款单是否已经充值过了 ssh 20250801
-     */
-    public static function checkShOrderRecharge($order)
-    {
-        $orderId = $order->id;
-        $has = self::getByCondition(['shOrderId' => $orderId, 'payStatus' => 1]);
-        if (!empty($has)) {
-            util::fail('已经充值过了,无需再次操作');
-        }
-    }
-
     public static function getStatProfile($shopId, $params = [])
     {
         $searchTime = $params['searchTime'] ?? 'today';

+ 401 - 0
biz-hd/refund/classes/HdNextDayRefundClass.php

@@ -0,0 +1,401 @@
+<?php
+/**
+ * HD 零售隔天售后 / 无原单退款业务类
+ * 用途:sameDay 判定、支付快照与资金三态、隔天金额写入、无原单通过与返充。
+ * 调用方:HdRefundService、RefundController;对齐 ghs NextDayRefundClass。
+ */
+namespace bizHd\refund\classes;
+
+use biz\shop\classes\MainClass;
+use biz\shop\classes\ShopCapitalClass;
+use bizHd\custom\classes\CustomClass;
+use bizHd\custom\classes\HdClass;
+use bizHd\merchant\classes\ShopClass;
+use bizHd\order\classes\OrderClass;
+use common\components\dict;
+use common\components\util;
+use Yii;
+
+class HdNextDayRefundClass
+{
+    const SAME_DAY_YES = 1;
+    const SAME_DAY_NO = 0;
+
+    const FLAG_NO = 0;
+    const FLAG_YES = 1;
+
+    /**
+     * 是否有有效支付时间
+     */
+    public static function hasValidPayTime($order)
+    {
+        if (empty($order)) {
+            return false;
+        }
+        $payTime = $order->payTime ?? '';
+        return !empty($payTime) && $payTime !== '0000-00-00 00:00:00';
+    }
+
+    /**
+     * 无支付时间不能售后
+     */
+    public static function assertCanRefund($order)
+    {
+        if (empty($order)) {
+            util::fail('没有原订单');
+        }
+        if (!self::hasValidPayTime($order)) {
+            util::fail('订单无支付时间,不能售后');
+        }
+    }
+
+    /**
+     * 是否必须按隔天售后(sameDay=0):
+     * 非付款日 / 挂账已结清 / 已过可售后时限
+     * @param object $order
+     * @param object|null $main
+     */
+    public static function mustUseNextDay($order, $main = null)
+    {
+        if (!self::hasValidPayTime($order)) {
+            return false;
+        }
+        $payTime = $order->payTime;
+        if (date('Y-m-d', strtotime($payTime)) !== date('Y-m-d')) {
+            return true;
+        }
+        $debtPay = intval(dict::getDict('payWay', 'debtPay'));
+        if (intval($order->payWay ?? 0) === $debtPay) {
+            if (bccomp((string)($order->remainDebtPrice ?? '0'), '0', 2) <= 0) {
+                return true;
+            }
+        }
+        if (!empty($main)) {
+            $cRet = OrderClass::couldRefund($order, $main);
+            if (empty($cRet['could'])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 是否微信/支付宝线上支付
+     */
+    public static function isOnlinePayOrder($orderOrRefund)
+    {
+        if (empty($orderOrRefund)) {
+            return false;
+        }
+        $payWay = intval($orderOrRefund->payWay ?? 0);
+        $onlinePay = intval($orderOrRefund->onlinePay ?? dict::getDict('onlinePay', 'not'));
+        $wxPay = dict::getDict('payWay', 'wxPay');
+        $aliPay = dict::getDict('payWay', 'alipay');
+        return $onlinePay == dict::getDict('onlinePay', 'yes')
+            && in_array($payWay, [$wxPay, $aliPay], true);
+    }
+
+    /**
+     * 余额付或挂账:原路就是动余额,禁止再「返充」
+     */
+    public static function isBalanceOrDebtPayWay($payWay)
+    {
+        $payWay = intval($payWay);
+        return $payWay === intval(dict::getDict('payWay', 'balancePay'))
+            || $payWay === intval(dict::getDict('payWay', 'debtPay'));
+    }
+
+    /**
+     * 是否待选手选资金落地
+     */
+    public static function needFundAction($refund)
+    {
+        if (empty($refund)) {
+            return 0;
+        }
+        if (intval($refund->hasReturn ?? 0) === self::FLAG_YES) {
+            return 0;
+        }
+        if (intval($refund->returnBalance ?? 0) === self::FLAG_YES) {
+            return 0;
+        }
+        if (intval($refund->couldReturn ?? 0) !== self::FLAG_YES) {
+            return 0;
+        }
+        return 1;
+    }
+
+    /**
+     * 生成售后资金快照(落 xhHdRefund)
+     * @param object|null $order
+     * @param bool $hasRelateOrder
+     * @param array $options
+     * @return array
+     */
+    public static function buildRefundPaySnapshot($order = null, $hasRelateOrder = true, $options = [])
+    {
+        $onlineNot = intval(dict::getDict('onlinePay', 'not'));
+        if (!$hasRelateOrder || empty($order)) {
+            $payWay = self::normalizeOfflinePayWay($options['payWay'] ?? null, null);
+            return [
+                'onlinePay' => $onlineNot,
+                'payWay' => $payWay,
+                'hasReturn' => self::FLAG_NO,
+                'couldReturn' => self::FLAG_YES,
+                'returnBalance' => self::FLAG_NO,
+            ];
+        }
+        $payWay = intval($order->payWay ?? 0);
+        $onlinePay = intval($order->onlinePay ?? $onlineNot);
+        $couldReturn = self::isBalanceOrDebtPayWay($payWay) ? self::FLAG_NO : self::FLAG_YES;
+        return [
+            'onlinePay' => $onlinePay,
+            'payWay' => $payWay,
+            'hasReturn' => self::FLAG_NO,
+            'couldReturn' => $couldReturn,
+            'returnBalance' => self::FLAG_NO,
+        ];
+    }
+
+    /**
+     * 线下转账渠道归一
+     */
+    protected static function normalizeOfflinePayWay($payWay, $order = null)
+    {
+        $allowed = [
+            intval(dict::getDict('payWay', 'wxPay')),
+            intval(dict::getDict('payWay', 'alipay')),
+            intval(dict::getDict('payWay', 'cash')),
+            intval(dict::getDict('payWay', 'bankCard')),
+        ];
+        if ($payWay !== null && $payWay !== '' && in_array(intval($payWay), $allowed, true)) {
+            return intval($payWay);
+        }
+        if (!empty($order)) {
+            $orderPayWay = intval($order->payWay ?? -1);
+            if (in_array($orderPayWay, $allowed, true)) {
+                return $orderPayWay;
+            }
+        }
+        return intval(dict::getDict('payWay', 'cash'));
+    }
+
+    /**
+     * 同步资金三态字段
+     */
+    public static function syncRefundFundFlags($refund, $fields = [])
+    {
+        if (empty($refund) || empty($fields)) {
+            return;
+        }
+        foreach ($fields as $k => $v) {
+            $refund->$k = $v;
+        }
+        $refund->save(false, array_keys($fields));
+    }
+
+    /**
+     * 标记已线下原路退回
+     */
+    public static function markHasReturn($refund)
+    {
+        if (empty($refund)) {
+            util::fail('退款单不存在');
+        }
+        if (intval($refund->status) !== HdRefundClass::STATUS_COMPLETE) {
+            util::fail('售后未通过,不能确认原路退回');
+        }
+        if (intval($refund->returnBalance ?? 0) === self::FLAG_YES) {
+            util::fail('已返充余额,不能再标记原路退回');
+        }
+        if (intval($refund->hasReturn ?? 0) === self::FLAG_YES) {
+            return $refund;
+        }
+        self::syncRefundFundFlags($refund, ['hasReturn' => self::FLAG_YES]);
+        return HdRefundClass::getById($refund->id, true);
+    }
+
+    /**
+     * 不想线下原路:返充到客户余额
+     * @return array{refund:object,customBalance:string}
+     */
+    public static function applyReturnBalance($refund, $order = null)
+    {
+        if (empty($refund)) {
+            util::fail('退款单不存在');
+        }
+        if (intval($refund->status) !== HdRefundClass::STATUS_COMPLETE) {
+            util::fail('售后未通过,不能返充余额');
+        }
+        if (intval($refund->hasReturn ?? 0) === self::FLAG_YES) {
+            util::fail('已原路退回,不能再返充余额');
+        }
+        if (intval($refund->returnBalance ?? 0) === self::FLAG_YES) {
+            util::fail('已返充到余额');
+        }
+        if (intval($refund->couldReturn ?? 0) !== self::FLAG_YES) {
+            util::fail('该单不允许返充到余额');
+        }
+
+        $customId = intval($refund->customId ?? 0);
+        $custom = CustomClass::getLockById($customId);
+        if (empty($custom)) {
+            util::fail('没有找到客户');
+        }
+        $refundPrice = bcadd((string)($refund->refundPrice ?? '0'), '0', 2);
+        CustomClass::nextDayRefundReturnBalance($custom, $refundPrice, $refund, $order);
+        self::syncRefundFundFlags($refund, ['returnBalance' => self::FLAG_YES]);
+
+        $custom = CustomClass::getById($customId, true);
+        return [
+            'refund' => HdRefundClass::getById($refund->id, true),
+            'customBalance' => bcadd((string)($custom->balance ?? '0'), '0', 2),
+        ];
+    }
+
+    /**
+     * 有原单隔天通过:累计 nextDayTkPrice;余额/挂账原路加余额并 hasReturn=1
+     * @return string 当前客户余额
+     */
+    public static function applyHdAmountAndFund($refund, $order)
+    {
+        $refundPrice = bcadd((string)($refund->refundPrice ?? '0'), '0', 2);
+        $order->nextDayTkPrice = bcadd((string)($order->nextDayTkPrice ?? '0'), $refundPrice, 2);
+        $order->refund = OrderClass::REFUND_YES;
+        $order->save(false, ['nextDayTkPrice', 'refund']);
+
+        $customId = intval($order->customId ?? 0);
+        $custom = CustomClass::getLockById($customId);
+        if (empty($custom)) {
+            util::fail('没有找到客户');
+        }
+        $buyAmount = bcsub((string)($custom->buyAmount ?? '0'), $refundPrice, 2);
+        if (bccomp($buyAmount, '0', 2) < 0) {
+            $buyAmount = '0.00';
+        }
+        $custom->buyAmount = $buyAmount;
+        $custom->save(false, ['buyAmount']);
+
+        $hdId = intval($custom->hdId ?? 0);
+        if ($hdId > 0) {
+            $hd = HdClass::getLockById($hdId);
+            if (!empty($hd) && isset($hd->expendAmount)) {
+                $expend = bcsub((string)($hd->expendAmount ?? '0'), $refundPrice, 2);
+                if (bccomp($expend, '0', 2) < 0) {
+                    $expend = '0.00';
+                }
+                $hd->expendAmount = $expend;
+                $hd->save(false, ['expendAmount']);
+            }
+        }
+
+        $payWay = intval($refund->payWay ?? ($order->payWay ?? 0));
+        $balance = bcadd((string)($custom->balance ?? '0'), '0', 2);
+        // 余额/挂账:原路加回余额,完成后 hasReturn=1
+        if (self::isBalanceOrDebtPayWay($payWay)) {
+            CustomClass::skRefundDebtAmountReduce($custom, $refundPrice, $refund, $payWay);
+            self::syncRefundFundFlags($refund, ['hasReturn' => self::FLAG_YES]);
+            $custom = CustomClass::getById($customId, true);
+            $balance = bcadd((string)($custom->balance ?? '0'), '0', 2);
+        } elseif (self::isOnlinePayOrder($refund) || self::isOnlinePayOrder($order)) {
+            // 线上原路:沿用当天售后拉卡拉通道(由调用方 HdRefundService 处理更合适)
+            // 此处只记账金额;线上退款在 service 里执行后置 hasReturn
+        }
+        return $balance;
+    }
+
+    /**
+     * 门店支出流水(无原单 / 隔天共用)
+     */
+    public static function addMainExpendForRefund($mainId, $shopId, $sjId, $refundPrice, $event = '销售退款')
+    {
+        $main = MainClass::getLockById($mainId);
+        if (empty($main)) {
+            util::fail('main信息缺失');
+        }
+        $shop = ShopClass::getLockById($shopId);
+        if (empty($shop)) {
+            util::fail('没有找到门店');
+        }
+        $currentTotalExpend = bcadd((string)($main->totalExpend ?? '0'), $refundPrice, 2);
+        $main->totalExpend = $currentTotalExpend;
+        $main->totalRefund = bcadd((string)($main->totalRefund ?? '0'), $refundPrice, 2);
+        $main->save();
+
+        $thisType = dict::getDict('capitalType', 'hdOrderRefund', 'id');
+        ShopCapitalClass::addCapital([
+            'capitalType' => $thisType,
+            'io' => 0,
+            'totalExpend' => $currentTotalExpend,
+            'payWay' => 0,
+            'amount' => $refundPrice,
+            'sjId' => $sjId,
+            'shopId' => $shopId,
+            'event' => $event,
+            'mainId' => $mainId,
+        ]);
+        return $main;
+    }
+
+    /**
+     * 无原单退款:扣客户累计消费;资金留给成功页确认
+     */
+    public static function applyFreeFund($refund)
+    {
+        $refundPrice = bcadd((string)($refund->refundPrice ?? '0'), '0', 2);
+        $customId = intval($refund->customId ?? 0);
+        $custom = CustomClass::getLockById($customId);
+        if (empty($custom)) {
+            util::fail('没有找到客户');
+        }
+        $buyAmount = bcsub((string)($custom->buyAmount ?? '0'), $refundPrice, 2);
+        if (bccomp($buyAmount, '0', 2) < 0) {
+            $buyAmount = '0.00';
+        }
+//        $custom->buyAmount = $buyAmount;
+//        $custom->save(false, ['buyAmount']);
+
+        $hdId = intval($custom->hdId ?? 0);
+        if ($hdId > 0) {
+            $hd = HdClass::getLockById($hdId);
+            if (!empty($hd) && isset($hd->expendAmount)) {
+                $expend = bcsub((string)($hd->expendAmount ?? '0'), $refundPrice, 2);
+                if (bccomp($expend, '0', 2) < 0) {
+                    $expend = '0.00';
+                }
+                $hd->expendAmount = $expend;
+                $hd->save(false, ['expendAmount']);
+            }
+        }
+
+        return bcadd((string)($custom->balance ?? '0'), '0', 2);
+    }
+
+    /**
+     * 组装成功页返回字段
+     */
+    public static function buildFundResultPayload($refund, $custom = null, $extra = [])
+    {
+        $customId = intval($refund->customId ?? 0);
+        if (empty($custom) && $customId > 0) {
+            $custom = CustomClass::getById($customId, true);
+        }
+        return array_merge([
+            'id' => intval($refund->id ?? 0),
+            'orderSn' => $refund->refundSn ?? ($refund->orderSn ?? ''),
+            'refundSn' => $refund->refundSn ?? '',
+            'sameDay' => intval($refund->sameDay ?? self::SAME_DAY_YES),
+            'onlinePay' => intval($refund->onlinePay ?? 0),
+            'payWay' => intval($refund->payWay ?? 0),
+            'hasReturn' => intval($refund->hasReturn ?? 0),
+            'couldReturn' => intval($refund->couldReturn ?? 0),
+            'returnBalance' => intval($refund->returnBalance ?? 0),
+            'refundPrice' => $refund->refundPrice ?? '0',
+            'customId' => $customId,
+            'customName' => $refund->customName ?? ($custom->name ?? ''),
+            'customBalance' => bcadd((string)($custom->balance ?? '0'), '0', 2),
+            'orderId' => intval($refund->orderId ?? 0),
+            'needFundAction' => self::needFundAction($refund),
+        ], $extra);
+    }
+}

+ 6 - 0
biz-hd/refund/classes/HdRefundClass.php

@@ -5,6 +5,7 @@ namespace bizHd\refund\classes;
 use biz\shop\classes\ShopExtClass;
 use common\components\noticeUtil;
 use common\components\orderSn;
+use common\components\util;
 use Yii;
 use bizHd\base\classes\BaseClass;
 use common\components\lakala\Lakala;
@@ -20,6 +21,11 @@ class HdRefundClass extends BaseClass
     const STATUS_UN_COMPLETE = 0; //退款处理中
     const STATUS_COMPLETE = 1; //已完成
 
+    /** 当天有原单售后 */
+    const SAME_DAY_YES = 1;
+    /** 隔天或无原单售后 */
+    const SAME_DAY_NO = 0;
+
     public static function onlyRefundAmount($shop, $order)
     {
         $refundSn = orderSn::getHdRefundSn();

+ 289 - 68
biz-hd/refund/services/HdRefundService.php

@@ -1,5 +1,8 @@
 <?php
-
+/**
+ * HD 零售售后服务
+ * 用途:有原单当天/隔天售后、无原单退款;隔天写 nextRefundNum/nextDayTkPrice,对齐资金三态。
+ */
 namespace bizHd\refund\services;
 
 use biz\product\classes\ProductClass;
@@ -15,6 +18,7 @@ use bizHd\merchant\classes\ShopClass;
 use bizHd\order\classes\OrderClass;
 use bizHd\order\classes\OrderGoodsClass;
 use bizHd\order\classes\OrderItemClass;
+use bizHd\refund\classes\HdNextDayRefundClass;
 use bizHd\refund\classes\HdRefundClass;
 use bizHd\refund\classes\HdRefundGoodsClass;
 use bizHd\refund\classes\HdRefundItemClass;
@@ -32,29 +36,51 @@ class HdRefundService extends BaseService
 
     public static $baseFile = '\bizHd\refund\classes\HdRefundClass';
 
-    //新增退款
+    /**
+     * 有原单退款(当天或隔天)
+     * 隔天:加 nextRefundNum/nextDayTkPrice,不减 actPrice;资金按快照三态落地。
+     * @return object|\bizHd\refund\models\HdRefund
+     */
     public static function addRefund($post, $order)
     {
-        $refundPrice = $post['price'] ?? 0;
-        if ($refundPrice <= 0) {
+        $refundPrice = bcadd((string)($post['price'] ?? '0'), '0', 2);
+        if (bccomp($refundPrice, '0', 2) <= 0) {
             util::fail('请填写退款金额');
         }
 
-        //记录总的退款金额
-        $order->refundPrice = $refundPrice;
-        $currentTkPrice = bcadd($order->tkPrice, $refundPrice, 2);
-        if ($currentTkPrice > $order->mainPay) {
-            util::fail('可退款金额不足');
+        HdNextDayRefundClass::assertCanRefund($order);
+        $sameDay = intval($post['sameDay'] ?? HdRefundClass::SAME_DAY_YES);
+        if (HdNextDayRefundClass::mustUseNextDay($order)) {
+            $sameDay = HdRefundClass::SAME_DAY_NO;
         }
-        $order->tkPrice = $currentTkPrice;
-        $order->refund = OrderClass::REFUND_YES;
-        $currentActPrice = bcsub($order->actPrice, $refundPrice, 2);
-        if ($currentActPrice < 0) {
-            util::fail('可退款金额不足!');
+
+        // 可退金额:当天看 tkPrice;隔天看 tkPrice+nextDayTkPrice 合计不超过 mainPay
+        $mainPay = bcadd((string)($order->mainPay ?? '0'), '0', 2);
+        $alreadyTk = bcadd((string)($order->tkPrice ?? '0'), '0', 2);
+        $alreadyNext = bcadd((string)($order->nextDayTkPrice ?? '0'), '0', 2);
+        if ($sameDay === HdRefundClass::SAME_DAY_YES) {
+            $currentTkPrice = bcadd($alreadyTk, $refundPrice, 2);
+            if (bccomp($currentTkPrice, $mainPay, 2) > 0) {
+                util::fail('可退款金额不足');
+            }
+            $order->tkPrice = $currentTkPrice;
+            $order->refund = OrderClass::REFUND_YES;
+            $currentActPrice = bcsub((string)($order->actPrice ?? '0'), $refundPrice, 2);
+            if (bccomp($currentActPrice, '0', 2) < 0) {
+                util::fail('可退款金额不足!');
+            }
+            $order->actPrice = $currentActPrice;
+            $order->realPrice = $currentActPrice;
+            $order->save();
+        } else {
+            $totalRefunded = bcadd(bcadd($alreadyTk, $alreadyNext, 2), $refundPrice, 2);
+            if (bccomp($totalRefunded, $mainPay, 2) > 0) {
+                util::fail('可退款金额不足');
+            }
+            // 隔天金额在通过资金段 applyHdAmountAndFund 写入 nextDayTkPrice
+            $order->refund = OrderClass::REFUND_YES;
+            $order->save(false, ['refund']);
         }
-        $order->actPrice = $currentActPrice;
-        $order->realPrice = $currentActPrice;
-        $order->save();
 
         $refundType = $post['refundType'] ?? 1;
         $post['orderId'] = $order->id ?? 0;
@@ -75,12 +101,12 @@ class HdRefundService extends BaseService
         $data['mainId'] = $mainId;
         $data['shopAdminId'] = $post['shopAdminId'] ?? 0;
         $data['shopAdminName'] = $post['shopAdminName'] ?? '';
+        $data['passTime'] = date('Y-m-d H:i:s');
 
         $postProduct = $post['product'] ?? '';
 
         if ($refundType == 1) {
             //商品花材结构 property 0商品 1花材
-            //[{productId:0,num:1,unitType:0,unitPrice:12,unitName:'扎',property:1}]
             if (empty($postProduct)) {
                 util::fail('没有退款商品');
             }
@@ -99,9 +125,6 @@ class HdRefundService extends BaseService
                 $calcPrice = bcmul($calcNum, $calcUnitPrice, 2);
                 $calcAmount = bcadd($calcAmount, $calcPrice, 2);
             }
-            if (floatval($refundPrice) < floatval($calcAmount)) {
-                //util::fail('退款金额要大于商品金额总和');
-            }
 
             if (!empty($itemInfoList)) {
                 $ids = array_column($itemInfoList, 'productId');
@@ -128,13 +151,16 @@ class HdRefundService extends BaseService
                         util::fail('没有找到退款的花材');
                     }
                     $orderItemNum = $currentOrderItem->num ?? 0;
-                    //已退货数
                     $hasRefundNum = $currentOrderItem->refundNum ?? 0;
                     $remainNum = bcsub($orderItemNum, $hasRefundNum);
                     if ($currentRefundNum > $remainNum) {
                         util::fail("{$name}超过可退数量");
                     }
+                    // refundNum=总已退;隔天另记 nextRefundNum
                     $currentOrderItem->refundNum = bcadd($hasRefundNum, $currentRefundNum);
+                    if ($sameDay === HdRefundClass::SAME_DAY_NO) {
+                        $currentOrderItem->nextRefundNum = bcadd($currentOrderItem->nextRefundNum ?? 0, $currentRefundNum);
+                    }
                     $currentOrderItem->save();
                     $thisItemData = [
                         'refundSn' => $refundSn,
@@ -178,6 +204,9 @@ class HdRefundService extends BaseService
                         util::fail("{$name}超过可退数量");
                     }
                     $currentOrderGoods->refundNum = bcadd($hasRefundNum, $currentRefundNum);
+                    if ($sameDay === HdRefundClass::SAME_DAY_NO) {
+                        $currentOrderGoods->nextRefundNum = bcadd($currentOrderGoods->nextRefundNum ?? 0, $currentRefundNum);
+                    }
                     $currentOrderGoods->save();
 
                     $cover = $goodsData[$goodsId]['cover'] ?? '';
@@ -195,17 +224,28 @@ class HdRefundService extends BaseService
                         'price' => $price,
                     ];
                     HdRefundGoodsClass::addData($saveGoods, $refundSn, $post);
-
                 }
             }
         }
 
+        $snap = HdNextDayRefundClass::buildRefundPaySnapshot(
+            $order,
+            true,
+            ['payWay' => $post['payWay'] ?? null]
+        );
+
         $data['customId'] = $order->customId ?? 0;
+        $data['customName'] = $order->customName ?? '';
         $data['remark'] = $post['remark'] ?? '';
         $data['refundPrice'] = $refundPrice;
         $data['refundType'] = $post['refundType'] ?? 0;
+        $data['sameDay'] = $sameDay;
+        $data['onlinePay'] = $snap['onlinePay'];
+        $data['payWay'] = $snap['payWay'];
+        $data['hasReturn'] = $snap['hasReturn'];
+        $data['couldReturn'] = $snap['couldReturn'];
+        $data['returnBalance'] = $snap['returnBalance'];
 
-        //写入订单表
         $refund = HdRefundClass::add($data, true);
 
         $payWay = $order->payWay;
@@ -226,10 +266,8 @@ class HdRefundService extends BaseService
         $main->totalRefund = $totalRefund;
         $main->save();
 
-        //支出流水
         $thisType = dict::getDict('capitalType', 'hdOrderRefund', 'id');
         $sjId = $order->sjId ?? 0;
-        $event = '销售退款';
         $capitalData = [
             'capitalType' => $thisType,
             'io' => 0,
@@ -238,50 +276,27 @@ class HdRefundService extends BaseService
             'amount' => $refundPrice,
             'sjId' => $sjId,
             'shopId' => $shopId,
-            'event' => $event,
+            'event' => '销售退款',
             'mainId' => $mainId,
         ];
         ShopCapitalClass::addCapital($capitalData);
-        //当天和当月支出统计
-        //StatOutClass::updateOrInsert($main, $shop, $refundPrice);
-        //退款统计
-        //StatRefundClass::replace($main, $shop, $payWay, $refundPrice);
 
+        // 隔天:金额与余额/挂账原路;线下现金等留给成功页
+        if ($sameDay === HdRefundClass::SAME_DAY_NO) {
+            HdNextDayRefundClass::applyHdAmountAndFund($refund, $order);
+            // 线上隔天:仍尝试原路退回通道
+            if (HdNextDayRefundClass::isOnlinePayOrder($refund) || HdNextDayRefundClass::isOnlinePayOrder($order)) {
+                self::doOnlineLakalaRefund($shop, $refund, $refundSn, $myOrderSn, $thirdNo, $refundPrice, $main);
+                HdNextDayRefundClass::syncRefundFundFlags($refund, ['hasReturn' => HdNextDayRefundClass::FLAG_YES]);
+            }
+            return HdRefundClass::getById($refund->id, true);
+        }
+
+        // —— 当天售后资金 ——
         if ($onlinePay == dict::getDict('onlinePay', 'yes')) {
-            //退款流程
             if ($payWay == dict::getDict('payWay', 'wxPay') || $payWay == dict::getDict('payWay', 'alipay')) {
-
-                //门店余额减少
-                \biz\shop\classes\ShopClass::hdSaleRefundReduceBalance($main, $shop, $refund, $refundPrice);
-                $refundFee = bcmul($refundPrice, 100);
-                $merchantPrivateKeyPath = Yii::getAlias("@vendor/lakala") . '/production/api_private_key.pem';
-                $lklCertificatePath = Yii::getAlias("@vendor/lakala") . '/production/lkl-apigw-v1.cer';
-                $params = [
-                    'appid' => 'OP00002119',
-                    'serial_no' => '018b08cfddbd',
-                    'merchant_no' => $shop->lklSjNo,
-                    'term_no' => $shop->lklScanTermNo,
-                    'merchantPrivateKeyPath' => $merchantPrivateKeyPath,
-                    'lklCertificatePath' => $lklCertificatePath,
-                ];
-                $laResource = new Lakala($params);
-                $refundReason = '';
-                $refundParams = [
-                    'refundSn' => $refundSn,
-                    'orderSn' => $myOrderSn,
-                    'refundAmount' => $refundFee,
-                    'refundReason' => $refundReason,
-                    'thirdNo' => $thirdNo,//这个参数必传,不然聚合收银合支付宝付款的退款不会成功
-                ];
-                $response = $laResource->refund($refundParams);
-                if (!isset($response['code']) || $response['code'] != 'BBS00000') {
-                    $errMsg = $response['msg'] ?? '';
-                    noticeUtil::push('散客订单,支付宝,退款失败,原因:' . $errMsg . ' refundSn:' . $refundSn . ' orderSn:' . $myOrderSn, '15280215347');
-                    util::fail('退款失败');
-                }
-                $wxRefundId = isset($response['resp_data']['trade_no']) ? $response['resp_data']['trade_no'] : '';
-                $refund->thirdRefundNo = $wxRefundId;
-                $refund->save();
+                self::doOnlineLakalaRefund($shop, $refund, $refundSn, $myOrderSn, $thirdNo, $refundPrice, $main);
+                HdNextDayRefundClass::syncRefundFundFlags($refund, ['hasReturn' => HdNextDayRefundClass::FLAG_YES]);
             }
         } else {
             $customId = $order->customId ?? 0;
@@ -290,7 +305,6 @@ class HdRefundService extends BaseService
                 util::fail('没有找到客户');
             }
             if ($payWay == dict::getDict('payWay', 'debtPay')) {
-                //如果欠款支付,减掉欠款,其实就是增加余额
                 $currentRemain = bcsub($order->remainDebtPrice, $refundPrice, 2);
                 if ($currentRemain < 0) {
                     util::fail('剩余欠款不足售后金额');
@@ -301,9 +315,9 @@ class HdRefundService extends BaseService
                 }
                 $order->save();
                 CustomClass::skRefundDebtAmountReduce($custom, $refundPrice, $refund, $payWay);
+                HdNextDayRefundClass::syncRefundFundFlags($refund, ['hasReturn' => HdNextDayRefundClass::FLAG_YES]);
             }
             if ($payWay == dict::getDict('payWay', 'cash')) {
-                //如果现金支付
                 $moneyData = [
                     'shopId' => $shopId,
                     'amount' => $refundPrice,
@@ -311,15 +325,222 @@ class HdRefundService extends BaseService
                     'mainId' => $mainId,
                 ];
                 ShopMoneyClass::hdRefundSkOutAmount($moneyData, $main);
+                // 现金:需人工确认原路(couldReturn=1),成功页 markHasReturn
             }
             if ($payWay == dict::getDict('payWay', 'balancePay')) {
-                //如果是余额支付,客户的余额要增加
                 CustomClass::skRefundDebtAmountReduce($custom, $refundPrice, $refund, $payWay);
+                HdNextDayRefundClass::syncRefundFundFlags($refund, ['hasReturn' => HdNextDayRefundClass::FLAG_YES]);
             }
         }
 
-        return $refund;
+        return HdRefundClass::getById($refund->id, true);
+    }
+
+    /**
+     * 拉卡拉线上原路退款
+     */
+    protected static function doOnlineLakalaRefund($shop, $refund, $refundSn, $myOrderSn, $thirdNo, $refundPrice, $main)
+    {
+        \biz\shop\classes\ShopClass::hdSaleRefundReduceBalance($main, $shop, $refund, $refundPrice);
+        $refundFee = bcmul($refundPrice, 100);
+        $merchantPrivateKeyPath = Yii::getAlias("@vendor/lakala") . '/production/api_private_key.pem';
+        $lklCertificatePath = Yii::getAlias("@vendor/lakala") . '/production/lkl-apigw-v1.cer';
+        $params = [
+            'appid' => 'OP00002119',
+            'serial_no' => '018b08cfddbd',
+            'merchant_no' => $shop->lklSjNo,
+            'term_no' => $shop->lklScanTermNo,
+            'merchantPrivateKeyPath' => $merchantPrivateKeyPath,
+            'lklCertificatePath' => $lklCertificatePath,
+        ];
+        $laResource = new Lakala($params);
+        $refundParams = [
+            'refundSn' => $refundSn,
+            'orderSn' => $myOrderSn,
+            'refundAmount' => $refundFee,
+            'refundReason' => '',
+            'thirdNo' => $thirdNo,
+        ];
+        $response = $laResource->refund($refundParams);
+        if (!isset($response['code']) || $response['code'] != 'BBS00000') {
+            $errMsg = $response['msg'] ?? '';
+            noticeUtil::push('散客订单,支付宝,退款失败,原因:' . $errMsg . ' refundSn:' . $refundSn . ' orderSn:' . $myOrderSn, '15280215347');
+            util::fail('退款失败');
+        }
+        $wxRefundId = isset($response['resp_data']['trade_no']) ? $response['resp_data']['trade_no'] : '';
+        $refund->thirdRefundNo = $wxRefundId;
+        $refund->save();
     }
 
+    /**
+     * 无原单退款(工作台「退款」):orderId=0、sameDay=0,立即完成;资金留给成功页。
+     * @param array $post
+     * @return array
+     */
+    public static function createFreeRefund($post)
+    {
+        $customId = intval($post['customId'] ?? 0);
+        if ($customId <= 0) {
+            util::fail('请选择客户');
+        }
+        $custom = CustomClass::getLockById($customId);
+        if (empty($custom)) {
+            util::fail('没有找到客户');
+        }
+        $mainId = intval($post['mainId'] ?? 0);
+        if ($mainId > 0 && intval($custom->mainId ?? 0) > 0 && intval($custom->mainId) !== $mainId) {
+            // 部分客户用 mainId 归属;无字段则不拦
+        }
+
+        $refundPrice = bcadd((string)($post['price'] ?? '0'), '0', 2);
+        if (bccomp($refundPrice, '0', 2) <= 0) {
+            util::fail('请填写退款金额');
+        }
 
+        $returnWay = intval($post['returnWay'] ?? -1);
+        if ($returnWay === 0) {
+            $refundType = HdRefundClass::REFUND_TYPE_MONEY;
+        } elseif ($returnWay === 1) {
+            $refundType = HdRefundClass::REFUND_TYPE_MONEY_GOOD;
+        } else {
+            $refundType = intval($post['refundType'] ?? HdRefundClass::REFUND_TYPE_MONEY_GOOD);
+        }
+
+        $snap = HdNextDayRefundClass::buildRefundPaySnapshot(null, false, [
+            'payWay' => $post['payWay'] ?? null,
+        ]);
+
+        $sjId = intval($post['sjId'] ?? 0);
+        $shopId = intval($post['shopId'] ?? 0);
+        $refundSn = orderSn::getHdRefundSn();
+        $remark = trim((string)($post['remark'] ?? ''));
+
+        $data = [
+            'status' => HdRefundClass::STATUS_COMPLETE,
+            'refundSn' => $refundSn,
+            'orderSn' => '',
+            'orderId' => 0,
+            'sjId' => $sjId,
+            'shopId' => $shopId,
+            'mainId' => $mainId,
+            'shopAdminId' => $post['shopAdminId'] ?? 0,
+            'shopAdminName' => $post['shopAdminName'] ?? '',
+            'customId' => $customId,
+            'customName' => $custom->name ?? '',
+            'remark' => $remark,
+            'refundPrice' => $refundPrice,
+            'refundType' => $refundType,
+            'sameDay' => HdRefundClass::SAME_DAY_NO,
+            'onlinePay' => $snap['onlinePay'],
+            'payWay' => $snap['payWay'],
+            'hasReturn' => $snap['hasReturn'],
+            'couldReturn' => $snap['couldReturn'],
+            'returnBalance' => $snap['returnBalance'],
+            'passTime' => date('Y-m-d H:i:s'),
+        ];
+
+        $postProduct = $post['product'] ?? [];
+        if ($refundType === HdRefundClass::REFUND_TYPE_MONEY_GOOD) {
+            if (empty($postProduct) || !is_array($postProduct)) {
+                util::fail('请选择花材');
+            }
+        }
+
+        $post['refundType'] = $refundType;
+        $post['orderId'] = 0;
+        $refund = HdRefundClass::add($data, true);
+
+        // 有花材则写售后行;是否回库由 refundType 决定(HdRefundItemClass::addData)
+        if (!empty($postProduct) && is_array($postProduct)) {
+            $itemInfoList = [];
+            $goodsInfoList = [];
+            foreach ($postProduct as $current) {
+                $property = intval($current['property'] ?? 1);
+                if ($property === 0) {
+                    $goodsInfoList[] = $current;
+                } else {
+                    $itemInfoList[] = $current;
+                }
+            }
+            if (!empty($itemInfoList)) {
+                $ids = array_column($itemInfoList, 'productId');
+                $productInfo = ProductClass::getByids($ids, null, 'id');
+                foreach ($itemInfoList as $currentProduct) {
+                    $id = intval($currentProduct['productId'] ?? 0);
+                    if ($id <= 0 || empty($productInfo[$id])) {
+                        util::fail('花材参数错误');
+                    }
+                    $num = bcadd((string)($currentProduct['num'] ?? '0'), '0', 2);
+                    if (bccomp($num, '0', 2) <= 0) {
+                        util::fail(($productInfo[$id]['name'] ?? '花材') . '数量必须大于0');
+                    }
+                    $unitPrice = bcadd((string)($currentProduct['unitPrice'] ?? '0'), '0', 2);
+                    $unitType = intval($currentProduct['unitType'] ?? 0);
+                    $unitName = $currentProduct['unitName'] ?? '';
+                    if ($unitName === '') {
+                        $unitName = $unitType === 1
+                            ? ($productInfo[$id]['smallUnit'] ?? '支')
+                            : ($productInfo[$id]['bigUnit'] ?? '扎');
+                    }
+                    $thisItemData = [
+                        'refundSn' => $refundSn,
+                        'mainId' => $mainId,
+                        'sjId' => $sjId,
+                        'shopId' => $shopId,
+                        'name' => $productInfo[$id]['name'] ?? '',
+                        'itemId' => $id,
+                        'ptItemId' => $productInfo[$id]['itemId'] ?? 0,
+                        'num' => $num,
+                        'unitName' => $unitName,
+                        'unitType' => $unitType,
+                        'cover' => $productInfo[$id]['cover'] ?? '',
+                        'ratio' => $productInfo[$id]['ratio'] ?? '',
+                        'bigUnit' => $productInfo[$id]['bigUnit'] ?? '',
+                        'smallUnit' => $productInfo[$id]['smallUnit'] ?? '',
+                        'unitPrice' => $unitPrice,
+                        'price' => bcmul($num, $unitPrice, 2),
+                    ];
+                    HdRefundItemClass::addData($thisItemData, $refundSn, $post);
+                }
+            }
+            if (!empty($goodsInfoList)) {
+                $ids = array_column($goodsInfoList, 'productId');
+                $goodsData = GoodsClass::getByIds($ids, null, 'id');
+                foreach ($goodsInfoList as $currentGoods) {
+                    $goodsId = intval($currentGoods['productId'] ?? 0);
+                    if ($goodsId <= 0 || empty($goodsData[$goodsId])) {
+                        util::fail('商品参数错误');
+                    }
+                    $num = bcadd((string)($currentGoods['num'] ?? '0'), '0', 2);
+                    if (bccomp($num, '0', 2) <= 0) {
+                        util::fail(($goodsData[$goodsId]['name'] ?? '商品') . '数量必须大于0');
+                    }
+                    $unitPrice = bcadd((string)($currentGoods['unitPrice'] ?? '0'), '0', 2);
+                    $saveGoods = [
+                        'refundSn' => $refundSn,
+                        'mainId' => $mainId,
+                        'sjId' => $sjId,
+                        'shopId' => $shopId,
+                        'name' => $goodsData[$goodsId]['name'] ?? '',
+                        'cover' => $goodsData[$goodsId]['cover'] ?? '',
+                        'goodsId' => $goodsId,
+                        'num' => $num,
+                        'unitPrice' => $unitPrice,
+                        'price' => bcmul($num, $unitPrice, 2),
+                    ];
+                    HdRefundGoodsClass::addData($saveGoods, $refundSn, $post);
+                }
+            }
+        }
+
+        HdNextDayRefundClass::addMainExpendForRefund($mainId, $shopId, $sjId, $refundPrice, '无原单退款');
+        HdNextDayRefundClass::applyFreeFund($refund);
+        $refund = HdRefundClass::getById($refund->id, true);
+        $custom = CustomClass::getById($customId, true);
+
+        return HdNextDayRefundClass::buildFundResultPayload($refund, $custom, [
+            'nextDayTkPrice' => '0.00',
+            'needFundAction' => 1,
+        ]);
+    }
 }