```
feat(split): 添加微信分账V3版本验证和汇付分账冻结资金处理 - 在ProfitSharingService中添加Yansongda Pay V3版本验证,确保微信官方分账功能正常运行 - 实现汇付分账冻结资金检测逻辑,当资金被冻结时标记分账失败而非抛出异常 - 在管理后台控制器中增强错误处理和状态显示功能 - 优化前端界面,根据分账模式动态显示相应操作按钮 ```
这个提交包含在:
@@ -40,11 +40,38 @@ class ProfitSharingService
|
||||
|
||||
protected function callPlugin($plugin, array $params)
|
||||
{
|
||||
$this->assertYansongdaPayV3();
|
||||
$wechat = Pay::wechat($this->config);
|
||||
if (!is_object($wechat) || !method_exists($wechat, 'mergeCommonPlugins')) {
|
||||
throw new \RuntimeException(
|
||||
'当前加载的是 Yansongda Pay V2,无法调用微信官方分账。'
|
||||
. '纯汇付分账请勿使用「同步微信」;若需微信分账请将 epay 插件 API 版本改为 V3 并重新进入后台再试。'
|
||||
);
|
||||
}
|
||||
$plugins = $wechat->mergeCommonPlugins([$plugin]);
|
||||
return $wechat->pay($plugins, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信官方分账(ProfitSharing)仅 epay 内置的 Yansongda V3 支持,与汇付 delaytrans/confirm 无关。
|
||||
*/
|
||||
protected function assertYansongdaPayV3()
|
||||
{
|
||||
if (!class_exists('\\addons\\epay\\library\\Service')) {
|
||||
throw new \RuntimeException('请先安装并启用 epay(微信支付宝整合)插件');
|
||||
}
|
||||
if (!\addons\epay\library\Service::isVersionV3()) {
|
||||
throw new \RuntimeException(
|
||||
'epay 当前为 API V2,不支持微信官方分账接口。'
|
||||
. '您若使用汇付延时分账:接收方在汇付控台绑定 huifu_id 即可,不要点「同步微信」。'
|
||||
. '若业务必须用微信分账:在 epay 配置里改为 V3,并配置微信支付 APIv3 密钥与证书(与 V2 密钥不通用)。'
|
||||
);
|
||||
}
|
||||
if (!class_exists(CreatePlugin::class)) {
|
||||
throw new \RuntimeException('Yansongda Pay V3 类未加载,请确认 epay 为 V3 后刷新页面再试');
|
||||
}
|
||||
}
|
||||
|
||||
protected function buildWechatConfig()
|
||||
{
|
||||
$paymentConfig = json_decode(ShoproConfig::get(['name' => 'wechat'])->value, true);
|
||||
|
||||
@@ -67,7 +67,11 @@ class HuifuSplitService
|
||||
|
||||
$outOrderNo = 'HFPS' . date('YmdHis') . $orderId;
|
||||
$ext = HuifuPayService::getPayExt($orderId);
|
||||
$this->assertDelayFundsReadyForConfirm($order['order_sn']);
|
||||
$blockReason = $this->getDelayFundsBlockReason($order['order_sn']);
|
||||
if ($blockReason !== null) {
|
||||
$this->markFailed($orderId, $outOrderNo, $order, $items, $blockReason);
|
||||
return false;
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
@@ -197,17 +201,19 @@ class HuifuSplitService
|
||||
|
||||
/**
|
||||
* 微信小程序 + 延时分账:通道侧资金可能处于 FREEZE,需发货/确认收货或等待解冻后才能 delaytrans/confirm。
|
||||
*
|
||||
* @return string|null 不可 confirm 时返回说明文案,否则 null
|
||||
*/
|
||||
protected function assertDelayFundsReadyForConfirm($orderSn)
|
||||
protected function getDelayFundsBlockReason($orderSn)
|
||||
{
|
||||
try {
|
||||
$query = (new HuifuQueryService())->queryByOrderSn($orderSn);
|
||||
} catch (\Throwable $e) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
$freezeStat = strtoupper(trim((string) ($this->queryFieldRecursive($query, 'fund_freeze_stat') ?? '')));
|
||||
if ($freezeStat !== 'FREEZE') {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
$unfreezeTime = trim((string) ($this->queryFieldRecursive($query, 'unfreeze_time') ?? ''));
|
||||
$hint = '汇付查单 fund_freeze_stat=FREEZE:延迟交易资金仍被通道冻结,暂不能交易确认分账。'
|
||||
@@ -216,7 +222,7 @@ class HuifuSplitService
|
||||
if ($unfreezeTime !== '') {
|
||||
$hint .= ' 预计解冻时间(unfreeze_time)=' . $unfreezeTime;
|
||||
}
|
||||
throw new \RuntimeException($hint);
|
||||
return $hint;
|
||||
}
|
||||
|
||||
protected function resolveConfirmPayFee($orderSn, $orderPayFee)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace app\admin\controller\shopro\split;
|
||||
|
||||
use app\admin\controller\shopro\Base;
|
||||
use addons\shopro\library\split\Config as SplitConfig;
|
||||
use addons\shopro\library\split\SplitExecutor;
|
||||
use addons\shopro\library\split\ProfitSharingService;
|
||||
|
||||
@@ -37,7 +38,11 @@ class Order extends Base
|
||||
}
|
||||
$orderId = (int) $row['order_id'];
|
||||
$channel = $row['channel'] ?? 'wechat';
|
||||
$result = (new SplitExecutor())->retry($id);
|
||||
try {
|
||||
$result = (new SplitExecutor())->retry($id);
|
||||
} catch (\Throwable $e) {
|
||||
$this->error('重试失败:' . $e->getMessage());
|
||||
}
|
||||
if ($result) {
|
||||
$this->success('重试成功');
|
||||
}
|
||||
@@ -58,6 +63,9 @@ class Order extends Base
|
||||
return '';
|
||||
}
|
||||
$raw = is_string($latest['wx_result']) ? $latest['wx_result'] : json_encode($latest['wx_result'], JSON_UNESCAPED_UNICODE);
|
||||
if ($raw !== '' && strpos($raw, 'fund_freeze_stat=FREEZE') !== false) {
|
||||
return ':' . mb_substr($raw, 0, 500);
|
||||
}
|
||||
$decoded = json_decode($raw, true);
|
||||
if (is_array($decoded)) {
|
||||
$parts = array_filter([
|
||||
@@ -80,6 +88,8 @@ class Order extends Base
|
||||
if (($row['channel'] ?? 'wechat') === 'huifu' && class_exists('\\addons\\shopro_huifu\\library\\HuifuSplitService')) {
|
||||
$service = new \addons\shopro_huifu\library\HuifuSplitService();
|
||||
$result = $service->querySplit($row['transaction_id']);
|
||||
} elseif ((new SplitConfig())->isHuifuMode()) {
|
||||
$this->error('当前为汇付分账,请对 channel=huifu 的分账记录查询,或到汇付控台查看分账结果');
|
||||
} else {
|
||||
$service = new ProfitSharingService();
|
||||
$result = $service->queryOrder([
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace app\admin\controller\shopro\split;
|
||||
|
||||
use app\admin\controller\shopro\Base;
|
||||
use addons\shopro\library\split\Config as SplitConfig;
|
||||
use addons\shopro\library\split\ProfitSharingService;
|
||||
|
||||
class Receiver extends Base
|
||||
@@ -24,6 +25,7 @@ class Receiver extends Base
|
||||
$list = $this->model->where($where)->order($sort, $order)->limit($offset, $limit)->select();
|
||||
return $this->success('操作成功', null, ['total' => $total, 'rows' => $list]);
|
||||
}
|
||||
$this->assignconfig('splitHuifuOnly', (new SplitConfig())->isHuifuMode());
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
@@ -71,6 +73,9 @@ class Receiver extends Base
|
||||
if (!$row) {
|
||||
$this->error('记录不存在');
|
||||
}
|
||||
if ((new SplitConfig())->isHuifuMode()) {
|
||||
$this->error('当前为汇付分账:无需同步微信接收方。请在汇付控台绑定分账关系,并在此填写 huifu_id');
|
||||
}
|
||||
try {
|
||||
$service = new ProfitSharingService();
|
||||
$service->addReceiver([
|
||||
|
||||
@@ -10,12 +10,13 @@
|
||||
<el-table-column prop="receiver_type" label="类型" width="140"></el-table-column>
|
||||
<el-table-column prop="receiver_account" label="账号" min-width="180"></el-table-column>
|
||||
<el-table-column prop="receiver_name" label="名称" width="120"></el-table-column>
|
||||
<el-table-column label="微信状态" width="100">
|
||||
<el-table-column v-if="!splitHuifuOnly" label="微信状态" width="100">
|
||||
<template slot-scope="s">{{ wxStatusText(s.row.wx_status) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160">
|
||||
<template slot-scope="s">
|
||||
<el-button type="text" @click="syncWx(s.row.id)">同步微信</el-button>
|
||||
<el-button v-if="!splitHuifuOnly" type="text" @click="syncWx(s.row.id)">同步微信</el-button>
|
||||
<span v-else style="color:#909399;font-size:12px;">汇付分账在控台绑定</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
@@ -3,7 +3,11 @@ define(['jquery', 'bootstrap', 'backend', 'form'], function ($, undefined, Backe
|
||||
index: function () {
|
||||
new Vue({
|
||||
el: '#splitReceiverIndex',
|
||||
data: { listData: [], loading: false },
|
||||
data: {
|
||||
listData: [],
|
||||
loading: false,
|
||||
splitHuifuOnly: !!(typeof Config !== 'undefined' && Config.splitHuifuOnly),
|
||||
},
|
||||
mounted() { this.getList(); },
|
||||
methods: {
|
||||
getList() {
|
||||
|
||||
在新工单中引用
屏蔽一个用户