fix: 优化订单及售后退款控制器异常捕获,避免第三方支付错误触发500
这个提交包含在:
@@ -297,32 +297,36 @@ class Aftersale extends Backend
|
|||||||
$this->error('订单商品已退款,不能重复退款');
|
$this->error('订单商品已退款,不能重复退款');
|
||||||
}
|
}
|
||||||
|
|
||||||
Db::transaction(function () use ($aftersale, $order, $orderItem, $refund_money, $refund_surplus_money) {
|
try {
|
||||||
$data = ['aftersale' => $aftersale, 'order' => $order, 'item' => $orderItem];
|
Db::transaction(function () use ($aftersale, $order, $orderItem, $refund_money, $refund_surplus_money) {
|
||||||
\think\Hook::listen('aftersale_finish_before', $data);
|
$data = ['aftersale' => $aftersale, 'order' => $order, 'item' => $orderItem];
|
||||||
|
\think\Hook::listen('aftersale_finish_before', $data);
|
||||||
|
|
||||||
$aftersale->aftersale_status = \app\admin\model\shopro\order\Aftersale::AFTERSALE_STATUS_OK; // 售后同意
|
$aftersale->aftersale_status = \app\admin\model\shopro\order\Aftersale::AFTERSALE_STATUS_OK; // 售后同意
|
||||||
$aftersale->refund_status = \app\admin\model\shopro\order\Aftersale::REFUND_STATUS_FINISH; // 售后同意退款
|
$aftersale->refund_status = \app\admin\model\shopro\order\Aftersale::REFUND_STATUS_FINISH; // 售后同意退款
|
||||||
$aftersale->refund_fee = bcadd($refund_money, $aftersale->refund_fee, 2); // 已退款金额
|
$aftersale->refund_fee = bcadd($refund_money, $aftersale->refund_fee, 2); // 已退款金额
|
||||||
$aftersale->save();
|
$aftersale->save();
|
||||||
|
|
||||||
// 增加售后单变动记录
|
// 增加售后单变动记录
|
||||||
OrderAftersaleLog::operAdd($order, $aftersale, $this->auth->getUserInfo(), 'admin', [
|
OrderAftersaleLog::operAdd($order, $aftersale, $this->auth->getUserInfo(), 'admin', [
|
||||||
'reason' => '卖家同意退款',
|
'reason' => '卖家同意退款',
|
||||||
'content' => '售后订单已退款',
|
'content' => '售后订单已退款',
|
||||||
'images' => []
|
'images' => []
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$orderItem->aftersale_status = OrderItem::AFTERSALE_STATUS_OK;
|
$orderItem->aftersale_status = OrderItem::AFTERSALE_STATUS_OK;
|
||||||
$orderItem->save();
|
$orderItem->save();
|
||||||
\addons\shopro\model\OrderAction::operAdd($order, $orderItem, $this->auth->getUserInfo(), 'admin', '管理员同意售后退款');
|
\addons\shopro\model\OrderAction::operAdd($order, $orderItem, $this->auth->getUserInfo(), 'admin', '管理员同意售后退款');
|
||||||
|
|
||||||
// 退款
|
// 退款
|
||||||
\app\admin\model\shopro\order\Order::startRefund($order, $orderItem, $refund_money, $this->auth->getUserInfo(), '管理员同意售后退款');
|
\app\admin\model\shopro\order\Order::startRefund($order, $orderItem, $refund_money, $this->auth->getUserInfo(), '管理员同意售后退款');
|
||||||
|
|
||||||
$data = ['aftersale' => $aftersale, 'order' => $order, 'item' => $orderItem];
|
$data = ['aftersale' => $aftersale, 'order' => $order, 'item' => $orderItem];
|
||||||
\think\Hook::listen('aftersale_finish_after', $data);
|
\think\Hook::listen('aftersale_finish_after', $data);
|
||||||
});
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->error($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
return $this->success('操作成功');
|
return $this->success('操作成功');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1034,40 +1034,44 @@ class Order extends Base
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Db::transaction(function () use ($order, $items, $item_id, $refund_money, $refund_surplus_money) {
|
try {
|
||||||
if ($item_id) {
|
Db::transaction(function () use ($order, $items, $item_id, $refund_money, $refund_surplus_money) {
|
||||||
// 单个商品退款
|
if ($item_id) {
|
||||||
$item = $items[$item_id];
|
// 单个商品退款
|
||||||
\app\admin\model\shopro\order\Order::startRefund($order, $item, $refund_money, $this->auth->getUserInfo(), '管理员操作退款');
|
$item = $items[$item_id];
|
||||||
} else {
|
\app\admin\model\shopro\order\Order::startRefund($order, $item, $refund_money, $this->auth->getUserInfo(), '管理员操作退款');
|
||||||
// 全部退款
|
} else {
|
||||||
// 未退款 item 商品总金额
|
// 全部退款
|
||||||
$goods_total_amount = 0;
|
// 未退款 item 商品总金额
|
||||||
foreach ($items as $ke => $it) {
|
$goods_total_amount = 0;
|
||||||
$goods_total_amount += ($it['goods_price'] * $it['goods_num']);
|
foreach ($items as $ke => $it) {
|
||||||
}
|
$goods_total_amount += ($it['goods_price'] * $it['goods_num']);
|
||||||
|
|
||||||
$current_refunded_money = 0;
|
|
||||||
for ($i = 0; $i < count($items); $i++) {
|
|
||||||
if ($i == (count($items) - 1)) {
|
|
||||||
// 最后一条,全部退完
|
|
||||||
$current_refund_money = $refund_money - $current_refunded_money;
|
|
||||||
} else {
|
|
||||||
// 按比例计算当前 item 应退金额
|
|
||||||
$current_refund_money = round($refund_money * (($items[$i]['goods_price'] * $it['goods_num']) / $goods_total_amount), 2);
|
|
||||||
}
|
|
||||||
if (($current_refunded_money + $current_refund_money) > $refund_money) {
|
|
||||||
$current_refund_money = $refund_money - $current_refunded_money;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($current_refund_money >= 0) { // 支付金额或者退款金额 为 0 也能退
|
$current_refunded_money = 0;
|
||||||
$current_refunded_money += $current_refund_money;
|
for ($i = 0; $i < count($items); $i++) {
|
||||||
|
if ($i == (count($items) - 1)) {
|
||||||
|
// 最后一条,全部退完
|
||||||
|
$current_refund_money = $refund_money - $current_refunded_money;
|
||||||
|
} else {
|
||||||
|
// 按比例计算当前 item 应退金额
|
||||||
|
$current_refund_money = round($refund_money * (($items[$i]['goods_price'] * $it['goods_num']) / $goods_total_amount), 2);
|
||||||
|
}
|
||||||
|
if (($current_refunded_money + $current_refund_money) > $refund_money) {
|
||||||
|
$current_refund_money = $refund_money - $current_refunded_money;
|
||||||
|
}
|
||||||
|
|
||||||
\app\admin\model\shopro\order\Order::startRefund($order, $items[$i], $current_refund_money, $this->auth->getUserInfo(), '管理员操作退款');
|
if ($current_refund_money >= 0) { // 支付金额或者退款金额 为 0 也能退
|
||||||
|
$current_refunded_money += $current_refund_money;
|
||||||
|
|
||||||
|
\app\admin\model\shopro\order\Order::startRefund($order, $items[$i], $current_refund_money, $this->auth->getUserInfo(), '管理员操作退款');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
} catch (\Throwable $e) {
|
||||||
|
$this->error($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
$item_list = \app\admin\model\shopro\order\OrderItem::where(['order_id' => $id])->select();
|
$item_list = \app\admin\model\shopro\order\OrderItem::where(['order_id' => $id])->select();
|
||||||
return $this->success('操作成功', null, $item_list);
|
return $this->success('操作成功', null, $item_list);
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 订单与售后退款异常捕获单元测试 (TDD)
|
||||||
|
*/
|
||||||
|
|
||||||
|
class OrderRefundExceptionCatchTest
|
||||||
|
{
|
||||||
|
private $passed = 0;
|
||||||
|
private $failed = 0;
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
echo "=== 开始执行 退款控制器异常捕获单元测试 (TDD) ===\n";
|
||||||
|
$this->testAftersaleRefundCatchesThrowable();
|
||||||
|
$this->testOrderRefundCatchesThrowable();
|
||||||
|
|
||||||
|
echo "\n测试结果: {$this->passed} 通过, {$this->failed} 失败\n";
|
||||||
|
if ($this->failed > 0) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function assert($condition, $message)
|
||||||
|
{
|
||||||
|
if ($condition) {
|
||||||
|
echo " [PASS] {$message}\n";
|
||||||
|
$this->passed++;
|
||||||
|
} else {
|
||||||
|
echo " [FAIL] {$message}\n";
|
||||||
|
$this->failed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试 Aftersale 控制器 refund 方法是否具备 try catch (\Throwable $e) 与 $this->error($e->getMessage())
|
||||||
|
*/
|
||||||
|
public function testAftersaleRefundCatchesThrowable()
|
||||||
|
{
|
||||||
|
$filePath = __DIR__ . '/../application/admin/controller/shopro/order/Aftersale.php';
|
||||||
|
$this->assert(is_file($filePath), "Aftersale.php 控制器必须存在");
|
||||||
|
|
||||||
|
$content = file_get_contents($filePath);
|
||||||
|
|
||||||
|
// 提取 refund 方法体
|
||||||
|
$startPos = strpos($content, 'function refund(');
|
||||||
|
$this->assert($startPos !== false, "Aftersale.php 必须包含 refund 方法");
|
||||||
|
|
||||||
|
$nextFuncPos = strpos($content, 'public function ', $startPos + 20);
|
||||||
|
$methodBody = substr($content, $startPos, $nextFuncPos - $startPos);
|
||||||
|
|
||||||
|
$hasTryCatch = preg_match('/try\s*\{\s*Db::transaction\(.+?\}\s*catch\s*\(\\\\?Throwable\s+\$e\)\s*\{\s*\$this->error\(\$e->getMessage\(\)\);\s*\}/s', $methodBody);
|
||||||
|
$this->assert((bool)$hasTryCatch, "Aftersale.php 的 refund 方法必须使用 try...catch(\\Throwable \$e) 包裹 Db::transaction 并在失败时调用 \$this->error(\$e->getMessage())");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试 Order 控制器 refund 方法是否具备 try catch (\Throwable $e) 与 $this->error($e->getMessage())
|
||||||
|
*/
|
||||||
|
public function testOrderRefundCatchesThrowable()
|
||||||
|
{
|
||||||
|
$filePath = __DIR__ . '/../application/admin/controller/shopro/order/Order.php';
|
||||||
|
$this->assert(is_file($filePath), "Order.php 控制器必须存在");
|
||||||
|
|
||||||
|
$content = file_get_contents($filePath);
|
||||||
|
|
||||||
|
// 提取 refund 方法体
|
||||||
|
$startPos = strpos($content, 'function refund(');
|
||||||
|
$this->assert($startPos !== false, "Order.php 必须包含 refund 方法");
|
||||||
|
|
||||||
|
$nextFuncPos = strpos($content, 'public function ', $startPos + 20);
|
||||||
|
$methodBody = substr($content, $startPos, $nextFuncPos - $startPos);
|
||||||
|
|
||||||
|
$hasTryCatch = preg_match('/try\s*\{\s*Db::transaction\(.+?\}\s*catch\s*\(\\\\?Throwable\s+\$e\)\s*\{\s*\$this->error\(\$e->getMessage\(\)\);\s*\}/s', $methodBody);
|
||||||
|
$this->assert((bool)$hasTryCatch, "Order.php 的 refund 方法必须使用 try...catch(\\Throwable \$e) 包裹 Db::transaction 并在失败时调用 \$this->error(\$e->getMessage())");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$test = new OrderRefundExceptionCatchTest();
|
||||||
|
$test->run();
|
||||||
在新工单中引用
屏蔽一个用户