Selaa lähdekoodia

fix(credit-balance-module-mt): 修复额度支付API用户ID检查安全漏洞

- 修复订单状态更新缺少用户ID检查的安全漏洞
- 在订单状态更新时添加用户ID检查:{ id: orderId, tenantId: user.tenantId, userId: user.id }
- 改进错误消息,包含用户ID信息以便调试
- 添加3个越权访问测试用例:
  - 测试用户尝试支付其他用户的订单应该返回404
  - 测试跨租户订单访问应该返回404
  - 测试正常支付流程的用户ID验证
- 更新故事文件任务状态和开发记录

🤖 Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 5 päivää sitten
vanhempi
sitoutus
2ad6aa2b18

+ 150 - 2
docs/stories/004.001.credit-balance-module-mt.story.md

@@ -108,6 +108,27 @@
     - [ ] 验证订单模块正确调用额度支付模块的`restoreBalanceForCancelOrder()`方法
     - [ ] 测试取消订单时,微信支付订单的退款逻辑(如果存在)
 
+  - [x] **补充:修复额度支付API的安全漏洞**
+    - [x] **问题发现**:额度支付API存在严重安全漏洞,支付金额从前端传递而不是从订单表查询
+    - [x] **问题分析**:
+      1. 小程序支付页面从路由参数获取`amount`并直接传递给额度支付API
+      2. 额度支付API没有验证传入金额是否与订单实际金额一致
+      3. 额度支付API没有检查订单的当前支付状态,可能导致重复支付
+      4. 额度支付API使用`referenceId`作为订单ID解析,但小程序传递的是订单号格式
+    - [x] **修复方案**:
+      1. 修改额度支付API,从订单表查询`payAmount`作为支付金额
+      2. 在额度支付前检查订单支付状态,只允许`PayStatus.UNPAID`状态的订单进行支付
+      3. 修改小程序传递订单ID而不是订单号作为`referenceId`
+      4. 添加订单状态验证逻辑,防止重复支付和无效状态订单支付
+    - [x] **安全影响**:前端可以传递任意金额进行额度支付,存在严重安全风险
+    - [x] **测试要求**:添加订单状态验证测试、金额验证测试、重复支付防护测试
+    - [x] **修复完成**:
+      - ✅ 修改额度支付API路由,添加订单状态验证和金额查询逻辑
+      - ✅ 更新PaymentDto Schema,移除`amount`字段
+      - ✅ 修改小程序传递订单ID而不是订单号
+      - ✅ 添加5个新的集成测试用例验证安全修复
+      - ✅ 所有30个测试通过,安全漏洞已修复
+
 
 - [x] **配置包依赖和导出** (AC: 3, 4)
   - [x] 配置package.json依赖关系(TypeORM、Hono等) (参考:`packages/advertisements-module-mt/package.json`)
@@ -116,6 +137,29 @@
   - [x] 配置Vitest测试环境 (参考:`packages/advertisements-module-mt/vitest.config.ts`)
   - [x] 确保包可以正确导入和使用
 
+- [x] **修复额度支付API的用户ID检查安全漏洞** (新发现的安全问题)
+  - [x] **问题发现**:额度支付API的订单状态更新缺少用户ID检查,存在越权更新风险
+  - [x] **修复方案**:
+    1. 在订单状态更新时添加用户ID检查:`{ id: orderId, tenantId: user.tenantId, userId: user.id }`
+    2. 改进错误消息,包含用户ID信息以便调试
+    3. 确保订单查询和更新都包含完整的用户ID验证
+  - [x] **修复完成**:
+    - ✅ 修改额度支付API路由,在订单状态更新时添加用户ID检查(`payment.mt.ts:162`)
+    - ✅ 改进错误消息:`订单不存在或无权访问,订单ID: ${orderId},用户ID: ${user.id}`
+    - ✅ 改进订单状态更新失败错误消息:包含用户ID信息
+
+- [x] **添加越权访问测试用例** (安全测试增强)
+  - [x] **测试场景1**:用户A尝试支付用户B的订单
+    - ✅ 测试用户尝试支付其他用户的订单应该返回404
+    - ✅ 验证订单状态保持不变(未支付)
+  - [x] **测试场景2**:跨租户订单访问
+    - ✅ 测试租户1的用户尝试支付租户2的订单应该返回404
+    - ✅ 验证租户隔离防护有效
+  - [x] **测试场景3**:正常支付流程的用户ID验证
+    - ✅ 测试正常支付流程仍然正常工作
+    - ✅ 验证订单状态正确更新为支付成功
+  - [x] **测试验证**:所有3个新的越权访问测试用例通过
+
 ## Dev Notes
 
 ### 技术栈信息 [Source: architecture/tech-stack.md]
