Просмотр исходного кода

改进debug:reproduce-error命令,根据响应头智能显示内容

- 修改displayResponse方法,使用响应头的Content-Type而非请求类型来决定显示方式
- 新增响应头信息显示,包含所有HTTP响应头
- 新增多种Content-Type检测方法:JSON、HTML、Protobuf、文本等
- 新增智能内容显示功能:
  * JSON响应:解析并格式化显示
  * HTML响应:提取标题、内容预览、错误页面检测
  * Protobuf响应:显示二进制信息和Base64编码
  * 文本响应:直接显示内容
  * 未知类型:智能检测并选择最佳显示方式
- 更新命令描述和帮助信息
- 通过全面测试验证所有功能正常工作
AI Assistant 6 месяцев назад
Родитель
Сommit
a67ee3e7a0

+ 142 - 0
AiWork/2025年06月/190211-URS余额检查汇率转换修复.md

@@ -0,0 +1,142 @@
+# URS余额检查汇率转换修复
+
+**时间**: 2025年06月19日 02:11:14 CST  
+**任务**: 修复URS余额检查中汇率转换导致的单位不一致问题
+
+## 问题描述
+
+用户报告请求68993679,金额1,配置汇率300时:
+- **返回结果**: `"principal_total": "1", "fee_total": "3.0000", "required_total": "4.0000"`
+- **期望结果**: 最少300个内部币
+
+## 问题分析
+
+### 根本原因
+在 `ThirdParty/Urs/Webhook/UrsCheckWebhook.php` 的 `processCheck` 方法中存在单位不一致问题:
+
+1. **汇率转换正确**: `calculateWithdrawFee` 方法中正确将外部金额1转换为内部金额300
+2. **手续费计算正确**: 基于内部金额300计算手续费3.0000
+3. **本金使用错误**: `principalAmount = $amount` 使用的是外部金额1,而不是内部金额300
+4. **单位混乱**: 本金用外部金额,手续费用内部金额,导致总额计算错误
+
+### 问题流程
+```
+外部金额1 → 汇率300 → 内部金额300
+                    ↓
+                手续费计算(基于内部金额300) → 3.0000
+                    ↓
+本金1(外部) + 手续费3.0000(内部) = 4.0000 ❌
+```
+
+### 正确流程
+```
+外部金额1 → 汇率300 → 内部金额300
+                    ↓
+                手续费计算(基于内部金额300) → 3.0000
+                    ↓
+本金300(内部) + 手续费3.0000(内部) = 303.0000 ✅
+```
+
+## 修复方案
+
+### 1. 获取汇率信息
+在计算手续费前先获取转账应用配置:
+```php
+// 4. 获取转账应用配置以获取汇率
+$transferApp = \App\Module\Transfer\Services\TransferThirdPartyService::getTransferAppByThirdPartyId($thirdPartyAppId);
+```
+
+### 2. 计算内部金额
+```php
+// 5. 将外部金额转换为内部金额(提现:外部金额转内部金额)
+$internalAmount = bcmul($amount, (string) $transferApp->exchange_rate, 4);
+```
+
+### 3. 使用内部金额作为本金
+```php
+// 7. 计算所需总金额(内部本金 + 手续费)
+$principalAmount = $internalAmount; // 使用内部金额作为本金
+$feeAmount = $feeDto->feeAmount;
+$requiredTotal = bcadd($principalAmount, $feeAmount, 4);
+```
+
+## 修复验证
+
+### 测试数据
+- **用户ID**: 10002
+- **外部金额**: 1
+- **汇率**: 300
+- **手续费率**: 1%
+
+### 修复前结果
+```json
+{
+    "check": true,
+    "diamond_balance": "499,781.0000",
+    "principal_total": "1",           // ❌ 外部金额
+    "fee_total": "3.0000",           // 基于内部金额计算
+    "required_total": "4.0000",      // ❌ 单位不一致
+    "message": "余额充足,允许提取"
+}
+```
+
+### 修复后结果
+```json
+{
+    "check": true,
+    "diamond_balance": "499,781.0000",
+    "principal_total": "300.0000",   // ✅ 内部金额
+    "fee_total": "3.0000",          // 基于内部金额计算
+    "required_total": "303.0000",   // ✅ 单位一致
+    "message": "余额充足,允许提取"
+}
+```
+
+### 验证结果
+- ✅ **本金差异**: +299 (1 × 300汇率 - 1 = 299)
+- ✅ **总额差异**: +299 (303 - 4 = 299)
+- ✅ **单位一致**: 本金、手续费、总额都使用内部金额
+
+## 测试文件
+
+### 1. `test_urs_fix_verification.php`
+- 使用原始请求数据验证修复效果
+- 对比修复前后的响应差异
+- 验证汇率转换和单位一致性
+
+### 2. `test_urs_check_fix.php`
+- 通过反射调用Webhook处理器
+- 测试不同金额的手续费计算
+- 验证DTO属性和计算逻辑
+
+### 3. `test_webhook_api.php`
+- 直接调用HTTP API测试
+- 验证完整的请求响应流程
+
+## 影响范围
+
+### 修改文件
+- `ThirdParty/Urs/Webhook/UrsCheckWebhook.php`
+
+### 影响功能
+- URS余额检查API (`/thirdParty/webhook/urs/check`)
+- 所有依赖该API的URS提现功能
+
+### 向后兼容性
+- ✅ 不影响API接口结构
+- ✅ 只修复计算逻辑,不改变字段定义
+- ⚠️ 返回值会发生变化(principal_total和required_total会增大)
+
+## 部署注意事项
+
+1. **数据一致性**: 修复后的返回值会显著增大,需要确保调用方能正确处理
+2. **余额检查**: 用户余额需要足够支付内部金额,而不是外部金额
+3. **监控告警**: 可能会触发余额不足的告警,这是正常现象
+
+## 总结
+
+此次修复解决了URS余额检查中汇率转换导致的单位不一致问题,确保:
+- 本金使用正确的内部金额(外部金额 × 汇率)
+- 手续费基于内部金额计算
+- 总额计算基于统一的内部金额单位
+- 提高了系统的准确性和一致性

