From 51f76ec771e7801d579c7b6da34d4e973226e24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=98=8A=E5=A4=A9?= <442384644@qq.com> Date: Fri, 23 Oct 2026 14:09:43 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E8=B5=84=E9=87=91=E6=B5=81=E6=B0=B4):=20?= =?UTF-8?q?=E5=8F=AA=E8=AE=B0=E5=BD=95=E7=9C=9F=E5=AE=9E=E8=B5=84=E9=87=91?= =?UTF-8?q?=E8=BF=9B=E5=87=BA=EF=BC=8C=E4=BD=99=E9=A2=9D=E7=BB=93=E7=AE=97?= =?UTF-8?q?=E4=B8=8E=E9=80=80=E5=9B=9E=E4=BD=99=E9=A2=9D=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E8=AE=B0=E8=B4=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PayServices 新增 isCashChannel():仅第三方在线渠道 + 线下支付为真; 余额支付、后台代付、空支付方式(0 元单)一律为假。isRealPaymentChannel() 保留余额支付语义(站内有结算无资金流),内部委托 isCashChannel 判定现金渠道。 - CapitalFlowServices::setFlow() 作为唯一出口统一拦截非现金渠道与未知业务类型, 避免各业务分支各自判断导致漏过滤;nickname/phone 缺省兼容,返回是否写入。 - 退款链路:refund_type=balance(含原路退款改退余额)属于站内结转, RefundExecutorServices::writeFlow() 与 RefundEffectServices 均跳过记账; 其余退款同样走 isCashChannel 判定,余额支付订单退款不再产生流水。 - 补充 .crmeb 售后文档的资金流水口径说明。 --- .../references/order-aftersale.md | 2 ++ .../services/order/RefundEffectServices.php | 8 +++++- .../services/order/RefundExecutorServices.php | 14 +++++++--- backend/app/services/pay/PayServices.php | 26 +++++++++++++++++-- .../statistic/CapitalFlowServices.php | 23 +++++++++++----- 5 files changed, 60 insertions(+), 13 deletions(-) diff --git a/.crmeb/skills/crmeb-order-dev/references/order-aftersale.md b/.crmeb/skills/crmeb-order-dev/references/order-aftersale.md index 75ed30b33..359bc98fe 100644 --- a/.crmeb/skills/crmeb-order-dev/references/order-aftersale.md +++ b/.crmeb/skills/crmeb-order-dev/references/order-aftersale.md @@ -72,6 +72,8 @@ AfterSaleServices::refund - Handler 中的资金/资源副作用一律经 `RefundEffectContextInterface::once()` 登记去重,禁止直接裸写。 - 中台 `refund_status`(0 无 / 1 申请中 / 2 已退)由 Adapter `resolveRefundStatus` 推导,退款完成时 `syncStatus` 同步。 +**资金流水(`eb_capital_flow`)只记真实资金进出**:判定统一入口为 `PayServices::isCashChannel()`(第三方在线渠道 + 线下支付;余额 `yue`、后台代付、0 元单一律 false)。`CapitalFlowServices::setFlow()` 内部已做拦截,退款链路在调用前还要排除 `refund_type=balance` / `refund_method=balance` 的站内结转,即「余额支付订单退款」「原路退款但改退余额」都不产生现金流出,不记流水。 + > **现状说明**:`OrderRefundCreateAfter / OrderRefundRefuse / OrderRefundCancelAfter` 的模块监听器已注册但服务层触发点待接线(见 [订单生命周期·事件地图](order-lifecycle.md#三订单事件地图)),相关后置逻辑目前挂在 `AfterSaleServices` 内部回调(`lifecycle->afterApply/afterAudit`)。 ## 五、自定义售后 Handler 开发步骤 diff --git a/backend/app/services/order/RefundEffectServices.php b/backend/app/services/order/RefundEffectServices.php index 38ddfec1a..863b200ad 100644 --- a/backend/app/services/order/RefundEffectServices.php +++ b/backend/app/services/order/RefundEffectServices.php @@ -9,6 +9,7 @@ use app\dao\order\RefundEffectDao; use app\dao\order\RefundOrderDao; use app\model\order\OrderCenter; use app\model\order\RefundOrder; +use app\services\pay\PayServices; use app\services\statistic\CapitalFlowServices; use app\services\user\UserBillServices; use app\services\user\UserMoneyServices; @@ -380,7 +381,12 @@ class RefundEffectServices extends BaseService implements RefundEffectContextInt }); } - if (bccomp($amount, '0.00', 2) > 0) { + // 资金流水只记录真实资金流出:退回余额(refund_method=balance)或余额支付订单退款 + // 属于站内结转,钱没有离开平台,不记流水 + $flowPayType = (string)($order['pay_type'] ?? ($refundOrder['pay_channel'] ?? '')); + /** @var PayServices $payServices */ + $payServices = $this->app->make(PayServices::class); + if (!$localBalance && bccomp($amount, '0.00', 2) > 0 && $payServices->isCashChannel($flowPayType)) { $this->once('capital_flow', (string)$refundOrder['refund_no'], ['amount' => $amount], function () use ($order, $amount): void { /** @var UserServices $userServices */ $userServices = $this->app->make(UserServices::class); diff --git a/backend/app/services/order/RefundExecutorServices.php b/backend/app/services/order/RefundExecutorServices.php index c37986ea5..a539dc58e 100644 --- a/backend/app/services/order/RefundExecutorServices.php +++ b/backend/app/services/order/RefundExecutorServices.php @@ -97,7 +97,7 @@ class RefundExecutorServices extends BaseService } // 3. 资本流水 + 开票标记 - $this->writeFlow($order, $refundPrice); + $this->writeFlow($order, $refundPrice, $context); $this->markInvoiceRefunded($order); return [ @@ -459,20 +459,26 @@ class RefundExecutorServices extends BaseService /** * 写资本流水(refund 类型) + * 只有真实资金流出才记账:退回用户余额属于站内结转,余额支付订单退款同理,均不记流水 * @param array $order 中台订单数据 * @param float $refundPrice 退款金额 + * @param array $context 退款上下文(refund_type: original/balance) * @return void */ - protected function writeFlow(array $order, float $refundPrice): void + protected function writeFlow(array $order, float $refundPrice, array $context = []): void { if ($refundPrice <= 0) { return; } + // 退款到余额:钱没离开平台,只是从订单转为用户余额,不产生资金流出 + if (($context['refund_type'] ?? 'original') === 'balance') { + return; + } $payType = (string)($order['pay_type'] ?? ''); - // 是否真实资金渠道由 PayServices 动态判定(第三方渠道来自驱动注册中心,yue/offline 为系统常量渠道) + // 是否真实资金渠道由 PayServices 动态判定(第三方渠道来自驱动注册中心,offline 为系统常量渠道) /** @var PayServices $payServices */ $payServices = app()->make(PayServices::class); - if (!$payServices->isRealPaymentChannel($payType)) { + if (!$payServices->isCashChannel($payType)) { return; } /** @var UserServices $userServices */ diff --git a/backend/app/services/pay/PayServices.php b/backend/app/services/pay/PayServices.php index e3d5018b4..d49ceceec 100644 --- a/backend/app/services/pay/PayServices.php +++ b/backend/app/services/pay/PayServices.php @@ -337,8 +337,30 @@ class PayServices */ public function isRealPaymentChannel(string $payType): bool { - // 系统固有渠道:余额支付 / 线下支付,不随驱动扩展,常量判定合法 - if ($payType === self::YUE_PAY || $payType === self::OFFLINE_PAY) { + // 余额支付:站内虚拟资产结算,属于"有结算但无资金流",在此方法中视为真实支付 + if ($payType === self::YUE_PAY) { + return true; + } + return $this->isCashChannel($payType); + } + + /** + * 判断支付方式是否产生真实资金进出(现金收付渠道) + * 包含:第三方在线支付渠道(由驱动注册中心动态获取)+ 线下支付 + * 排除:余额支付(站内虚拟资产,充值后消费不再产生现金流动)、 + * 空支付方式(0 元单)、后台代付等无资金流动的结算方式 + * 资金流水(eb_capital_flow)只允许记录本方法判定为 true 的支付方式 + * @param string $payType 支付方式标识 + * @return bool + */ + public function isCashChannel(string $payType): bool + { + $payType = trim($payType); + if ($payType === '' || $payType === self::YUE_PAY) { + return false; + } + // 线下支付为系统固有渠道,不随驱动扩展,常量判定合法 + if ($payType === self::OFFLINE_PAY) { return true; } // 第三方渠道:从驱动注册中心动态获取 diff --git a/backend/app/services/statistic/CapitalFlowServices.php b/backend/app/services/statistic/CapitalFlowServices.php index b89362d70..047edb371 100644 --- a/backend/app/services/statistic/CapitalFlowServices.php +++ b/backend/app/services/statistic/CapitalFlowServices.php @@ -33,8 +33,12 @@ class CapitalFlowServices extends BaseService /** * 添加资金流水 - * @param $orderInfo - * @param string $type + * 资金流水记录的是平台真实资金进出(第三方在线收款、线下收银、充值及对应退款), + * 余额支付、后台代付、0 元单等站内虚拟结算不产生现金流动,一律不记账。 + * 该判定放在此处作为唯一出口,避免各业务分支各自判断导致漏过滤。 + * @param array $orderInfo 业务订单信息 + * @param string $type 流水类型 + * @return bool 是否写入流水 */ public function setFlow($orderInfo, $type = '') { @@ -97,12 +101,19 @@ class CapitalFlowServices extends BaseService $data['pay_type'] = $orderInfo['pay_type']; break; default: - break; + // 未知业务类型不记账,避免写入无金额无单号的空流水 + return false; } - $data['nickname'] = $orderInfo['nickname']; - $data['phone'] = $orderInfo['phone']; + // 非现金渠道(余额支付/后台代付/0 元单等)无真实资金流,不记流水 + /** @var PayServices $payServices */ + $payServices = app()->make(PayServices::class); + if (!$payServices->isCashChannel((string)($data['pay_type'] ?? ''))) { + return false; + } + $data['nickname'] = $orderInfo['nickname'] ?? ''; + $data['phone'] = $orderInfo['phone'] ?? ''; $data['add_time'] = time(); - $this->dao->save($data); + return (bool)$this->dao->save($data); } /**