@@ -390,6 +434,105 @@ packages/
      - ✅ 额度支付API集成测试验证:包含订单状态更新的测试用例通过
      - ✅ 微信支付模块部分测试通过:系统配置和支付路由集成测试通过
 
+19. ✅ **修复额度支付API严重安全漏洞**:已成功修复
+   - **问题发现**:额度支付API存在严重安全漏洞,支付金额从前端传递而不是从订单表查询
+   - **问题分析**:
+     1. 小程序支付页面从路由参数获取`amount`并直接传递给额度支付API(`mini/src/pages/payment/index.tsx:53,164`)
+     2. 额度支付API没有验证传入金额是否与订单实际金额一致(`packages/credit-balance-module-mt/src/routes/payment.mt.ts:82`)
+     3. 额度支付API没有检查订单的当前支付状态,可能导致重复支付
+     4. 额度支付API使用`referenceId`作为订单ID解析,但小程序传递的是订单号格式(`payment.mt.ts:91` vs `payment/index.tsx:165`)
+   - **安全影响**:前端可以传递任意金额进行额度支付,存在严重安全风险
+   - **修复方案**:
+     1. 修改额度支付API,从订单表查询`payAmount`作为支付金额
+     2. 在额度支付前检查订单支付状态,只允许`PayStatus.UNPAID`状态的订单进行支付
+     3. 修改小程序传递订单ID而不是订单号作为`referenceId`
+     4. 添加订单状态验证逻辑,防止重复支付和无效状态订单支付
+   - **修复完成**:
+     - ✅ 修改额度支付API路由(`packages/credit-balance-module-mt/src/routes/payment.mt.ts:72-188`):
+       - 添加订单ID验证和解析
+       - 查询订单信息并验证订单状态
+       - 使用订单的`payAmount`作为支付金额
+       - 添加完整的订单状态验证逻辑
+     - ✅ 更新PaymentDto Schema,移除`amount`字段(`packages/credit-balance-module-mt/src/schemas/index.ts:142-159`)
+     - ✅ 修改小程序支付页面(`mini/src/pages/payment/index.tsx:165`):
+       - 传递订单ID而不是订单号作为`referenceId`
+       - 移除`amount`参数传递
+     - ✅ 更新集成测试,添加5个新的测试用例验证安全修复:
+       - 测试拒绝支付已支付成功的订单
+       - 测试拒绝支付不存在的订单
+       - 测试拒绝支付无效的订单ID
+       - 测试拒绝支付金额为0的订单
+       - 更新现有测试使用订单ID
+     - ✅ 更新小程序单元测试验证API调用参数变更
+   - **测试验证**:
+     - ✅ 额度支付模块测试通过:30个测试全部通过(新增4个测试用例)
+     - ✅ 新增的订单状态验证测试全部通过
+     - ✅ 安全漏洞已修复,前端无法传递任意金额
+   - **修复效果**:
+     - 支付金额从订单表查询,前端无法篡改
+     - 订单状态验证防止重复支付
+     - 完整的错误处理和用户提示
+     - 保持事务一致性,确保数据安全
+
+20. ✅ **修复TypeORM decimal字段类型转换问题**:已成功修复
+   - **问题发现**:额度支付API在处理已有欠款的用户时出现数据库错误:`invalid input syntax for type numeric: "198.0199.00"`
+   - **问题分析**:
+     1. TypeORM的decimal字段可能返回字符串类型,而不是数字类型
+     2. 当用户已有欠款(如`usedAmount: 198.01`)时,`deductAmount`方法中的`beforeUsed + amount`会进行字符串拼接而不是数字相加
+     3. 错误信息`"198.0199.00"`是`"198.01" + 99 + ".00"`的字符串拼接结果
+     4. 集成测试没有覆盖已有欠款的情况,所有测试都从`usedAmount: 0.00`开始
+   - **根本原因**:
+     - `order.payAmount`(decimal字段)可能返回字符串`"99.00"`
+     - 在`deductAmount`方法中:`const newUsedAmount = beforeUsed + amount`
+     - 如果`amount`是字符串`"99.00"`,`beforeUsed`是数字`198.01`,JavaScript会进行字符串拼接
+   - **修复方案**:
+     1. 在额度支付API中确保`paymentAmount`是数字类型:`const paymentAmount = Number(order.payAmount)`
+     2. 添加`isNaN()`检查确保转换成功
+     3. 添加测试用例覆盖已有欠款的情况
+     4. 添加测试用例模拟TypeORM decimal字段返回字符串的情况
+   - **修复完成**:
+     - ✅ 修改额度支付API路由(`packages/credit-balance-module-mt/src/routes/payment.mt.ts:140`):
+       - 添加`Number()`类型转换:`const paymentAmount = Number(order.payAmount)`
+       - 添加`isNaN()`检查:`if (isNaN(paymentAmount) || paymentAmount <= 0)`
+     - ✅ 添加2个新的集成测试用例:
+       - `应该成功扣减额度当用户已有欠款时`:测试用户已有欠款`198.01`,支付`99.00`订单
+       - `应该正确处理decimal字段的字符串类型`:模拟TypeORM返回字符串金额的情况
+     - ✅ 所有32个测试通过(新增2个测试用例)
+   - **测试验证**:
+     - ✅ 新增的已有欠款测试通过:`198.01 + 99.00 = 297.01`
+     - ✅ 新增的decimal字符串类型测试通过
+     - ✅ 所有现有测试保持通过
+   - **经验教训**:
+     - TypeORM decimal字段可能返回字符串,需要显式类型转换
+     - 测试应该覆盖真实生产环境场景(用户已有欠款)
+     - 数据库字段类型转换是常见的错误来源,需要防御性编程
+
+21. ✅ **修复额度支付API的用户ID检查安全漏洞和添加越权访问测试**:已成功修复
+   - **问题发现**:额度支付API存在用户ID检查不完整的安全漏洞
+     - 订单状态更新缺少用户ID检查:`{ id: orderId, tenantId: user.tenantId }`(缺少`userId: user.id`)
+     - 存在潜在的越权更新风险
+   - **修复方案**:
+     1. 在订单状态更新时添加用户ID检查:`{ id: orderId, tenantId: user.tenantId, userId: user.id }`
+     2. 改进错误消息,包含用户ID信息以便调试
+     3. 添加完整的越权访问测试用例
+   - **修复完成**:
+     - ✅ 修改额度支付API路由(`packages/credit-balance-module-mt/src/routes/payment.mt.ts:162`):
+       - 在订单状态更新时添加用户ID检查
+       - 改进错误消息:`订单不存在或无权访问,订单ID: ${orderId},用户ID: ${user.id}`
+       - 改进订单状态更新失败错误消息:包含用户ID信息
+     - ✅ 添加3个新的越权访问测试用例(`packages/credit-balance-module-mt/tests/integration/credit-balance-routes.integration.test.ts:697-888`):
+       - 测试用户尝试支付其他用户的订单应该返回404
+       - 测试跨租户订单访问应该返回404
+       - 测试正常支付流程的用户ID验证
+     - ✅ 所有新的测试用例通过验证
+   - **安全影响**:
+     - 防止用户越权更新其他用户的订单状态
+     - 增强租户数据隔离防护
+     - 提供完整的安全测试覆盖
+   - **测试验证**:
+     - ✅ 3个新的越权访问测试全部通过
+     - ✅ 现有测试保持通过,确保向后兼容
+
 ### File List
 **创建的文件:**
 1. `packages/credit-balance-module-mt/package.json` - 包配置
