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

Merge branch 'after-sale' into redesign‌-260706

shish 2 дней назад
Родитель
Сommit
4ee43aaf28

+ 110 - 3
app-ghs/controllers/RefundController.php

@@ -762,15 +762,21 @@ class RefundController extends BaseController
                         'id' => $refundId,
                         'orderSn' => $refundInfo->orderSn ?? '',
                         'sameDay' => intval($refundInfo->sameDay ?? 1),
-                        'fundType' => intval($refundInfo->fundType ?? 0),
                         'onlinePay' => intval($refundInfo->onlinePay ?? 0),
                         'payWay' => intval($refundInfo->payWay ?? 0),
+                        'hasReturn' => intval($refundInfo->hasReturn ?? 0),
+                        'couldReturn' => intval($refundInfo->couldReturn ?? 0),
+                        'returnBalance' => intval($refundInfo->returnBalance ?? 0),
                         'refundPrice' => $refundInfo->refundPrice ?? 0,
                         'customId' => intval($order->customId ?? 0),
                         'customName' => $custom->name ?? ($order->customName ?? ''),
                         'customBalance' => bcadd((string)($custom->balance ?? '0'), '0', 2),
                         'orderId' => intval($id),
                         'nextDayTkPrice' => $order->nextDayTkPrice ?? '0.00',
+                        'needFundAction' => (
+                            intval($refundInfo->hasReturn ?? 0) === 0
+                            && intval($refundInfo->returnBalance ?? 0) === 0
+                        ) ? 1 : 0,
                     ];
                 } catch (\Exception $exception) {
                     if ($transaction->isActive) {
@@ -793,8 +799,8 @@ class RefundController extends BaseController
 
     /**
      * 无原单退款(工作台「退款」)
-     * POST: customId, price, product(json), fundType, refundType, remark
-     * 立即写售后单 sameDay=0 并通过;无关联销售单/采购单
+     * POST: customId, price, product(json), payWay, refundType/returnWay, remark
+     * 立即写售后单 sameDay=0 并通过;资金落地留给成功页确认
      */
     public function actionCreateFreeRefund()
     {
@@ -900,6 +906,107 @@ class RefundController extends BaseController
         }
     }
 
+    /**
+     * 确认已线下原路退回(现金/银行卡等人工确认)
+     * POST: id=售后单 id
+     */
+    public function actionMarkHasReturn()
+    {
+        $post = Yii::$app->request->post();
+        $id = intval($post['id'] ?? 0);
+        $refund = RefundOrderClass::getById($id, true);
+        if (empty($refund)) {
+            util::fail('没有找到退款信息');
+        }
+        if (intval($refund->mainId) !== intval($this->mainId)) {
+            util::fail('没有权限');
+        }
+        try {
+            $respond = util::runWithDbConcurrencyRetry(function () use ($refund) {
+                $connection = Yii::$app->db;
+                $transaction = $connection->beginTransaction();
+                try {
+                    $refund = NextDayRefundService::markHasReturn($refund);
+                    $transaction->commit();
+                    return [
+                        'id' => intval($refund->id ?? 0),
+                        'hasReturn' => intval($refund->hasReturn ?? 0),
+                        'couldReturn' => intval($refund->couldReturn ?? 0),
+                        'returnBalance' => intval($refund->returnBalance ?? 0),
+                        'payWay' => intval($refund->payWay ?? 0),
+                        'onlinePay' => intval($refund->onlinePay ?? 0),
+                        'refundPrice' => $refund->refundPrice ?? 0,
+                        'customId' => intval($refund->customId ?? 0),
+                        'customName' => $refund->customName ?? '',
+                        'orderSn' => $refund->orderSn ?? '',
+                    ];
+                } catch (\Exception $exception) {
+                    if ($transaction->isActive) {
+                        $transaction->rollBack();
+                    }
+                    throw $exception;
+                }
+            });
+            util::success($respond);
+        } catch (\Exception $exception) {
+            util::fail($exception->getMessage() ?: '操作失败');
+        }
+    }
+
+    /**
+     * 不想线下转账:返充到客户余额
+     * POST: id=售后单 id
+     */
+    public function actionReturnBalance()
+    {
+        $post = Yii::$app->request->post();
+        $id = intval($post['id'] ?? 0);
+        $refund = RefundOrderClass::getById($id, true);
+        if (empty($refund)) {
+            util::fail('没有找到退款信息');
+        }
+        if (intval($refund->mainId) !== intval($this->mainId)) {
+            util::fail('没有权限');
+        }
+        try {
+            $respond = util::runWithDbConcurrencyRetry(function () use ($refund) {
+                $connection = Yii::$app->db;
+                $transaction = $connection->beginTransaction();
+                try {
+                    $order = null;
+                    $relateOrderSn = trim((string)($refund->relateOrderSn ?? ''));
+                    if ($relateOrderSn !== '') {
+                        $order = OrderClass::getOneByCondition(['orderSn' => $relateOrderSn], true);
+                    }
+                    $result = NextDayRefundService::applyReturnBalance($refund, $order);
+                    $refund = $result['refund'];
+                    $transaction->commit();
+                    return [
+                        'id' => intval($refund->id ?? 0),
+                        'hasReturn' => intval($refund->hasReturn ?? 0),
+                        'couldReturn' => intval($refund->couldReturn ?? 0),
+                        'returnBalance' => intval($refund->returnBalance ?? 0),
+                        'payWay' => intval($refund->payWay ?? 0),
+                        'onlinePay' => intval($refund->onlinePay ?? 0),
+                        'refundPrice' => $refund->refundPrice ?? 0,
+                        'customId' => intval($refund->customId ?? 0),
+                        'customName' => $refund->customName ?? '',
+                        'customBalance' => $result['customBalance'],
+                        'orderSn' => $refund->orderSn ?? '',
+                    ];
+                } catch (\Exception $exception) {
+                    if ($transaction->isActive) {
+                        $transaction->rollBack();
+                    }
+                    throw $exception;
+                }
+            });
+            util::success($respond);
+        } catch (\Exception $exception) {
+            util::fail($exception->getMessage() ?: '操作失败');
+        }
+    }
+
     //修改售后原因 ssh 20250623
     public function actionChangeRefundOption()
     {

+ 5 - 1
biz-ghs/order/classes/RefundOrderClass.php

@@ -244,7 +244,7 @@ class RefundOrderClass extends BaseClass
         $main->totalRefund = $totalRefund;
         $main->save();
 
-        // 隔天售后:累计 nextDayTkPrice,不改 actPrice;资金按 fundType
+        // 隔天售后:累计 nextDayTkPrice,不改 actPrice;资金按支付快照原路处理
         $sameDay = intval($refund->sameDay ?? self::SAME_DAY_YES);
         if ($sameDay === self::SAME_DAY_NO) {
             \bizGhs\order\services\NextDayRefundService::applyGhsAmountAndFund($refund, $order);
@@ -392,6 +392,10 @@ class RefundOrderClass extends BaseClass
             }
         }
 
+        // 当天售后:账务/通道原路已在上方完成,标记 hasReturn=1
+        \bizGhs\order\services\NextDayRefundService::syncRefundFundFlags($refund, [
+            'hasReturn' => \bizGhs\order\services\NextDayRefundService::FLAG_YES,
+        ]);
     }
 
     public static function rejectRefund($refund, $rejectReason)