+ 222 - 14
app/Console/Commands/ReproduceErrorCommand.php

@@ -15,6 +15,7 @@ use UCore\Helper\Logger;
  *
  * 通过 sys_request_logs 表的记录来复现请求,用于调试和错误排查
  * 默认使用 post 字段的原始请求数据,根据 headers 中的 Content-Type 自动检测数据类型
+ * 根据响应的 Content-Type 头智能显示响应内容(JSON、HTML、Protobuf、文本等)
  *
  * 使用示例:
  * php artisan debug:reproduce-error 68981433                                    # 使用ID查找,自动选择数据源
@@ -40,7 +41,7 @@ class ReproduceErrorCommand extends Command
      *
      * @var string
      */
-    protected $description = '通过请求日志记录复现错误请求,用于调试和问题排查。支持运行前清空日志文件,默认使用post字段数据并根据headers自动检测数据类型';
+    protected $description = '通过请求日志记录复现错误请求,用于调试和问题排查。支持运行前清空日志文件,默认使用post字段数据并根据headers自动检测数据类型,根据响应Content-Type智能显示内容';
 
     /**
      * HTTP 客户端
@@ -147,7 +148,7 @@ class ReproduceErrorCommand extends Command
 
         // 输出结果
         $this->line("响应内容:");
-        $this->displayResponse($response, $requestData['type']);
+        $this->displayResponse($response);
 
         return 0;
     }
@@ -451,36 +452,243 @@ class ReproduceErrorCommand extends Command
     /**
      * 显示响应内容
      *
-     * @param array $response 响应数据
-     * @param string $requestType 请求类型
+     * @param array $response 响应数据(包含status_code、headers、body)
      */
