testAftersaleRefundCatchesThrowable(); $this->testAftersaleRefundAntiDuplicateLockAndRowLock(); $this->testOrderRefundCatchesThrowable(); $this->testOrderRefundAntiDuplicateLockAndRowLock(); $this->testHuifuRefundIdempotentHandling(); 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++; } } public function testAftersaleRefundCatchesThrowable() { $filePath = __DIR__ . '/../application/admin/controller/shopro/order/Aftersale.php'; $this->assert(is_file($filePath), "Aftersale.php 控制器必须存在"); $content = file_get_contents($filePath); $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*\{.+?\$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) 排他行锁防多退"); } public function testOrderRefundCatchesThrowable() { $filePath = __DIR__ . '/../application/admin/controller/shopro/order/Order.php'; $this->assert(is_file($filePath), "Order.php 控制器必须存在"); $content = file_get_contents($filePath); $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*\{.+?\$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 必须包含针对汇付已退款的幂等核查与对齐机制"); } } $test = new OrderRefundExceptionCatchTest(); $test->run();