@@ -421,9 +564,9 @@ packages/
 4. `packages/credit-balance-module-mt/src/routes/get-balance-logs.mt.ts` - 修复API响应类型转换
 5. `packages/credit-balance-module-mt/src/routes/get-balance.mt.ts` - 修复API响应类型转换
 6. `packages/credit-balance-module-mt/src/routes/me.mt.ts` - 修复API响应类型转换
-7. `packages/credit-balance-module-mt/src/routes/payment.mt.ts` - 修复API响应类型转换(添加订单状态更新逻辑)
+7. `packages/credit-balance-module-mt/src/routes/payment.mt.ts` - 修复API响应类型转换(添加订单状态更新逻辑)**并修复安全漏洞和decimal类型转换问题**
 8. `packages/credit-balance-module-mt/src/routes/set-limit.mt.ts` - 修复API响应类型转换
-9. `packages/credit-balance-module-mt/tests/integration/credit-balance-routes.integration.test.ts` - 添加响应数据类型验证测试
+9. `packages/credit-balance-module-mt/tests/integration/credit-balance-routes.integration.test.ts` - 添加响应数据类型验证测试**并添加7个安全修复和类型转换测试用例**
 10. `packages/orders-module/src/schemas/order.schema.ts` - 在PayType枚举中添加微信支付类型:`WECHAT: 4`
 11. `packages/orders-module/src/entities/order.entity.ts` - 更新pay_type字段注释:添加"4微信支付"
 12. `packages/mini-payment-mt/src/services/payment.mt.service.ts` - 修复同时更新payState和payType字段,使用PayStatus枚举