+ 154 - 151
biz-ghs/order/services/NextDayRefundService.php

@@ -1,8 +1,8 @@
 <?php
 /**
  * 售后资金/隔天售后辅助服务
- * 用途:资格校验、资金快照(fundType/onlinePay/payWay)、隔天通过后的金额与返余额。
- * 字段:sameDay 区分当天/隔天;fundType 当天隔天通用;onlinePay/payWay 落在售后单上
+ * 用途:资格校验、支付快照与资金三态(hasReturn/couldReturn/returnBalance)、隔天金额与返余额。
+ * 字段:sameDay 区分当天/隔天;onlinePay/payWay 与原单一致;废弃 fundType
  */
 namespace bizGhs\order\services;
 
@@ -25,16 +25,10 @@ class NextDayRefundService
     const SAME_DAY_YES = 1;
     const SAME_DAY_NO = 0;
 
-    /** 资金未确认 */
-    const FUND_UNUSED = 0;
-    /** 原路退回 */
-    const FUND_ORIGINAL = 1;
-    /** 返充到余额 */
-    const FUND_BALANCE = 2;
-    /** 线下自行转账(原「仅记账」) */
-    const FUND_NONE = 3;
-    /** 与 FUND_NONE 同义,便于语义阅读 */
-    const FUND_OFFLINE = 3;
+    /** 三态开关:否 */
+    const FLAG_NO = 0;
+    /** 三态开关:是 */
+    const FLAG_YES = 1;
 
     /**
      * 是否有有效支付时间(售后唯一时间依据;无支付时间不能售后)
@@ -102,7 +96,6 @@ class NextDayRefundService
             'ok' => true,
             'reason' => '',
             'sameDay' => self::SAME_DAY_NO,
-            'suggestFundBalance' => true,
             'orderCleared' => $orderCleared ? 1 : 0,
             'hasPayTime' => 1,
         ];
@@ -123,15 +116,15 @@ class NextDayRefundService
     }
 
     /**
-     * 原单是否为微信/支付宝线上支付(隔天也可原路退回的前提
+     * 是否微信/支付宝线上支付(看订单或售后快照
      */