-    protected function displayResponse(array $response, string $requestType): void
+    protected function displayResponse(array $response): void
     {
         $this->line("状态码: " . $response['status_code']);
 
-        if ($requestType === 'json') {
-            // JSON 响应,尝试解析
-            $jsonData = json_decode($response['body'], true);
+        // 显示响应头信息
+        $this->line("响应头:");
+        foreach ($response['headers'] as $name => $values) {
+            $value = is_array($values) ? implode(', ', $values) : $values;
+            $this->line("  {$name}: {$value}");
+        }
+
+        // 从响应头中提取Content-Type
+        $responseContentType = $this->extractResponseContentType($response['headers']);
+        $this->line("检测到响应Content-Type: " . ($responseContentType ?: '未知'));
+
+        // 根据响应Content-Type决定显示方式
+        $responseBody = $response['body'];
+        $bodyLength = strlen($responseBody);
+
+        $this->line("响应体长度: {$bodyLength} 字节");
+
+        if ($this->isJsonContentType($responseContentType)) {
+            // JSON 响应
+            $this->line("按JSON格式解析响应:");
+            $jsonData = json_decode($responseBody, true);
             if ($jsonData !== null) {
                 dump($jsonData);
             } else {
-                $this->warn("响应不是有效的 JSON 格式");
-                $this->line("原始响应: " . substr($response['body'], 0, 500) . "...");
+                $this->warn("响应Content-Type为JSON但解析失败");
+                $this->displayRawContent($responseBody);
             }
-        } else {
+        } elseif ($this->isProtobufContentType($responseContentType)) {
             // Protobuf 二进制响应
-            $this->line("二进制响应长度: " . strlen($response['body']) . " 字节");
-            $this->line("Base64 编码: " . substr(base64_encode($response['body']), 0, 200) . "...");
+            $this->line("按Protobuf格式处理响应:");
+            $this->line("Base64 编码: " . substr(base64_encode($responseBody), 0, 200) . "...");
 
             // 尝试解析为 JSON(某些情况下服务器可能返回 JSON)
-            $jsonData = json_decode($response['body'], true);
+            $jsonData = json_decode($responseBody, true);
             if ($jsonData !== null) {
                 $this->line("响应可解析为 JSON:");
                 dump($jsonData);
             } else {
                 $this->line("响应为二进制 Protobuf 数据,无法直接显示");
             }
+        } elseif ($this->isHtmlContentType($responseContentType)) {
+            // HTML 响应
+            $this->line("按HTML格式处理响应:");
+            $this->displayHtmlContent($responseBody);
+        } elseif ($this->isTextContentType($responseContentType)) {
+            // 纯文本响应
+            $this->line("按文本格式显示响应:");
+            $this->line($responseBody);
+        } else {
+            // 未知类型,尝试智能检测
+            $this->line("未知Content-Type,尝试智能检测:");
+            $this->smartDisplayContent($responseBody);
+        }
+    }
+
+    /**
+     * 从响应头中提取Content-Type
+     *
+     * @param array $headers 响应头数组
+     * @return string|null
+     */
+    protected function extractResponseContentType(array $headers): ?string
+    {
+        // 尝试不同的Content-Type键名(大小写不敏感)
+        $contentTypeKeys = ['Content-Type', 'content-type', 'Content-type', 'CONTENT-TYPE'];
+
+        foreach ($contentTypeKeys as $key) {
+            if (isset($headers[$key])) {
+                $value = $headers[$key];
+                // 如果是数组,取第一个值
+                if (is_array($value)) {
+                    $value = $value[0] ?? '';
+                }
+                // 只取分号前的部分(去掉charset等参数)
+                return trim(explode(';', $value)[0]);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * 判断是否为JSON内容类型
+     *
+     * @param string|null $contentType
+     * @return bool
+     */
+    protected function isJsonContentType(?string $contentType): bool
+    {
+        if (!$contentType) {
+            return false;
+        }
+
+        $jsonTypes = [
+            'application/json',
+            'text/json',
+            'application/ld+json'
+        ];
+
+        return in_array(strtolower($contentType), $jsonTypes);
+    }
+
+    /**
+     * 判断是否为Protobuf内容类型
+     *
+     * @param string|null $contentType
+     * @return bool
+     */
+    protected function isProtobufContentType(?string $contentType): bool
+    {
+        if (!$contentType) {
+            return false;
+        }
+
+        $protobufTypes = [
+            'application/x-protobuf',
+            'application/protobuf',
+            'application/octet-stream'
+        ];
+
+        return in_array(strtolower($contentType), $protobufTypes);
+    }
+
+    /**
+     * 判断是否为HTML内容类型
+     *
+     * @param string|null $contentType
+     * @return bool
+     */
+    protected function isHtmlContentType(?string $contentType): bool
+    {
+        if (!$contentType) {
+            return false;
+        }
+
+        $htmlTypes = [
+            'text/html',
+            'application/xhtml+xml'
+        ];
+
+        return in_array(strtolower($contentType), $htmlTypes);
+    }
+
+    /**
+     * 判断是否为文本内容类型
+     *
+     * @param string|null $contentType
+     * @return bool
+     */
+    protected function isTextContentType(?string $contentType): bool
+    {
+        if (!$contentType) {
+            return false;
+        }
+
+        return str_starts_with(strtolower($contentType), 'text/');
+    }
+
+    /**
+     * 显示HTML内容
+     *
+     * @param string $content
+     */
+    protected function displayHtmlContent(string $content): void
+    {
+        // 检查是否包含错误页面标识
+        if (str_contains($content, '<title>') && str_contains($content, '</title>')) {
+            preg_match('/<title>(.*?)<\/title>/i', $content, $matches);
+            $title = $matches[1] ?? '未知';
+            $this->line("HTML页面标题: {$title}");
         }
+
+        // 显示部分内容
+        $preview = substr(strip_tags($content), 0, 500);
+        $this->line("HTML内容预览: " . $preview . "...");
+
+        // 检查是否为错误页面
+        if (str_contains($content, 'error') || str_contains($content, 'Error') || str_contains($content, 'exception')) {
+            $this->warn("检测到可能的错误页面");
+        }
+    }
+
+    /**
+     * 显示原始内容
+     *
+     * @param string $content
+     */
+    protected function displayRawContent(string $content): void
+    {
+        $this->line("原始响应内容:");
+        if (strlen($content) > 1000) {
+            $this->line(substr($content, 0, 1000) . "...");
+            $this->line("(内容过长,已截断显示)");
+        } else {
+            $this->line($content);
+        }
+    }
+
+    /**
+     * 智能检测并显示内容
+     *
+     * @param string $content
+     */
+    protected function smartDisplayContent(string $content): void
+    {
+        // 尝试JSON解析
+        $jsonData = json_decode($content, true);
+        if ($jsonData !== null) {
+            $this->line("内容可解析为JSON:");
+            dump($jsonData);
+            return;
+        }
+
+        // 检查是否为HTML
+        if (str_contains($content, '<html') || str_contains($content, '<!DOCTYPE')) {
+            $this->line("检测到HTML内容:");
+            $this->displayHtmlContent($content);
+            return;
+        }
+
+        // 检查是否为二进制数据
+        if (!mb_check_encoding($content, 'UTF-8')) {
+            $this->line("检测到二进制数据:");
+            $this->line("数据长度: " . strlen($content) . " 字节");
+            $this->line("Base64 编码: " . substr(base64_encode($content), 0, 200) . "...");
+            return;
+        }
+
+        // 默认按文本显示
+        $this->line("按文本格式显示:");
+        $this->displayRawContent($content);
     }
 
     /**

+ 0 - 105
test_urs_check_fix.php

@@ -1,105 +0,0 @@
-<?php
-
-require_once __DIR__ . '/vendor/autoload.php';
-
-// 设置Laravel环境
-$app = require_once __DIR__ . '/bootstrap/app.php';
-$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();
-
-use ThirdParty\Urs\Webhook\UrsCheckWebhook;
-use App\Module\ThirdParty\Models\ThirdPartyService as ServiceModel;
-use Illuminate\Http\Request;
-
-echo "=== URS余额检查修复测试 ===\n\n";
-
-// 测试数据
-$testCases = [
-    [
-        'name' => '正常提现测试(金额1,汇率300)',
-        'user_id' => 68993679,
-        'amount' => '1',
-        'expected_principal' => '300.0000', // 应该是内部金额
-        'expected_fee_min' => '3.0000', // 假设1%手续费
-    ],
-    [
-        'name' => '大额提现测试(金额10,汇率300)',
-        'user_id' => 68993679,
-        'amount' => '10',
-        'expected_principal' => '3000.0000',
-        'expected_fee_min' => '30.0000',
-    ]
-];
-
-foreach ($testCases as $case) {
-    echo "--- {$case['name']} ---\n";
-
-    try {
-        // 创建模拟的Request对象
-        $request = new Request([
-            'user_id' => $case['user_id'],
-            'amount' => $case['amount']
-        ]);
-
-        // 获取URS服务配置
-        $service = ServiceModel::find(11);
-        if (!$service) {
-            echo "错误:未找到服务配置 ID: 11\n";
-            continue;
-        }
-
-        // 创建Webhook处理器
-        $webhook = new UrsCheckWebhook('urs', $request, $service);
-
-        // 使用反射调用protected方法
-        $reflection = new ReflectionClass($webhook);
-        $method = $reflection->getMethod('processCheck');
-        $method->setAccessible(true);
-
-        $result = $method->invoke($webhook, $request);
-        
-        echo "请求参数:\n";
-        echo "  用户ID: {$case['user_id']}\n";
-        echo "  外部金额: {$case['amount']}\n";
-        
-        echo "\n返回结果:\n";
-        echo "  检查结果: " . ($result['check'] ? '通过' : '失败') . "\n";
-        echo "  钻石余额: {$result['diamond_balance']}\n";
-        echo "  本金总额: {$result['principal_total']}\n";
-        echo "  手续费: {$result['fee_total']}\n";
-        echo "  所需总额: {$result['required_total']}\n";
-        echo "  消息: {$result['message']}\n";
-        
-        // 验证修复结果
-        echo "\n验证结果:\n";
-        
-        // 检查本金是否正确(应该是内部金额)
-        if ($result['principal_total'] === $case['expected_principal']) {
-            echo "  ✓ 本金金额正确(内部金额)\n";
-        } else {
-            echo "  ✗ 本金金额错误,期望: {$case['expected_principal']},实际: {$result['principal_total']}\n";
-        }
-        
-        // 检查手续费是否合理
-        if (bccomp($result['fee_total'], $case['expected_fee_min'], 4) >= 0) {
-            echo "  ✓ 手续费金额合理\n";
-        } else {
-            echo "  ✗ 手续费金额可能有误,期望最少: {$case['expected_fee_min']},实际: {$result['fee_total']}\n";
-        }
-        
-        // 检查总额计算
-        $calculatedTotal = bcadd($result['principal_total'], $result['fee_total'], 4);
-        if ($calculatedTotal === $result['required_total']) {
-            echo "  ✓ 总额计算正确\n";
-        } else {
-            echo "  ✗ 总额计算错误,期望: {$calculatedTotal},实际: {$result['required_total']}\n";
-        }
-        
-    } catch (\Exception $e) {
-        echo "测试失败: " . $e->getMessage() . "\n";
-        echo "错误追踪: " . $e->getTraceAsString() . "\n";
-    }
-    
-    echo "\n" . str_repeat("-", 50) . "\n\n";
-}
-
-echo "=== 测试完成 ===\n";

+ 0 - 114
test_urs_fix_verification.php

@@ -1,114 +0,0 @@
-<?php
-
-require_once __DIR__ . '/vendor/autoload.php';
-
-// 设置Laravel环境
-$app = require_once __DIR__ . '/bootstrap/app.php';
-$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();
-
-use ThirdParty\Urs\Webhook\UrsCheckWebhook;
-use App\Module\ThirdParty\Models\ThirdPartyService as ServiceModel;
-use Illuminate\Http\Request;
-
-echo "=== URS修复验证测试 ===\n\n";
-
-// 从数据库获取的原始请求数据
-$originalRequestData = [
-    'user_id' => '10002',
-    'amount' => '1',
-    'order_id' => '0'
-];
-
-echo "原始请求数据: " . json_encode($originalRequestData, JSON_UNESCAPED_UNICODE) . "\n";
-echo "期望结果: principal_total应该是300.0000(内部金额),而不是1(外部金额)\n\n";
-
-try {
-    // 创建模拟的Request对象
-    $request = new Request($originalRequestData);
-    
-    // 获取URS服务配置
-    $service = ServiceModel::find(11);
-    if (!$service) {
-        echo "错误:未找到服务配置 ID: 11\n";
-        exit(1);
-    }
-    
-    // 创建Webhook处理器
-    $webhook = new UrsCheckWebhook('urs', $request, $service);
-    
-    // 使用反射调用protected方法
-    $reflection = new ReflectionClass($webhook);
-    $method = $reflection->getMethod('processCheck');
-    $method->setAccessible(true);
-    
-    $result = $method->invoke($webhook, $request);
-    
-    echo "=== 修复后的响应 ===\n";
-    echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n\n";
-    
-    // 对比修复前后的结果
-    echo "=== 修复验证 ===\n";
-    
-    $principalTotal = $result['principal_total'] ?? 'N/A';
-    $feeTotal = $result['fee_total'] ?? 'N/A';
-    $requiredTotal = $result['required_total'] ?? 'N/A';
-    
-    echo "本金总额: {$principalTotal}\n";
-    echo "手续费: {$feeTotal}\n";
-    echo "所需总额: {$requiredTotal}\n\n";
-    
-    // 验证修复结果
-    if ($principalTotal === '300.0000') {
-        echo "✓ 修复成功:本金已正确转换为内部金额(300.0000)\n";
-    } elseif ($principalTotal === '1') {
-        echo "✗ 修复失败:本金仍然是外部金额(1)\n";
-    } else {
-        echo "? 本金金额异常:{$principalTotal}\n";
-    }
-    
-    // 验证手续费计算
-    if (is_numeric($feeTotal) && floatval($feeTotal) >= 3.0) {
-        echo "✓ 手续费计算正确:基于内部金额300计算\n";
-    } else {
-        echo "✗ 手续费计算可能有误:{$feeTotal}\n";
-    }
-    
-    // 验证总额计算
-    if (is_numeric($requiredTotal) && floatval($requiredTotal) >= 300.0) {
-        echo "✓ 总额计算正确:基于内部金额\n";
-    } else {
-        echo "✗ 总额计算可能有误:{$requiredTotal}\n";
-    }
-    
-    // 对比原始响应
-    echo "\n=== 对比原始响应 ===\n";
-    $originalResponse = '{"check":true,"diamond_balance":"499,781.0000","principal_total":"1","fee_total":"3.0000","required_total":"4.0000","message":"余额充足,允许提取"}';
-    $originalData = json_decode($originalResponse, true);
-    
-    echo "原始响应:\n";
-    echo "  本金总额: {$originalData['principal_total']}\n";
-    echo "  手续费: {$originalData['fee_total']}\n";
-    echo "  所需总额: {$originalData['required_total']}\n\n";
-    
-    echo "修复后响应:\n";
-    echo "  本金总额: {$principalTotal}\n";
-    echo "  手续费: {$feeTotal}\n";
-    echo "  所需总额: {$requiredTotal}\n\n";
-    
-    // 计算差异
-    if (is_numeric($principalTotal) && is_numeric($originalData['principal_total'])) {
-        $principalDiff = floatval($principalTotal) - floatval($originalData['principal_total']);
-        echo "本金差异: +{$principalDiff} (应该是+299,因为汇率是300)\n";
-    }
-    
-    if (is_numeric($requiredTotal) && is_numeric($originalData['required_total'])) {
-        $totalDiff = floatval($requiredTotal) - floatval($originalData['required_total']);
-        echo "总额差异: +{$totalDiff}\n";
-    }
-    
-} catch (\Exception $e) {
-    echo "测试失败: " . $e->getMessage() . "\n";
-    echo "错误追踪: " . $e->getTraceAsString() . "\n";
-}
-
-echo "\n=== 测试完成 ===\n";

+ 0 - 102
test_webhook_api.php

@@ -1,102 +0,0 @@
-<?php
-
-require_once __DIR__ . '/vendor/autoload.php';
-
-// 设置Laravel环境
-$app = require_once __DIR__ . '/bootstrap/app.php';
-$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();
-
-echo "=== URS Webhook API 测试 ===\n\n";
-
-// 测试URL
-$url = 'http://kku_laravel.local.gd/thirdParty/webhook/urs/check';
-
-// 测试数据
-$testData = [
-    'user_id' => 68993679,
-    'amount' => '1'
-];
-
-echo "测试URL: {$url}\n";
-echo "测试数据: " . json_encode($testData, JSON_UNESCAPED_UNICODE) . "\n\n";
-
-// 发送POST请求
-$ch = curl_init();
-curl_setopt($ch, CURLOPT_URL, $url);
-curl_setopt($ch, CURLOPT_POST, true);
-curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($testData));
-curl_setopt($ch, CURLOPT_HTTPHEADER, [
-    'Content-Type: application/json',
-    'Accept: application/json'
-]);
-curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-curl_setopt($ch, CURLOPT_TIMEOUT, 30);
-
-$response = curl_exec($ch);
-$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
-$error = curl_error($ch);
-curl_close($ch);
-
-if ($error) {
-    echo "CURL错误: {$error}\n";
-    exit(1);
-}
-
-echo "HTTP状态码: {$httpCode}\n";
-echo "响应内容:\n";
-
-if ($response) {
-    $responseData = json_decode($response, true);
-    if ($responseData) {
-        echo json_encode($responseData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n\n";
-        
-        // 验证修复结果
-        echo "=== 修复验证 ===\n";
-        
-        if (isset($responseData['principal_total'])) {
-            $principalTotal = $responseData['principal_total'];
-            echo "本金总额: {$principalTotal}\n";
-            
-            // 检查是否是内部金额(应该是300,不是1)
-            if ($principalTotal === '300.0000') {
-                echo "✓ 修复成功:本金已正确转换为内部金额\n";
-            } elseif ($principalTotal === '1') {
-                echo "✗ 修复失败:本金仍然是外部金额\n";
-            } else {
-                echo "? 本金金额异常:{$principalTotal}\n";
-            }
-        }
-        
-        if (isset($responseData['fee_total'])) {
-            $feeTotal = $responseData['fee_total'];
-            echo "手续费: {$feeTotal}\n";
-            
-            // 检查手续费是否合理(基于内部金额300计算)
-            if (floatval($feeTotal) >= 3.0) {
-                echo "✓ 手续费计算正确:基于内部金额计算\n";
-            } else {
-                echo "✗ 手续费计算可能有误\n";
-            }
-        }
-        
-        if (isset($responseData['required_total'])) {
-            $requiredTotal = $responseData['required_total'];
-            echo "所需总额: {$requiredTotal}\n";
-            
-            // 检查总额是否合理(应该是300+手续费)
-            if (floatval($requiredTotal) >= 300.0) {
-                echo "✓ 总额计算正确:基于内部金额\n";
-            } else {
-                echo "✗ 总额计算可能有误\n";
-            }
-        }
-        
-    } else {
-        echo "响应不是有效的JSON格式\n";
-        echo $response . "\n";
-    }
-} else {
-    echo "无响应内容\n";
-}
-
-echo "\n=== 测试完成 ===\n";