@@ -431,6 +574,11 @@ packages/
 14. `packages/mini-payment-mt/tests/integration/payment.integration.test.ts` - 修复实体依赖,添加OrderMt、OrderGoodsMt等实体导入
 15. `packages/mini-payment-mt/tests/integration/payment-callback.integration.test.ts` - 修复实体依赖,添加OrderGoodsMt、GoodsMt等实体导入
 16. `packages/mini-payment-mt/tests/integration/payment-refund.integration.test.ts` - 修复实体依赖,添加GoodsMt、UserEntityMt等实体导入
+17. `packages/credit-balance-module-mt/src/schemas/index.ts` - 更新PaymentDto Schema,移除`amount`字段
+18. `mini/src/pages/payment/index.tsx` - 修改传递订单ID而不是订单号,移除`amount`参数传递
+19. `mini/tests/unit/pages/payment/credit-payment.test.tsx` - 更新测试验证API调用参数变更
+20. `packages/credit-balance-module-mt/src/routes/payment.mt.ts` - 修复订单状态更新缺少用户ID检查的安全漏洞,改进错误消息
+21. `packages/credit-balance-module-mt/tests/integration/credit-balance-routes.integration.test.ts` - 添加3个越权访问测试用例
 
 ## QA Results
 *此部分由QA代理在审查完成后填写*

+ 89 - 29
packages/credit-balance-module-mt/src/routes/payment.mt.ts

@@ -69,50 +69,110 @@ const paymentRoutes = new OpenAPIHono<AuthContext>()
     const user = c.get('user');
     const data = c.req.valid('json');
 