-    public static function isOnlinePayOrder($order)
+    public static function isOnlinePayOrder($orderOrRefund)
     {
-        if (empty($order)) {
+        if (empty($orderOrRefund)) {
             return false;
         }
-        $payWay = intval($order->payWay ?? 0);
-        $onlinePay = intval($order->onlinePay ?? dict::getDict('onlinePay', 'not'));
+        $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')
@@ -139,136 +132,59 @@ class NextDayRefundService
     }
 
     /**
-     * 隔天售后未选手选资金时的默认值:线上默认原路,其它默认返余额。
+     * 余额付或挂账:原路就是动余额/欠款,禁止再「返充」
      */
-    public static function defaultNextDayFundType($order)
+    public static function isBalanceOrDebtPayWay($payWay)
     {
-        return self::isOnlinePayOrder($order) ? self::FUND_ORIGINAL : self::FUND_BALANCE;
+        $payWay = intval($payWay);
+        return $payWay === intval(dict::getDict('payWay', 'balancePay'))
+            || $payWay === intval(dict::getDict('payWay', 'debtPay'));
     }
 
     /**
-     * 当天售后未选手选资金时的默认值:线上原路;余额/挂账返余额;线下默认线下转账。
-     */
-    public static function defaultSameDayFundType($order)
-    {
-        if (self::isOnlinePayOrder($order)) {
-            return self::FUND_ORIGINAL;
-        }
-        $payWay = intval($order->payWay ?? 0);
-        $balancePay = dict::getDict('payWay', 'balancePay');
-        $debtPay = dict::getDict('payWay', 'debtPay');
-        if ($payWay === $balancePay || $payWay === $debtPay) {
-            return self::FUND_BALANCE;
-        }
-        return self::FUND_NONE;
-    }
-
-    /**
-     * 解析资金方式(当天/隔天通用):挂账/余额强制返余额;线上可选原路/返余额/线下转账。
-     * $hasRelateOrder=false 表示无原单退款,不可原路退。
-     */
-    public static function resolveFundType($fundType, $order, $hasRelateOrder = true)
-    {
-        $fundType = intval($fundType);
-        // 无原单:只能返余额或线下自行转账
-        if (!$hasRelateOrder || empty($order)) {
-            if ($fundType === self::FUND_ORIGINAL) {
-                util::fail('无原单退款不能原路退回');
-            }
-            if (!in_array($fundType, [self::FUND_BALANCE, self::FUND_NONE], true)) {
-                return self::FUND_BALANCE;
-            }
-            return $fundType;
-        }
-        $payWay = intval($order->payWay ?? 0);
-        $debtPay = dict::getDict('payWay', 'debtPay');
-        $balancePay = dict::getDict('payWay', 'balancePay');
-        // 挂账/余额付款:资金落到返充余额
-        if ($payWay === $debtPay || $payWay === $balancePay) {
-            return self::FUND_BALANCE;
-        }
-        // 线上付款:可选原路 / 返余额 / 线下转账
-        if (self::isOnlinePayOrder($order)) {
-            if (!in_array($fundType, [self::FUND_ORIGINAL, self::FUND_BALANCE, self::FUND_NONE], true)) {
-                util::fail('请选择资金处理方式');
-            }
-            return $fundType;
-        }
-        // 线下付款:返余额或线下自行转账
-        if (!in_array($fundType, [self::FUND_BALANCE, self::FUND_NONE], true)) {
-            return self::FUND_BALANCE;
-        }
-        return $fundType;
-    }
-
-    /**
-     * 生成写入售后单的资金快照:fundType + onlinePay + payWay(落 xhRefund/xhCgRefund)。
-     * onlinePay:原路且走线上通道为 2,否则 1;payWay:原路/线下转账的具体渠道。
+     * 生成售后资金快照(落 xhRefund/xhCgRefund)
+     * - 有原单:onlinePay/payWay 与订单一致;余额/挂账 couldReturn=0,其它=1
+     * - 无原单:线下渠道由选手选 payWay;couldReturn=1(成功页可改走余额)
      *
-     * @param int $fundTypeInput 前端/上游传入,0 表示按 sameDay 自动默认
      * @param object|null $order 原销售单;无原单传 null
      * @param bool $hasRelateOrder 是否有关联原单
-     * @param array $options sameDay、payWay(线下转账选手选渠道)
-     * @return array{fundType:int,onlinePay:int,payWay:int}
+     * @param array $options payWay(无原单线下渠道)
+     * @return array{onlinePay:int,payWay:int,hasReturn:int,couldReturn:int,returnBalance:int}
      */
-    public static function buildRefundFundSnapshot($fundTypeInput, $order = null, $hasRelateOrder = true, $options = [])
+    public static function buildRefundPaySnapshot($order = null, $hasRelateOrder = true, $options = [])
     {
-        $fundTypeInput = intval($fundTypeInput);
-        $sameDay = intval($options['sameDay'] ?? self::SAME_DAY_YES);
         $onlineNot = intval(dict::getDict('onlinePay', 'not'));
-        $onlineYes = intval(dict::getDict('onlinePay', 'yes'));
-
-        if ($fundTypeInput === self::FUND_UNUSED && $hasRelateOrder && !empty($order)) {
-            $fundTypeInput = ($sameDay === self::SAME_DAY_NO)
-                ? self::defaultNextDayFundType($order)
-                : self::defaultSameDayFundType($order);
-        }
-
-        $fundType = self::resolveFundType($fundTypeInput, $order, $hasRelateOrder);
-
-        // 返充余额:渠道固定为余额,非线上原路
-        if ($fundType === self::FUND_BALANCE) {
-            return [
-                'fundType' => self::FUND_BALANCE,
-                'onlinePay' => $onlineNot,
-                'payWay' => intval(dict::getDict('payWay', 'balancePay')),
-            ];
-        }
-
-        // 线下自行转账:优先用选手选 payWay(微信/支付宝/现金/银行卡)
-        if ($fundType === self::FUND_NONE) {
-            $payWay = self::normalizeOfflinePayWay($options['payWay'] ?? null, $order);
+        // 兼容旧调用名:内部仍可被 buildRefundFundSnapshot 转发
+        if (!$hasRelateOrder || empty($order)) {
+            $payWay = self::normalizeOfflinePayWay($options['payWay'] ?? null, null);
             return [
-                'fundType' => self::FUND_NONE,
                 'onlinePay' => $onlineNot,
                 'payWay' => $payWay,
+                'hasReturn' => self::FLAG_NO,
+                'couldReturn' => self::FLAG_YES,
+                'returnBalance' => self::FLAG_NO,
             ];
         }
-
-        // 原路退回:线上微信/支付宝标记 onlinePay=2;否则记线下原路渠道
-        if ($fundType === self::FUND_ORIGINAL && !empty($order)) {
-            $orderPayWay = intval($order->payWay ?? 0);
-            if (self::isOnlinePayOrder($order)) {
-                return [
-                    'fundType' => self::FUND_ORIGINAL,
-                    'onlinePay' => $onlineYes,
-                    'payWay' => $orderPayWay,
-                ];
-            }
-            return [
-                'fundType' => self::FUND_ORIGINAL,
-                'onlinePay' => $onlineNot,
-                'payWay' => $orderPayWay,
-            ];
-        }
-
+        $payWay = intval($order->payWay ?? 0);
+        $onlinePay = intval($order->onlinePay ?? $onlineNot);
+        $couldReturn = self::isBalanceOrDebtPayWay($payWay) ? self::FLAG_NO : self::FLAG_YES;
         return [
-            'fundType' => self::FUND_UNUSED,
-            'onlinePay' => $onlineNot,
-            'payWay' => 0,
+            'onlinePay' => $onlinePay,
+            'payWay' => $payWay,
+            'hasReturn' => self::FLAG_NO,
+            'couldReturn' => $couldReturn,
+            'returnBalance' => self::FLAG_NO,
         ];
     }
 
+    /**
+     * @deprecated 已废弃 fundType;保留转发以免旧调用报错
+     */
+    public static function buildRefundFundSnapshot($fundTypeInput, $order = null, $hasRelateOrder = true, $options = [])
+    {
+        return self::buildRefundPaySnapshot($order, $hasRelateOrder, $options);
+    }
+
     /**
      * 线下转账渠道:允许 0微信/1支付宝/4现金/5银行卡;非法则回落原单或现金
      */
@@ -293,8 +209,93 @@ class NextDayRefundService
     }
 
     /**
-     * GHS 侧隔天通过:累计 nextDayTkPrice,按 fundType 处理资金,不改 actPrice。
-     * @return string 返余额后的客户余额(非返余额则为当前余额)
+     * 售后单与采购镜像同步资金三态
+     */
+    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));
+        $cgRefundId = intval($refund->cgRefundId ?? 0);
+        if ($cgRefundId <= 0) {
+            return;
+        }
+        $cgRefund = CgRefundClass::getById($cgRefundId, true);
+        if (empty($cgRefund)) {
+            return;
+        }
+        foreach ($fields as $k => $v) {
+            $cgRefund->$k = $v;
+        }
+        $cgRefund->save(false, array_keys($fields));
+    }
+
+    /**
+     * 标记已原路退回(现金/银行卡等线下人工确认)
+     */
+    public static function markHasReturn($refund)
+    {
+        if (empty($refund)) {
+            util::fail('退款单不存在');
+        }
+        if (intval($refund->status) !== RefundOrderClass::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 RefundOrderClass::getById($refund->id, true);
+    }
+
+    /**
+     * 不想线下原路时:返充到客户余额(须 couldReturn=1 且尚未原路)
+     * @return array{refund:object,customBalance:string}
+     */
+    public static function applyReturnBalance($refund, $order = null)
+    {
+        if (empty($refund)) {
+            util::fail('退款单不存在');
+        }
+        if (intval($refund->status) !== RefundOrderClass::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('没有找到客户');
+        }
+        $pair = GhsRechargeSettleService::lockAccountPair($custom);
+        CustomClass::nextDayRefundReturnBalance($pair['custom'], $pair['ghs'], $refund, $order);
+        self::syncRefundFundFlags($refund, ['returnBalance' => self::FLAG_YES]);
+
+        $custom = CustomClass::getById($customId, true);
+        return [
+            'refund' => RefundOrderClass::getById($refund->id, true),
+            'customBalance' => bcadd((string)($custom->balance ?? '0'), '0', 2),
+        ];
+    }
+
+    /**
+     * GHS 侧隔天通过:累计 nextDayTkPrice;余额/挂账原路加余额并 hasReturn=1;线上/线下现金等留给通道或人工。
+     * @return string 当前客户余额
      */
     public static function applyGhsAmountAndFund($refund, $order)
     {
@@ -315,20 +316,22 @@ class NextDayRefundService
         $custom->buyAmount = $buyAmount;
         $custom->save(false, ['buyAmount']);
 
-        $fundType = intval($refund->fundType ?? self::FUND_UNUSED);
+        $payWay = intval($refund->payWay ?? ($order->payWay ?? 0));
         $balance = bcadd((string)($custom->balance ?? '0'), '0', 2);
-        if ($fundType === self::FUND_BALANCE) {
+        // 余额/挂账:原路=加回余额,完成后 hasReturn=1(couldReturn 已为 0)
+        if (self::isBalanceOrDebtPayWay($payWay)) {
             $pair = GhsRechargeSettleService::lockAccountPair($custom);
             CustomClass::nextDayRefundReturnBalance($pair['custom'], $pair['ghs'], $refund, $order);
+            self::syncRefundFundFlags($refund, ['hasReturn' => self::FLAG_YES]);
             $custom = CustomClass::getById($customId, true);
             $balance = bcadd((string)($custom->balance ?? '0'), '0', 2);
         }
-        // FUND_ORIGINAL:网关退款在采购侧执行;FUND_NONE:线下自行转账(只记账)
+        // 线上原路在采购侧发起;现金/银行卡等线下原路等人工 markHasReturn
         return $balance;
     }
 
     /**
-     * HD 采购侧隔天通过:累计 nextDayTkPrice;原路退走拉卡拉,不改 actPrice
+     * HD 采购侧隔天通过:累计 nextDayTkPrice;线上原路走拉卡拉成功后 hasReturn=1
      */
     public static function applyCgAmountAndFund($cgRefund, $cg)
     {
@@ -337,9 +340,17 @@ class NextDayRefundService
         $cg->refund = PurchaseClass::REFUND_YES;
         $cg->save(false, ['nextDayTkPrice', 'refund']);
 
-        $fundType = intval($cgRefund->fundType ?? self::FUND_UNUSED);
-        if ($fundType === self::FUND_ORIGINAL) {
+        if (self::isOnlinePayOrder($cgRefund) || self::isOnlinePayOrder($cg)) {
             CgRefundClass::nextDayOriginalOnlineRefund($cgRefund, $cg);
+            $cgRefund->hasReturn = self::FLAG_YES;
+            $cgRefund->save(false, ['hasReturn']);
+            $saleRefundId = intval($cgRefund->saleRefundId ?? 0);
+            if ($saleRefundId > 0) {
+                $saleRefund = RefundOrderClass::getById($saleRefundId, true);
+                if (!empty($saleRefund)) {
+                    self::syncRefundFundFlags($saleRefund, ['hasReturn' => self::FLAG_YES]);
+                }
+            }
         }
 
         $ghsId = intval($cg->ghsId ?? 0);
@@ -357,7 +368,7 @@ class NextDayRefundService
     }
 
     /**
-     * 无原单退款通过:加库存(退货退款)、累计门店支出、扣客户消费、按 fundType 返余额/仅记账
+     * 无原单退款通过:加库存(退货退款)、累计门店支出、扣客户消费;资金留给成功页确认原路/返充
      * 不写订单 nextDayTkPrice(无关联销售单)。
      */
     public static function passFreeRefund($refund)
@@ -448,8 +459,8 @@ class NextDayRefundService
     }
 
     /**
-     * 无原单退款资金:扣减客户累计消费;返余额走成对账户,仅记账不动余额
-     * @return string 处理后的客户余额
+     * 无原单退款资金:仅扣减客户累计消费;原路/返充在成功页由 markHasReturn / applyReturnBalance 落地
+     * @return string 当前客户余额
      */
     public static function applyFreeFund($refund)
     {
@@ -466,16 +477,7 @@ class NextDayRefundService
         $custom->buyAmount = $buyAmount;
         $custom->save(false, ['buyAmount']);
 
-        $fundType = intval($refund->fundType ?? self::FUND_UNUSED);
-        $balance = bcadd((string)($custom->balance ?? '0'), '0', 2);
-        if ($fundType === self::FUND_BALANCE) {
-            $pair = GhsRechargeSettleService::lockAccountPair($custom);
-            // 无原单:order 传 null,流水挂售后单号
-            CustomClass::nextDayRefundReturnBalance($pair['custom'], $pair['ghs'], $refund, null);
-            $custom = CustomClass::getById($customId, true);
-            $balance = bcadd((string)($custom->balance ?? '0'), '0', 2);
-        }
-        return $balance;
+        return bcadd((string)($custom->balance ?? '0'), '0', 2);
     }
 
     /**
@@ -501,8 +503,9 @@ class NextDayRefundService
      */
     public static function listForChannelIncomeDeduct($mainId, $startTime, $endTime)
     {
-        $sql = "SELECT r.refundPrice AS amount, r.fundType, o.id AS orderId,
-                o.debtPrice, o.remainDebtPrice, o.onlinePay, o.payWay AS orderPayWay, o.payWay
+        $sql = "SELECT r.refundPrice AS amount, r.hasReturn, r.returnBalance, r.couldReturn,
+                r.onlinePay AS refundOnlinePay, r.payWay AS refundPayWay,
+                o.id AS orderId, o.debtPrice, o.remainDebtPrice, o.onlinePay, o.payWay AS orderPayWay, o.payWay
             FROM xhRefund r
             LEFT JOIN xhGhsOrder o ON o.orderSn = r.relateOrderSn
             WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay

+ 8 - 4
biz-ghs/order/services/OrderService.php

@@ -1008,11 +1008,13 @@ class OrderService extends BaseService
             'cause' => $cause,
             'refundSendCost' => $refundSendCost,
             'refundPackCost' => $refundPackCost,
-            // 与销售侧售后成对写入 sameDay + 资金快照
+            // 与销售侧售后成对写入 sameDay + 支付快照 + 资金三态
             'sameDay' => $saleRefund->sameDay ?? \bizGhs\order\classes\RefundOrderClass::SAME_DAY_YES,
-            'fundType' => $saleRefund->fundType ?? 0,
             'onlinePay' => $saleRefund->onlinePay ?? 0,
             'payWay' => $saleRefund->payWay ?? 0,
+            'hasReturn' => $saleRefund->hasReturn ?? 0,
+            'couldReturn' => $saleRefund->couldReturn ?? 0,
+            'returnBalance' => $saleRefund->returnBalance ?? 0,
         ];
         $cgRefund = CgRefundService::addRefund($cgData, $cg);
         $cgRefundId = $cgRefund->id;
@@ -1021,10 +1023,12 @@ class OrderService extends BaseService
         $saleRefund->save();
         $cgRefund->saleRefundId = $saleRefundId;
         $cgRefund->sameDay = intval($saleRefund->sameDay ?? 1);
-        $cgRefund->fundType = intval($saleRefund->fundType ?? 0);
         $cgRefund->onlinePay = intval($saleRefund->onlinePay ?? 0);
         $cgRefund->payWay = intval($saleRefund->payWay ?? 0);
-        $cgRefund->save(false, ['saleRefundId', 'sameDay', 'fundType', 'onlinePay', 'payWay']);
+        $cgRefund->hasReturn = intval($saleRefund->hasReturn ?? 0);
+        $cgRefund->couldReturn = intval($saleRefund->couldReturn ?? 0);
+        $cgRefund->returnBalance = intval($saleRefund->returnBalance ?? 0);
+        $cgRefund->save(false, ['saleRefundId', 'sameDay', 'onlinePay', 'payWay', 'hasReturn', 'couldReturn', 'returnBalance']);
 
         return $saleRefund;
     }

+ 28 - 29
biz-ghs/order/services/RefundOrderService.php

@@ -134,35 +134,37 @@ class RefundOrderService extends BaseService
 
     /**
      * 将当天售后单转为隔天售后类型(审核跨日/已结清时调用)
-     * 同步写销售/采购售后单的 sameDay、fundType、onlinePay、payWay
+     * 同步写销售/采购售后单的 sameDay + 支付快照 + 资金三态
      */
     public static function convertRefundToNextDay($refund, $order)
     {
-        $snap = NextDayRefundService::buildRefundFundSnapshot(
-            intval($refund->fundType ?? 0),
+        $snap = NextDayRefundService::buildRefundPaySnapshot(
             $order,
             true,
             [
-                'sameDay' => NextDayRefundService::SAME_DAY_NO,
                 'payWay' => $refund->payWay ?? null,
             ]
         );
 
         $refund->sameDay = RefundOrderClass::SAME_DAY_NO;
-        $refund->fundType = $snap['fundType'];
         $refund->onlinePay = $snap['onlinePay'];
         $refund->payWay = $snap['payWay'];
-        $refund->save(false, ['sameDay', 'fundType', 'onlinePay', 'payWay']);
+        $refund->hasReturn = $snap['hasReturn'];
+        $refund->couldReturn = $snap['couldReturn'];
+        $refund->returnBalance = $snap['returnBalance'];
+        $refund->save(false, ['sameDay', 'onlinePay', 'payWay', 'hasReturn', 'couldReturn', 'returnBalance']);
 
         $cgRefundId = intval($refund->cgRefundId ?? 0);
         if ($cgRefundId > 0) {
             $cgRefund = CgRefundClass::getById($cgRefundId, true);
             if (!empty($cgRefund)) {
                 $cgRefund->sameDay = CgRefundClass::SAME_DAY_NO;
-                $cgRefund->fundType = $snap['fundType'];
                 $cgRefund->onlinePay = $snap['onlinePay'];
                 $cgRefund->payWay = $snap['payWay'];
-                $cgRefund->save(false, ['sameDay', 'fundType', 'onlinePay', 'payWay']);
+                $cgRefund->hasReturn = $snap['hasReturn'];
+                $cgRefund->couldReturn = $snap['couldReturn'];
+                $cgRefund->returnBalance = $snap['returnBalance'];
+                $cgRefund->save(false, ['sameDay', 'onlinePay', 'payWay', 'hasReturn', 'couldReturn', 'returnBalance']);
             }
         }
     }
@@ -276,7 +278,7 @@ class RefundOrderService extends BaseService
 
         // 无支付时间不能售后
         NextDayRefundService::assertCanRefund($order);
-        // sameDay:非当天/已结清强制隔天;资金快照当天/隔天都写入 fundType/onlinePay/payWay
+        // sameDay:非当天/已结清强制隔天;支付快照与资金三态写入售后单
         $sameDay = intval($post['sameDay'] ?? RefundOrderClass::SAME_DAY_YES);
         if (NextDayRefundService::mustUseNextDay($order)) {
             $sameDay = RefundOrderClass::SAME_DAY_NO;
@@ -287,19 +289,19 @@ class RefundOrderService extends BaseService
                 util::fail($check['reason'] ?? '当前订单不可隔天售后');
             }
         }
-        $snap = NextDayRefundService::buildRefundFundSnapshot(
-            intval($post['fundType'] ?? 0),
+        $snap = NextDayRefundService::buildRefundPaySnapshot(
             $order,
             true,
             [
-                'sameDay' => $sameDay,
                 'payWay' => $post['payWay'] ?? null,
             ]
         );
         $data['sameDay'] = $sameDay;
-        $data['fundType'] = $snap['fundType'];
         $data['onlinePay'] = $snap['onlinePay'];
         $data['payWay'] = $snap['payWay'];
+        $data['hasReturn'] = $snap['hasReturn'];
+        $data['couldReturn'] = $snap['couldReturn'];
+        $data['returnBalance'] = $snap['returnBalance'];
 
         $refund = RefundOrderClass::add($data, true);
         return $refund;
@@ -307,9 +309,9 @@ class RefundOrderService extends BaseService
 
     /**
      * 无原单退款(工作台「退款」):写 xhRefund.sameDay=0,relateOrderSn 为空;
-     * 选花材后立即通过——退货加库存、返余额/仅记账,不写采购侧镜像单
-     * @param array $post customId/price/product/fundType/refundType/...
-     * @return array 结果页/海报所需字段
+     * 立即通过(库存/支出),资金三态待成功页「已线下转账 / 返充余额」
+     * @param array $post customId/price/product/payWay/refundType/...
+     * @return array 结果页所需字段
      */
     public static function createFreeRefund($post)
     {
@@ -341,20 +343,11 @@ class RefundOrderService extends BaseService
             $refundType = intval($post['refundType'] ?? RefundOrderClass::REFUND_TYPE_MONEY_GOOD);
         }
 
-        // 前端 toBalance:1返充余额→fundType2;0否→线下自行转账3
-        $toBalance = intval($post['toBalance'] ?? -1);
-        if ($toBalance === 1) {
-            $post['fundType'] = NextDayRefundService::FUND_BALANCE;
-        } elseif ($toBalance === 0) {
-            $post['fundType'] = NextDayRefundService::FUND_NONE;
-        }
-        // 无原单:写入 fundType/onlinePay/payWay(payWay 为线下转账渠道)
-        $snap = NextDayRefundService::buildRefundFundSnapshot(
-            intval($post['fundType'] ?? 0),
+        // 无原单:线下原路渠道 = 确认页「退款方式」;couldReturn=1 供成功页返充
+        $snap = NextDayRefundService::buildRefundPaySnapshot(
             null,
             false,
             [
-                'sameDay' => NextDayRefundService::SAME_DAY_NO,
                 'payWay' => $post['payWay'] ?? null,
             ]
         );
@@ -386,9 +379,11 @@ class RefundOrderService extends BaseService
             'refundPrice' => $refundPrice,
             'refundType' => $refundType,
             'sameDay' => RefundOrderClass::SAME_DAY_NO,
-            'fundType' => $snap['fundType'],
             'onlinePay' => $snap['onlinePay'],
             'payWay' => $snap['payWay'],
+            'hasReturn' => $snap['hasReturn'],
+            'couldReturn' => $snap['couldReturn'],
+            'returnBalance' => $snap['returnBalance'],
         ];
 
         $postProduct = $post['product'] ?? [];
@@ -463,15 +458,19 @@ class RefundOrderService extends BaseService
             'id' => intval($refund->id ?? 0),
             'orderSn' => $refund->orderSn ?? '',
             'sameDay' => RefundOrderClass::SAME_DAY_NO,
-            'fundType' => intval($refund->fundType ?? $snap['fundType']),
             'onlinePay' => intval($refund->onlinePay ?? $snap['onlinePay']),
             'payWay' => intval($refund->payWay ?? $snap['payWay']),
+            'hasReturn' => intval($refund->hasReturn ?? 0),
+            'couldReturn' => intval($refund->couldReturn ?? 1),
+            'returnBalance' => intval($refund->returnBalance ?? 0),
             'refundPrice' => $refund->refundPrice ?? $refundPrice,
             'customId' => $customId,
             'customName' => $custom->name ?? ($refund->customName ?? ''),
             'customBalance' => bcadd((string)($custom->balance ?? '0'), '0', 2),
             'orderId' => 0,
             'nextDayTkPrice' => '0.00',
+            // 成功页需选手选资金落地
+            'needFundAction' => 1,
         ];
     }
 }

