文件
xionggeshop/addons/shopro/library/split/SplitExecutor.php
可乐加冰 6a654070ed ```
feat(split): 新增分账订单线下结案功能

- 添加 SplitCloseService 类用于处理订单结案逻辑
- 实现订单结案状态检查和标记功能
- 在汇付分账重试逻辑中加入结案状态验证,防止已结案订单继续重试
- 更新分账手动服务,添加结案操作类型
- 扩展钱包结算类,增加结案相关诊断方法
- 修改测试脚本,支持批量标记订单为已结案状态
- 添加数据库升级脚本,增加结案相关字段和菜单权限
```
2026-10-24 21:18:48 +08:00

238 行
8.8 KiB
PHP

<?php
namespace addons\shopro\library\split;
use addons\shopro\model\Order;
use addons\shopro\library\split\SplitOrderHelper;
use addons\shopro\model\split\SplitOrder;
use addons\shopro\model\split\SplitOrderReceiver;
use think\Db;
class SplitExecutor
{
protected $config;
protected $service;
protected $calculator;
public function __construct()
{
$this->config = new Config();
$this->service = new ProfitSharingService();
$this->calculator = new SplitCalculator();
}
public function executeByOrderId($orderId, $event = 'payed')
{
$order = Order::get($orderId);
$deferWallet = $order && SplitWalletSettlement::shouldDeferForGatewaySplit($order, $event);
$ok = false;
try {
if (!$this->config->isEnable()) {
return false;
}
if (class_exists('\\addons\\shopro_huifu\\library\\Bridge')) {
$hookParams = [
'order_id' => $orderId,
'event' => $event,
];
$handled = \think\Hook::listen('shopro_split_execute', $hookParams, null, true);
if ($handled === true) {
$ok = true;
return true;
}
}
$orderCheck = Order::get($orderId);
if ($orderCheck && $orderCheck['pay_type'] === 'huifu_wechat') {
return false;
}
$exists = SplitOrder::where('order_id', $orderId)->where('status', 'in', [1, 2])->find();
if ($exists) {
$ok = true;
return true;
}
$order = Order::get($orderId);
if (!$order || empty($order['transaction_id'])) {
return false;
}
$items = $this->calculator->calculate($orderId);
if (empty($items)) {
return false;
}
$outOrderNo = 'PS' . date('YmdHis') . $orderId;
$splitAmount = array_sum(array_column($items, 'amount'));
Db::startTrans();
try {
$splitOrder = SplitOrder::create([
'order_id' => $orderId,
'order_sn' => $order['order_sn'],
'transaction_id' => $order['transaction_id'],
'out_order_no' => $outOrderNo,
'total_amount' => $order['pay_fee'],
'split_amount' => $splitAmount,
'status' => 1,
]);
$receivers = [];
foreach ($items as $item) {
SplitOrderReceiver::create([
'split_order_id' => $splitOrder->id,
'receiver_id' => $item['receiver_id'],
'role_type' => $item['role_type'],
'amount' => $item['amount'],
'description' => $item['description'],
'status' => 0,
]);
$receivers[] = [
'type' => $item['receiver_type'],
'account' => $item['receiver_account'],
'name' => $item['receiver_name'] ?? '',
'amount' => intval(round($item['amount'] * 100)),
'description' => $item['description'],
];
}
$result = $this->service->createOrder([
'transaction_id' => $order['transaction_id'],
'out_order_no' => $outOrderNo,
'receivers' => $receivers,
'unfreeze_unsplit' => true,
]);
$splitOrder->wx_result = is_array($result) ? json_encode($result, JSON_UNESCAPED_UNICODE) : json_encode($result);
$splitOrder->status = 2;
$splitOrder->save();
SplitOrderReceiver::where('split_order_id', $splitOrder->id)->update(['status' => 1]);
Db::commit();
$ok = true;
return true;
} catch (\Exception $e) {
Db::rollback();
$this->markFailed($orderId, $outOrderNo ?? ('PS' . $orderId), $order, $items ?? [], $e->getMessage());
return false;
}
} finally {
if ($deferWallet) {
SplitWalletSettlement::finalizeAfterSplitAttempt($orderId, $event, $ok);
}
}
}
public function retry($splitOrderId)
{
$splitOrder = SplitOrder::get($splitOrderId, ['receivers']);
if (!$splitOrder) {
return false;
}
if (intval($splitOrder['status']) === SplitCloseService::STATUS_CLOSED) {
throw new \RuntimeException('该订单已标记线下结案,不可重试');
}
if ((new SplitCloseService())->isClosed((int) $splitOrder['order_id'])) {
throw new \RuntimeException('该订单已标记线下结案,不可重试');
}
if (($splitOrder['channel'] ?? 'wechat') === 'huifu' && class_exists('\\addons\\shopro_huifu\\library\\HuifuSplitService')) {
return (new \addons\shopro_huifu\library\HuifuSplitService())->retry($splitOrderId);
}
try {
$receivers = [];
foreach ($splitOrder['receivers'] as $row) {
$receiver = \addons\shopro\model\split\Receiver::get($row['receiver_id']);
if (!$receiver) {
continue;
}
$receivers[] = [
'type' => $receiver['receiver_type'],
'account' => $receiver['receiver_account'],
'name' => $receiver['receiver_name'] ?? '',
'amount' => intval(round($row['amount'] * 100)),
'description' => $row['description'],
];
}
$result = $this->service->createOrder([
'transaction_id' => $splitOrder['transaction_id'],
'out_order_no' => $splitOrder['out_order_no'] . 'R' . ($splitOrder['retry_count'] + 1),
'receivers' => $receivers,
'unfreeze_unsplit' => true,
]);
$splitOrder->status = 2;
$splitOrder->wx_result = json_encode($result, JSON_UNESCAPED_UNICODE);
$splitOrder->retry_count = $splitOrder['retry_count'] + 1;
$splitOrder->next_retry_time = null;
$splitOrder->save();
return true;
} catch (\Exception $e) {
$splitOrder->retry_count = $splitOrder['retry_count'] + 1;
$splitOrder->status = 4;
$splitOrder->next_retry_time = time() + $this->config->getRetryInterval();
$splitOrder->wx_result = $e->getMessage();
$splitOrder->save();
return false;
}
}
public function returnByOrderId($orderId)
{
$splitOrder = SplitOrder::where('order_id', $orderId)->where('status', 2)->find();
if (!$splitOrder) {
return false;
}
try {
foreach ($splitOrder->receivers as $row) {
$this->service->returnOrder([
'out_order_no' => $splitOrder['out_order_no'],
'out_return_no' => 'RT' . $splitOrder['id'] . $row['id'],
'return_mchid' => '',
'amount' => intval(round($row['amount'] * 100)),
'description' => '订单退款分账回退',
]);
}
$splitOrder->status = 4;
$splitOrder->save();
return true;
} catch (\Exception $e) {
\think\Log::error('split-return-error:' . $e->getMessage());
return false;
}
}
protected function markFailed($orderId, $outOrderNo, $order, $items, $message)
{
$splitOrder = SplitOrder::where('order_id', $orderId)->find();
if (!$splitOrder) {
$splitOrder = SplitOrder::create([
'order_id' => $orderId,
'order_sn' => $order['order_sn'],
'transaction_id' => $order['transaction_id'] ?? '',
'out_order_no' => $outOrderNo,
'total_amount' => $order['pay_fee'],
'split_amount' => array_sum(array_column($items, 'amount')),
'status' => 4,
'wx_result' => $message,
'retry_count' => 1,
'next_retry_time' => time() + $this->config->getRetryInterval(),
]);
} else {
$splitOrder->status = 4;
$splitOrder->wx_result = $message;
$splitOrder->retry_count = $splitOrder['retry_count'] + 1;
$splitOrder->next_retry_time = time() + $this->config->getRetryInterval();
$splitOrder->save();
}
SplitOrderHelper::syncReceivers($splitOrder->id, $items);
}
}