fix: 优化退款防重与防多退机制,增加防重锁、事务排他行锁及汇付幂等对齐
这个提交包含在:
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* 订单与售后退款异常捕获单元测试 (TDD)
|
||||
* 订单与售后退款异常捕获与防重复退款单元测试 (TDD)
|
||||
*/
|
||||
|
||||
class OrderRefundExceptionCatchTest
|
||||
@@ -10,9 +10,12 @@ class OrderRefundExceptionCatchTest
|
||||
|
||||
public function run()
|
||||
{
|
||||
echo "=== 开始执行 退款控制器异常捕获单元测试 (TDD) ===\n";
|
||||
echo "=== 开始执行 退款控制器与防重复退款单元测试 (TDD) ===\n";
|
||||
$this->testAftersaleRefundCatchesThrowable();
|
||||
$this->testAftersaleRefundAntiDuplicateLockAndRowLock();
|
||||
$this->testOrderRefundCatchesThrowable();
|
||||
$this->testOrderRefundAntiDuplicateLockAndRowLock();
|
||||
$this->testHuifuRefundIdempotentHandling();
|
||||
|
||||
echo "\n测试结果: {$this->passed} 通过, {$this->failed} 失败\n";
|
||||
if ($this->failed > 0) {
|
||||
@@ -31,46 +34,76 @@ class OrderRefundExceptionCatchTest
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 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())");
|
||||
$hasTryCatch = preg_match('/try\s*\{\s*Db::transaction\(.+?\}\s*catch\s*\(\\\\?Throwable\s+\$e\)\s*\{.+?\$this->error\(\$e->getMessage\(\)\);\s*\}/s', $methodBody);
|
||||
$this->assert((bool)$hasTryCatch, "Aftersale.php 的 refund 方法必须使用 try...catch(\\Throwable \$e) 包裹 Db::transaction");
|
||||
}
|
||||
|
||||
public function testAftersaleRefundAntiDuplicateLockAndRowLock()
|
||||
{
|
||||
$filePath = __DIR__ . '/../application/admin/controller/shopro/order/Aftersale.php';
|
||||
$content = file_get_contents($filePath);
|
||||
$startPos = strpos($content, 'function refund(');
|
||||
$nextFuncPos = strpos($content, 'public function ', $startPos + 20);
|
||||
$methodBody = substr($content, $startPos, $nextFuncPos - $startPos);
|
||||
|
||||
$hasCacheLock = strpos($methodBody, 'lock:refund:aftersale:') !== false;
|
||||
$this->assert($hasCacheLock, "Aftersale.php 的 refund 方法必须使用 Cache 锁防止重复点击/并发提交");
|
||||
|
||||
$hasRowLock = strpos($methodBody, 'lock(true)') !== false;
|
||||
$this->assert($hasRowLock, "Aftersale.php 的 refund 方法事务内必须使用 lock(true) 排他行锁防多退");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 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())");
|
||||
$hasTryCatch = preg_match('/try\s*\{\s*Db::transaction\(.+?\}\s*catch\s*\(\\\\?Throwable\s+\$e\)\s*\{.+?\$this->error\(\$e->getMessage\(\)\);\s*\}/s', $methodBody);
|
||||
$this->assert((bool)$hasTryCatch, "Order.php 的 refund 方法必须使用 try...catch(\\Throwable \$e) 包裹 Db::transaction");
|
||||
}
|
||||
|
||||
public function testOrderRefundAntiDuplicateLockAndRowLock()
|
||||
{
|
||||
$filePath = __DIR__ . '/../application/admin/controller/shopro/order/Order.php';
|
||||
$content = file_get_contents($filePath);
|
||||
$startPos = strpos($content, 'function refund(');
|
||||
$nextFuncPos = strpos($content, 'public function ', $startPos + 20);
|
||||
$methodBody = substr($content, $startPos, $nextFuncPos - $startPos);
|
||||
|
||||
$hasCacheLock = strpos($methodBody, 'lock:refund:order:') !== false;
|
||||
$this->assert($hasCacheLock, "Order.php 的 refund 方法必须使用 Cache 锁防止重复点击/并发提交");
|
||||
|
||||
$hasRowLock = strpos($methodBody, 'lock(true)') !== false;
|
||||
$this->assert($hasRowLock, "Order.php 的 refund 方法事务内必须使用 lock(true) 排他行锁防多退");
|
||||
}
|
||||
|
||||
public function testHuifuRefundIdempotentHandling()
|
||||
{
|
||||
$filePath = __DIR__ . '/../addons/shopro_huifu/library/HuifuRefundService.php';
|
||||
$this->assert(is_file($filePath), "HuifuRefundService.php 必须存在");
|
||||
|
||||
$content = file_get_contents($filePath);
|
||||
$hasIdempotent = strpos($content, '23000003') !== false || strpos($content, 'isAlreadyRefunded') !== false;
|
||||
$this->assert($hasIdempotent, "HuifuRefundService 必须包含针对汇付已退款的幂等核查与对齐机制");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户