+ 23 - 6
biz-ghs/stat/classes/StatKdClass.php

@@ -447,19 +447,36 @@ class StatKdClass extends BaseClass
         }
     }
 
-    /** 与批发开单入账分支一致,映射渠道 key;无原单退款按 fundType 归余额/其它 */
+    /** 与批发开单入账分支一致,映射渠道 key;无原单按售后 payWay / 是否已返充归类 */
     protected static function resolveNextDayIncomeChannel($row)
     {
         $debtPrice = bcadd((string)($row['debtPrice'] ?? '0'), '0', 2);
         $remainDebt = bcadd((string)($row['remainDebtPrice'] ?? '0'), '0', 2);
         $orderId = intval($row['orderId'] ?? 0);
-        $payWay = intval($row['orderPayWay'] ?? ($row['payWay'] ?? 0));
-        $onlinePay = intval($row['onlinePay'] ?? dict::getDict('onlinePay', 'not'));
+        $payWay = intval($row['orderPayWay'] ?? ($row['payWay'] ?? ($row['refundPayWay'] ?? 0)));
+        $onlinePay = intval($row['onlinePay'] ?? ($row['refundOnlinePay'] ?? dict::getDict('onlinePay', 'not')));
 
-        // 无关联销售单:返余额记 balancePay,仅记账进 other
+        // 无关联销售单:已返充记 balancePay,否则按线下 payWay;未落地资金归 other
         if ($orderId <= 0) {
-            $fundType = intval($row['fundType'] ?? 0);
-            return $fundType === 2 ? 'balancePay' : 'other';
+            if (intval($row['returnBalance'] ?? 0) === 1) {
+                return 'balancePay';
+            }
+            if (intval($row['hasReturn'] ?? 0) !== 1) {
+                return 'other';
+            }
+            $refundPayWay = intval($row['refundPayWay'] ?? $payWay);
+            switch ($refundPayWay) {
+                case dict::getDict('payWay', 'wxPay'):
+                    return 'kfWx';
+                case dict::getDict('payWay', 'alipay'):
+                    return 'aliPay';
+                case dict::getDict('payWay', 'cash'):
+                    return 'cash';
+                case dict::getDict('payWay', 'bankCard'):
+                    return 'bankCard';
+                default:
+                    return 'other';
+            }
         }
 
         if ($orderId > 0 && bccomp($debtPrice, '0', 2) > 0) {

+ 17 - 12
biz-hd/cg/services/CgRefundService.php

@@ -44,7 +44,7 @@ class CgRefundService extends BaseService
     }
 
     /**
-     * 花店申请售后:先判销售单是否须隔天(非当天/已结清),两端成对写 sameDay/fundType
+     * 花店申请售后:先判销售单是否须隔天(非当天/已结清),两端成对写 sameDay + 支付快照 + 资金三态
      */
     public static function createOrder($cgData, $cg)
     {
@@ -56,25 +56,24 @@ class CgRefundService extends BaseService
         // 无支付时间不能售后;仅用 payTime 判当天/隔天
         NextDayRefundService::assertCanRefund($order);
 
-        // 非当天 / 已结账 / 挂账结清 → 隔天;资金快照当天/隔天都写 fundType/onlinePay/payWay
+        // 非当天 / 已结账 / 挂账结清 → 隔天;支付快照与订单一致
         $sameDay = CgRefundClass::SAME_DAY_YES;
         if (NextDayRefundService::mustUseNextDay($order)) {
             $sameDay = CgRefundClass::SAME_DAY_NO;
         }
-        $snap = NextDayRefundService::buildRefundFundSnapshot(
-            intval($cgData['fundType'] ?? 0),
+        $snap = NextDayRefundService::buildRefundPaySnapshot(
             $order,
             true,
             [
-                'sameDay' => $sameDay,
                 'payWay' => $cgData['payWay'] ?? null,
             ]
         );
         $cgData['sameDay'] = $sameDay;
-        $cgData['fundType'] = $snap['fundType'];
         $cgData['onlinePay'] = $snap['onlinePay'];
         $cgData['payWay'] = $snap['payWay'];
-        $fundType = $snap['fundType'];
+        $cgData['hasReturn'] = $snap['hasReturn'];
+        $cgData['couldReturn'] = $snap['couldReturn'];
+        $cgData['returnBalance'] = $snap['returnBalance'];
 
         $respond = self::addRefund($cgData, $cg);
         $remark = $cgData['remark'] ?? '';
@@ -139,9 +138,11 @@ class CgRefundService extends BaseService
             'mainId' => $mainId,
             'product' => $newProductList,
             'sameDay' => $sameDay,
-            'fundType' => $snap['fundType'],
             'onlinePay' => $snap['onlinePay'],
             'payWay' => $snap['payWay'],
+            'hasReturn' => $snap['hasReturn'],
+            'couldReturn' => $snap['couldReturn'],
+            'returnBalance' => $snap['returnBalance'],
         ];
         $cgRefundId = $respond->id ?? 0;
         $saleRefund = RefundOrderService::addRefund($orderData, $order);
@@ -151,10 +152,12 @@ class CgRefundService extends BaseService
         // 与销售侧最终资金快照对齐(addRefund 内可能再次强制隔天)
         $respond->saleRefundId = $saleRefundId;
         $respond->sameDay = intval($saleRefund->sameDay ?? $sameDay);
-        $respond->fundType = intval($saleRefund->fundType ?? $snap['fundType']);
         $respond->onlinePay = intval($saleRefund->onlinePay ?? $snap['onlinePay']);
         $respond->payWay = intval($saleRefund->payWay ?? $snap['payWay']);
-        $respond->save(false, ['saleRefundId', 'sameDay', 'fundType', 'onlinePay', 'payWay']);
+        $respond->hasReturn = intval($saleRefund->hasReturn ?? $snap['hasReturn']);
+        $respond->couldReturn = intval($saleRefund->couldReturn ?? $snap['couldReturn']);
+        $respond->returnBalance = intval($saleRefund->returnBalance ?? $snap['returnBalance']);
+        $respond->save(false, ['saleRefundId', 'sameDay', 'onlinePay', 'payWay', 'hasReturn', 'couldReturn', 'returnBalance']);
         return $respond;
     }
 
@@ -222,11 +225,13 @@ class CgRefundService extends BaseService
         $data['bigNum'] = $bigNum;
         $data['smallNum'] = $smallNum;
         $data['cause'] = $cause;
-        // 与销售侧成对:sameDay + 资金快照由上游传入
+        // 与销售侧成对:sameDay + 支付快照 + 资金三态由上游传入
         $data['sameDay'] = intval($post['sameDay'] ?? CgRefundClass::SAME_DAY_YES);
-        $data['fundType'] = intval($post['fundType'] ?? 0);
         $data['onlinePay'] = intval($post['onlinePay'] ?? dict::getDict('onlinePay', 'not'));
         $data['payWay'] = intval($post['payWay'] ?? 0);
+        $data['hasReturn'] = intval($post['hasReturn'] ?? 0);
+        $data['couldReturn'] = intval($post['couldReturn'] ?? 0);
+        $data['returnBalance'] = intval($post['returnBalance'] ?? 0);
         $cgRefund = CgRefundClass::add($data, true);
 
         if ($refundType == CgRefundClass::REFUND_TYPE_MONEY_GOOD) {