mirror of
https://github.com/crmeb/CRMEB.git
synced 2026-10-28 13:31:04 +00:00
fix(资金流水): 只记录真实资金进出,余额结算与退回余额不再记账
- PayServices 新增 isCashChannel():仅第三方在线渠道 + 线下支付为真; 余额支付、后台代付、空支付方式(0 元单)一律为假。isRealPaymentChannel() 保留余额支付语义(站内有结算无资金流),内部委托 isCashChannel 判定现金渠道。 - CapitalFlowServices::setFlow() 作为唯一出口统一拦截非现金渠道与未知业务类型, 避免各业务分支各自判断导致漏过滤;nickname/phone 缺省兼容,返回是否写入。 - 退款链路:refund_type=balance(含原路退款改退余额)属于站内结转, RefundExecutorServices::writeFlow() 与 RefundEffectServices 均跳过记账; 其余退款同样走 isCashChannel 判定,余额支付订单退款不再产生流水。 - 补充 .crmeb 售后文档的资金流水口径说明。
This commit is contained in:
parent
f9ba517389
commit
51f76ec771
@ -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 开发步骤
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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;
|
||||
}
|
||||
// 第三方渠道:从驱动注册中心动态获取
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user