+    // 验证referenceId是否为有效的订单ID
+    if (!data.referenceId) {
+      return c.json(
+        { code: 400, message: '订单ID不能为空' },
+        400
+      );
+    }
+
+    const orderId = parseInt(data.referenceId);
+    if (isNaN(orderId) || orderId <= 0) {
+      return c.json(
+        { code: 400, message: '订单ID格式无效' },
+        400
+      );
+    }
+
     const queryRunner = AppDataSource.createQueryRunner();
     await queryRunner.connect();
     await queryRunner.startTransaction();
 
     try {
-      // 在事务中执行额度扣减
+      // 1. 查询订单信息,验证订单状态和金额
+      const orderRepository = queryRunner.manager.getRepository(OrderMt);
+      const order = await orderRepository.findOne({
+        where: { id: orderId, tenantId: user.tenantId, userId: user.id }
+      });
+
+      if (!order) {
+        await queryRunner.rollbackTransaction();
+        return c.json(
+          { code: 404, message: `订单不存在或无权访问,订单ID: ${orderId},用户ID: ${user.id}` },
+          404
+        );
+      }
+
+      // 2. 验证订单支付状态,只允许未支付的订单进行支付
+      if (order.payState !== PayStatus.UNPAID) {
+        await queryRunner.rollbackTransaction();
+
+        let statusMessage = '';
+        switch (order.payState) {
+          case PayStatus.PAYING:
+            statusMessage = '订单正在支付中';
+            break;
+          case PayStatus.SUCCESS:
+            statusMessage = '订单已支付成功';
+            break;
+          case PayStatus.REFUNDED:
+            statusMessage = '订单已退款';
+            break;
+          case PayStatus.FAILED:
+            statusMessage = '订单支付失败';
+            break;
+          case PayStatus.CLOSED:
+            statusMessage = '订单已关闭';
+            break;
+          default:
+            statusMessage = '订单状态异常';
+        }
+
+        return c.json(
+          { code: 400, message: `无法支付: ${statusMessage}` },
+          400
+        );
+      }
+
+      // 3. 使用订单的实际支付金额,而不是前端传递的金额
+      // 确保paymentAmount是数字类型(TypeORM decimal字段可能返回字符串)
+      const paymentAmount = Number(order.payAmount);
+      if (isNaN(paymentAmount) || paymentAmount <= 0) {
+        await queryRunner.rollbackTransaction();
+        return c.json(
+          { code: 400, message: '订单支付金额无效' },
+          400
+        );
+      }
+
+      // 4. 在事务中执行额度扣减
       const service = new CreditBalanceService(queryRunner.manager.connection);
       const balance = await service.deductAmount({
         tenantId: user.tenantId,
         userId: user.id,
-        amount: data.amount,
-        referenceId: data.referenceId,
+        amount: paymentAmount, // 使用订单的实际支付金额
+        referenceId: orderId.toString(), // 使用订单ID作为referenceId
         operatorId: data.operatorId,
-        remark: data.remark
+        remark: data.remark || `订单支付 - 订单号: ${order.orderNo}`
       });
 
-      // 如果提供了订单ID,更新订单支付状态
-      if (data.referenceId) {
-        try {
-          const orderId = parseInt(data.referenceId);
-          if (!isNaN(orderId)) {
-            const orderRepository = queryRunner.manager.getRepository(OrderMt);
-            const updateResult = await orderRepository.update(
-              { id: orderId, tenantId: user.tenantId },
-              {
-                payState: PayStatus.SUCCESS,
-                payType: PayType.CREDIT,
-                updatedAt: new Date()
-              }
-            );
-
-            if (updateResult.affected === 0) {
-              console.warn(`订单ID ${orderId} 不存在或更新失败,租户ID: ${user.tenantId}`);
-              // 这里不抛出错误,因为额度扣减已经成功,订单状态更新失败不影响支付成功
-            } else {
-              console.debug(`订单支付状态更新成功,订单ID: ${orderId}, 租户ID: ${user.tenantId}`);
-            }
-          }
-        } catch (orderError) {
-          console.error('订单状态更新失败:', orderError);
-          // 这里不抛出错误,因为额度扣减已经成功,订单状态更新失败不影响支付成功
+      // 5. 更新订单支付状态(添加用户ID检查,防止越权更新)
+      const updateResult = await orderRepository.update(
+        { id: orderId, tenantId: user.tenantId, userId: user.id },
+        {
+          payState: PayStatus.SUCCESS,
+          payType: PayType.CREDIT,
+          updatedAt: new Date()
         }
+      );
+
+      if (updateResult.affected === 0) {
+        throw new Error(`订单状态更新失败,订单ID: ${orderId},用户ID: ${user.id}`);
       }
 
+      console.debug(`额度支付成功,订单ID: ${orderId}, 租户ID: ${user.tenantId}, 金额: ${paymentAmount}`);
+
       await queryRunner.commitTransaction();
 
       const responseData = await parseWithAwait(CreditBalanceSchema, balance);

+ 483 - 2
packages/credit-balance-module-mt/tests/integration/credit-balance-routes.integration.test.ts

@@ -265,10 +265,29 @@ describe('多租户信用额度API集成测试', () => {
         isEnabled: 1
       });
 
+      // 创建测试订单
+      const merchant = await CreditBalanceTestDataFactory.createTestMerchant(dataSource, 1, testUser.id);
+      const supplier = await CreditBalanceTestDataFactory.createTestSupplier(dataSource, 1, testUser.id);
+      const address = await CreditBalanceTestDataFactory.createTestDeliveryAddress(dataSource, 1, testUser.id);
+
+      const testOrder = await CreditBalanceTestDataFactory.createTestOrder(
+        dataSource,
+        1,
+        testUser.id,
+        merchant.id,
+        supplier.id,
+        address.id,
+        {
+          orderNo: 'TEST_ORDER_003',
+          amount: 500.00,
+          payAmount: 500.00,
+          payState: PayStatus.UNPAID
+        }
+      );
+
       const response = await client.payment.$post({
         json: {
-          amount: 500.00,
-          referenceId: 'ORD202412010001',
+          referenceId: testOrder.id.toString(), // 使用订单ID
           operatorId: 1,
           remark: '订单支付'
         }
@@ -405,6 +424,468 @@ describe('多租户信用额度API集成测试', () => {
       expect(updatedOrder!.payState).toBe(PayStatus.UNPAID); // 仍然是未支付状态
       expect(updatedOrder!.payType).toBe(0); // 支付类型未改变
     });
+
+    it('应该拒绝支付已支付成功的订单', async () => {
+      // 先创建信用额度记录
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
+      await creditBalanceRepo.save({
+        tenantId: 1,
+        userId: testUser.id,
+        totalLimit: 10000.00,
+        usedAmount: 0.00,
+        isEnabled: 1
+      });
+
+      // 创建已支付成功的测试订单
+      const merchant = await CreditBalanceTestDataFactory.createTestMerchant(dataSource, 1, testUser.id);
+      const supplier = await CreditBalanceTestDataFactory.createTestSupplier(dataSource, 1, testUser.id);
+      const address = await CreditBalanceTestDataFactory.createTestDeliveryAddress(dataSource, 1, testUser.id);
+
+      const testOrder = await CreditBalanceTestDataFactory.createTestOrder(
+        dataSource,
+        1,
+        testUser.id,
+        merchant.id,
+        supplier.id,
+        address.id,
+        {
+          orderNo: 'TEST_ORDER_004',
+          amount: 500.00,
+          payAmount: 500.00,
+          payState: PayStatus.SUCCESS, // 已支付成功
+          payType: PayType.CREDIT
+        }
+      );
+
+      const response = await client.payment.$post({
+        json: {
+          referenceId: testOrder.id.toString(),
+          operatorId: 1,
+          remark: '订单支付'
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      // 应该返回错误,因为订单已支付成功
+      expect(response.status).toBe(400);
+      if (response.status === 400) {
+        const data = await response.json();
+        expect(data.message).toContain('订单已支付成功');
+      }
+    });
+
+    it('应该拒绝支付不存在的订单', async () => {
+      // 先创建信用额度记录
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
+      await creditBalanceRepo.save({
+        tenantId: 1,
+        userId: testUser.id,
+        totalLimit: 10000.00,
+        usedAmount: 0.00,
+        isEnabled: 1
+      });
+
+      const response = await client.payment.$post({
+        json: {
+          referenceId: '99999', // 不存在的订单ID
+          operatorId: 1,
+          remark: '订单支付'
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      // 应该返回404错误
+      expect(response.status).toBe(404);
+    });
+
+    it('应该拒绝支付无效的订单ID', async () => {
+      // 先创建信用额度记录
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
+      await creditBalanceRepo.save({
+        tenantId: 1,
+        userId: testUser.id,
+        totalLimit: 10000.00,
+        usedAmount: 0.00,
+        isEnabled: 1
+      });
+
+      const response = await client.payment.$post({
+        json: {
+          referenceId: 'invalid-order-id', // 无效的订单ID格式
+          operatorId: 1,
+          remark: '订单支付'
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      // 应该返回400错误
+      expect(response.status).toBe(400);
+    });
+
+    it('应该拒绝支付金额为0的订单', async () => {
+      // 先创建信用额度记录
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
+      await creditBalanceRepo.save({
+        tenantId: 1,
+        userId: testUser.id,
+        totalLimit: 10000.00,
+        usedAmount: 0.00,
+        isEnabled: 1
+      });
+
+      // 创建支付金额为0的测试订单
+      const merchant = await CreditBalanceTestDataFactory.createTestMerchant(dataSource, 1, testUser.id);
+      const supplier = await CreditBalanceTestDataFactory.createTestSupplier(dataSource, 1, testUser.id);
+      const address = await CreditBalanceTestDataFactory.createTestDeliveryAddress(dataSource, 1, testUser.id);
+
+      const testOrder = await CreditBalanceTestDataFactory.createTestOrder(
+        dataSource,
+        1,
+        testUser.id,
+        merchant.id,
+        supplier.id,
+        address.id,
+        {
+          orderNo: 'TEST_ORDER_005',
+          amount: 0.00,
+          payAmount: 0.00, // 支付金额为0
+          payState: PayStatus.UNPAID
+        }
+      );
+
+      const response = await client.payment.$post({
+        json: {
+          referenceId: testOrder.id.toString(),
+          operatorId: 1,
+          remark: '订单支付'
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      // 应该返回400错误
+      expect(response.status).toBe(400);
+      if (response.status === 400) {
+        const data = await response.json();
+        expect(data.message).toContain('订单支付金额无效');
+      }
+    });
+
+    it('应该成功扣减额度当用户已有欠款时', async () => {
+      // 先创建信用额度记录,用户已有欠款
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
+      await creditBalanceRepo.save({
+        tenantId: 1,
+        userId: testUser.id,
+        totalLimit: 1000.00,
+        usedAmount: 198.01, // 用户已有欠款
+        isEnabled: 1
+      });
+
+      // 创建测试订单
+      const merchant = await CreditBalanceTestDataFactory.createTestMerchant(dataSource, 1, testUser.id);
+      const supplier = await CreditBalanceTestDataFactory.createTestSupplier(dataSource, 1, testUser.id);
+      const address = await CreditBalanceTestDataFactory.createTestDeliveryAddress(dataSource, 1, testUser.id);
+
+      const testOrder = await CreditBalanceTestDataFactory.createTestOrder(
+        dataSource,
+        1,
+        testUser.id,
+        merchant.id,
+        supplier.id,
+        address.id,
+        {
+          orderNo: 'TEST_ORDER_006',
+          amount: 99.00,
+          payAmount: 99.00,
+          payState: PayStatus.UNPAID
+        }
+      );
+
+      const response = await client.payment.$post({
+        json: {
+          referenceId: testOrder.id.toString(),
+          operatorId: 1,
+          remark: '订单支付'
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      expect(response.status).toBe(200);
+      if (response.status === 200) {
+        const data = await response.json();
+        // 已用额度应该是 198.01 + 99.00 = 297.01
+        expect(data.usedAmount).toBeCloseTo(297.01, 2);
+        // 可用额度应该是 1000.00 - 297.01 = 702.99
+        expect(data.availableAmount).toBeCloseTo(702.99, 2);
+      }
+    });
+
+    it('应该正确处理decimal字段的字符串类型', async () => {
+      // 测试TypeORM decimal字段返回字符串的情况
+      // 先创建信用额度记录,使用字符串格式的金额
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
+
+      // 直接使用SQL插入,模拟TypeORM返回字符串的情况
+      await dataSource.query(`
+        INSERT INTO credit_balance_mt (tenant_id, user_id, total_limit, used_amount, is_enabled)
+        VALUES (1, $1, '500.00', '150.75', 1)
+      `, [testUser.id]);
+
+      // 创建测试订单,使用字符串格式的金额
+      const merchant = await CreditBalanceTestDataFactory.createTestMerchant(dataSource, 1, testUser.id);
+      const supplier = await CreditBalanceTestDataFactory.createTestSupplier(dataSource, 1, testUser.id);
+      const address = await CreditBalanceTestDataFactory.createTestDeliveryAddress(dataSource, 1, testUser.id);
+
+      const testOrder = await CreditBalanceTestDataFactory.createTestOrder(
+        dataSource,
+        1,
+        testUser.id,
+        merchant.id,
+        supplier.id,
+        address.id,
+        {
+          orderNo: 'TEST_ORDER_007',
+          amount: 50.25,
+          payAmount: 50.25,
+          payState: PayStatus.UNPAID
+        }
+      );
+
+      const response = await client.payment.$post({
+        json: {
+          referenceId: testOrder.id.toString(),
+          operatorId: 1,
+          remark: '订单支付'
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      expect(response.status).toBe(200);
+      if (response.status === 200) {
+        const data = await response.json();
+        // 已用额度应该是 150.75 + 50.25 = 201.00
+        expect(data.usedAmount).toBeCloseTo(201.00, 2);
+        // 可用额度应该是 500.00 - 201.00 = 299.00
+        expect(data.availableAmount).toBeCloseTo(299.00, 2);
+      }
+    });
+
+    it('应该拒绝支付其他用户的订单(越权访问防护)', async () => {
+      // 先为testUser创建信用额度记录
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
+      await creditBalanceRepo.save({
+        tenantId: 1,
+        userId: testUser.id,
+        totalLimit: 10000.00,
+        usedAmount: 0.00,
+        isEnabled: 1
+      });
+
+      // 为otherUser创建信用额度记录
+      await creditBalanceRepo.save({
+        tenantId: 1,
+        userId: otherUser.id,
+        totalLimit: 5000.00,
+        usedAmount: 0.00,
+        isEnabled: 1
+      });
+
+      // 为otherUser创建测试订单(注意:订单的用户ID是otherUser.id)
+      const merchant = await CreditBalanceTestDataFactory.createTestMerchant(dataSource, 1, otherUser.id);
+      const supplier = await CreditBalanceTestDataFactory.createTestSupplier(dataSource, 1, otherUser.id);
+      const address = await CreditBalanceTestDataFactory.createTestDeliveryAddress(dataSource, 1, otherUser.id);
+
+      const otherUserOrder = await CreditBalanceTestDataFactory.createTestOrder(
+        dataSource,
+        1,
+        otherUser.id, // 这是关键:订单属于otherUser
+        merchant.id,
+        supplier.id,
+        address.id,
+        {
+          orderNo: 'OTHER_USER_ORDER',
+          amount: 300.00,
+          payAmount: 300.00,
+          payState: PayStatus.UNPAID
+        }
+      );
+
+      // testUser尝试支付otherUser的订单
+      const response = await client.payment.$post({
+        json: {
+          referenceId: otherUserOrder.id.toString(),
+          operatorId: 1,
+          remark: '尝试支付其他用户的订单'
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}` // testUser的token
+        }
+      });
+
+      // 应该返回404,因为订单查询会失败(userId不匹配)
+      expect(response.status).toBe(404);
+      if (response.status === 404) {
+        const data = await response.json();
+        expect(data.message).toContain('订单不存在或无权访问');
+      }
+
+      // 验证订单状态没有改变
+      const orderRepo = dataSource.getRepository(OrderMt);
+      const orderAfterAttempt = await orderRepo.findOne({
+        where: { id: otherUserOrder.id }
+      });
+      expect(orderAfterAttempt).toBeDefined();
+      expect(orderAfterAttempt!.payState).toBe(PayStatus.UNPAID); // 应该还是未支付状态
+    });
+
+    it('应该拒绝跨租户支付订单(租户隔离防护)', async () => {
+      // 为otherTenantUser创建信用额度记录(租户2)
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
+      await creditBalanceRepo.save({
+        tenantId: 2,
+        userId: otherTenantUser.id,
+        totalLimit: 8000.00,
+        usedAmount: 0.00,
+        isEnabled: 1
+      });
+
+      // 为otherTenantUser创建测试订单(租户2)
+      const merchant = await CreditBalanceTestDataFactory.createTestMerchant(dataSource, 2, otherTenantUser.id);
+      const supplier = await CreditBalanceTestDataFactory.createTestSupplier(dataSource, 2, otherTenantUser.id);
+      const address = await CreditBalanceTestDataFactory.createTestDeliveryAddress(dataSource, 2, otherTenantUser.id);
+
+      const otherTenantOrder = await CreditBalanceTestDataFactory.createTestOrder(
+        dataSource,
+        2, // 租户2
+        otherTenantUser.id,
+        merchant.id,
+        supplier.id,
+        address.id,
+        {
+          orderNo: 'OTHER_TENANT_ORDER',
+          amount: 400.00,
+          payAmount: 400.00,
+          payState: PayStatus.UNPAID
+        }
+      );
+
+      // testUser(租户1)尝试支付otherTenantUser(租户2)的订单
+      const response = await client.payment.$post({
+        json: {
+          referenceId: otherTenantOrder.id.toString(),
+          operatorId: 1,
+          remark: '尝试支付其他租户的订单'
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}` // 租户1的用户
+        }
+      });
+
+      // 应该返回404,因为租户ID不匹配
+      expect(response.status).toBe(404);
+      if (response.status === 404) {
+        const data = await response.json();
+        expect(data.message).toContain('订单不存在或无权访问');
+      }
+
+      // 验证订单状态没有改变
+      const orderRepo = dataSource.getRepository(OrderMt);
+      const orderAfterAttempt = await orderRepo.findOne({
+        where: { id: otherTenantOrder.id }
+      });
+      expect(orderAfterAttempt).toBeDefined();
+      expect(orderAfterAttempt!.payState).toBe(PayStatus.UNPAID);
+    });
+
+    it('应该正确处理订单状态更新时的用户ID验证', async () => {
+      // 这个测试验证订单状态更新也包含用户ID检查
+      // 先创建信用额度记录
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
+      await creditBalanceRepo.save({
+        tenantId: 1,
+        userId: testUser.id,
+        totalLimit: 10000.00,
+        usedAmount: 0.00,
+        isEnabled: 1
+      });
+
+      // 创建测试订单
+      const merchant = await CreditBalanceTestDataFactory.createTestMerchant(dataSource, 1, testUser.id);
+      const supplier = await CreditBalanceTestDataFactory.createTestSupplier(dataSource, 1, testUser.id);
+      const address = await CreditBalanceTestDataFactory.createTestDeliveryAddress(dataSource, 1, testUser.id);
+
+      const testOrder = await CreditBalanceTestDataFactory.createTestOrder(
+        dataSource,
+        1,
+        testUser.id,
+        merchant.id,
+        supplier.id,
+        address.id,
+        {
+          orderNo: 'USER_ID_VALIDATION_ORDER',
+          amount: 250.00,
+          payAmount: 250.00,
+          payState: PayStatus.UNPAID
+        }
+      );
+
+      // 正常支付应该成功
+      const response = await client.payment.$post({
+        json: {
+          referenceId: testOrder.id.toString(),
+          operatorId: 1,
+          remark: '正常支付测试用户ID验证'
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      expect(response.status).toBe(200);
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data.usedAmount).toBeCloseTo(250, 2);
+
+        // 验证订单状态已更新
+        const orderRepo = dataSource.getRepository(OrderMt);
+        const updatedOrder = await orderRepo.findOne({
+          where: { id: testOrder.id }
+        });
+        expect(updatedOrder).toBeDefined();
+        expect(updatedOrder!.payState).toBe(PayStatus.SUCCESS);
+        expect(updatedOrder!.payType).toBe(PayType.CREDIT);
+      }
+    });
   });
 
   describe('结账恢复额度', () => {