|
|
@@ -4,13 +4,13 @@ import {
|
|
|
IntegrationTestDatabase,
|
|
|
setupIntegrationDatabaseHooksWithEntities
|
|
|
} from '@d8d/shared-test-util';
|
|
|
-import { PaymentRoutes } from '../../src/routes/payment.routes.js';
|
|
|
-import { PaymentEntity } from '../../src/entities/payment.entity.js';
|
|
|
+import { PaymentMtRoutes } from '../../src/routes/payment.mt.routes.js';
|
|
|
+import { PaymentMtEntity } from '../../src/entities/payment.mt.entity.js';
|
|
|
import { PaymentStatus } from '../../src/entities/payment.types.js';
|
|
|
-import { UserEntity } from '@d8d/user-module';
|
|
|
-import { Role } from '@d8d/user-module';
|
|
|
-import { File } from '@d8d/file-module';
|
|
|
-import { PaymentService } from '../../src/services/payment.service.js';
|
|
|
+import { UserEntityMt } from '@d8d/user-module-mt';
|
|
|
+import { RoleMt } from '@d8d/user-module-mt';
|
|
|
+import { FileMt } from '@d8d/file-module-mt';
|
|
|
+import { OrderMt } from '@d8d/orders-module-mt';
|
|
|
import { config } from 'dotenv';
|
|
|
import { resolve } from 'path';
|
|
|
// 导入微信支付SDK用于模拟
|
|
|
@@ -22,12 +22,13 @@ config({ path: resolve(process.cwd(), '.env.test') });
|
|
|
vi.mock('wechatpay-node-v3')
|
|
|
|
|
|
// 设置集成测试钩子
|
|
|
-setupIntegrationDatabaseHooksWithEntities([PaymentEntity, UserEntity, File, Role])
|
|
|
+setupIntegrationDatabaseHooksWithEntities([PaymentMtEntity, UserEntityMt, FileMt, RoleMt, OrderMt])
|
|
|
|
|
|
-describe('支付回调API集成测试', () => {
|
|
|
- let client: ReturnType<typeof testClient<typeof PaymentRoutes>>;
|
|
|
- let testUser: UserEntity;
|
|
|
- let testPayment: PaymentEntity;
|
|
|
+describe('支付回调API集成测试 - 多租户版本', () => {
|
|
|
+ let client: ReturnType<typeof testClient<typeof PaymentMtRoutes>>;
|
|
|
+ let testUser: UserEntityMt;
|
|
|
+ let testPayment: PaymentMtEntity;
|
|
|
+ let testOrder: OrderMt;
|
|
|
|
|
|
// 使用真实的微信支付回调数据 - 直接使用原始请求体字符串
|
|
|
const rawBody = '{"id":"495e231b-9fd8-54a1-8a30-2a38a807744c","create_time":"2025-10-25T12:48:11+08:00","resource_type":"encrypt-resource","event_type":"TRANSACTION.SUCCESS","summary":"支付成功","resource":{"original_type":"transaction","algorithm":"AEAD_AES_256_GCM","ciphertext":"tl1/8FRRn6g0gRq8IoVR8+95VuIADYBDOt6N9PKiHVhiD6l++W5g/wg6VlsCRIZJ+KWMYTaf5FzQHMjCs8o9otIkLLuJA2aZC+kCQtGxNfyVBwxool/tLT9mHd0dFGThqbj8vb/lm+jjNcmmiWHz+J1ZRvGl7mH4I714vudok7JRt5Q0u0tYaLWr76TTXuQErlA7T4KbeVeGAj8iMpu2ErCpR9QRif36Anc5ARjNYrIWfraXlmUXVbXermDyJ8r4o/4QCFfGk8L1u1WqNYASrRTQvQ8OPqj/J21OkDxbPPrOiEmAX1jOvONvIVEe9Lbkm6rdhW4aLRoZYtiusAk/Vm7MI/UYPwRZbyuc4wwdA1T1D4RdJd/m2I4KSvZHQgs0DM0tLqlb0z3880XYNr8iPFnyu2r8Z8LGcXD+COm06vc7bvNWh3ODwmMrmZQkym/Y/T3X/h/4MZj7+1h2vYHqnnrsgtNPHc/2IwWC/fQlPwtSrLh6iUxSd0betFpKLSq08CaJZvnenpDf1ORRMvd8EhTtIJJ4mV4v+VzCOYNhIcBhKp9XwsuhxIdkpGGmNPpow2c2BXY=","associated_data":"transaction","nonce":"sTnWce32BTQP"}}';
|
|
|
@@ -40,36 +41,56 @@ describe('支付回调API集成测试', () => {
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
// 创建测试客户端
|
|
|
- client = testClient(PaymentRoutes);
|
|
|
+ client = testClient(PaymentMtRoutes);
|
|
|
|
|
|
// 创建测试用户
|
|
|
const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
|
|
|
- const userRepository = dataSource.getRepository(UserEntity);
|
|
|
+ const userRepository = dataSource.getRepository(UserEntityMt);
|
|
|
testUser = userRepository.create({
|
|
|
username: `test_user_${Date.now()}`,
|
|
|
password: 'test_password',
|
|
|
nickname: '测试用户',
|
|
|
- openid: 'oJy1-16IIG18XZLl7G32k1hHMUFg'
|
|
|
+ openid: 'oJy1-16IIG18XZLl7G32k1hHMUFg',
|
|
|
+ tenantId: 1
|
|
|
});
|
|
|
await userRepository.save(testUser);
|
|
|
|
|
|
+ // 创建测试订单
|
|
|
+ const orderRepository = dataSource.getRepository(OrderMt);
|
|
|
+ testOrder = orderRepository.create({
|
|
|
+ tenantId: 1,
|
|
|
+ orderNo: `ORD${Date.now()}`,
|
|
|
+ userId: testUser.id,
|
|
|
+ amount: 1,
|
|
|
+ costAmount: 0.5,
|
|
|
+ payAmount: 1,
|
|
|
+ orderType: 1,
|
|
|
+ payType: 2,
|
|
|
+ payState: 0, // 未支付
|
|
|
+ state: 0,
|
|
|
+ addressId: 0,
|
|
|
+ merchantId: 0,
|
|
|
+ supplierId: 0,
|
|
|
+ createdBy: testUser.id,
|
|
|
+ updatedBy: testUser.id
|
|
|
+ });
|
|
|
+ await orderRepository.save(testOrder);
|
|
|
+
|
|
|
// 创建测试支付记录,使用与真实回调数据一致的金额
|
|
|
- const paymentRepository = dataSource.getRepository(PaymentEntity);
|
|
|
+ const paymentRepository = dataSource.getRepository(PaymentMtEntity);
|
|
|
testPayment = paymentRepository.create({
|
|
|
- externalOrderId: 13, // 与真实回调数据一致
|
|
|
+ externalOrderId: testOrder.id, // 使用订单ID作为外部订单ID
|
|
|
userId: testUser.id,
|
|
|
totalAmount: 1, // 1分钱,与真实回调数据一致
|
|
|
description: '测试支付',
|
|
|
paymentStatus: PaymentStatus.PROCESSING, // 设置为处理中状态,模拟已发起支付
|
|
|
openid: testUser.openid!,
|
|
|
- outTradeNo: `ORDER_13_${Date.now()}`
|
|
|
+ outTradeNo: `ORDER_${testOrder.id}_${Date.now()}`,
|
|
|
+ tenantId: 1
|
|
|
});
|
|
|
await paymentRepository.save(testPayment);
|
|
|
|
|
|
- // 手动更新支付记录ID为13,与真实回调数据一致
|
|
|
- await dataSource.query('UPDATE payments SET external_order_id = 13 WHERE id = $1', [testPayment.id]);
|
|
|
-
|
|
|
// 设置微信支付SDK的全局mock
|
|
|
const mockWxPay = {
|
|
|
transactions_jsapi: vi.fn().mockResolvedValue({
|
|
|
@@ -101,7 +122,7 @@ describe('支付回调API集成测试', () => {
|
|
|
});
|
|
|
|
|
|
describe('POST /payment/callback - 支付回调', () => {
|
|
|
- it('应该成功处理支付成功回调', async () => {
|
|
|
+ it('应该成功处理支付成功回调并更新订单状态', async () => {
|
|
|
const response = await client.payment.callback.$post({
|
|
|
// 使用空的json参数,通过init传递原始请求体
|
|
|
json: {}
|
|
|
@@ -118,13 +139,35 @@ describe('支付回调API集成测试', () => {
|
|
|
if (response.status === 200) {
|
|
|
const result = await response.text();
|
|
|
expect(result).toBe('SUCCESS');
|
|
|
+
|
|
|
+ // 验证订单状态已更新为已支付 (2)
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ const orderRepository = dataSource.getRepository(OrderMt);
|
|
|
+ const updatedOrder = await orderRepository.findOne({
|
|
|
+ where: { id: testOrder.id, tenantId: 1 }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(updatedOrder).toBeDefined();
|
|
|
+ expect(updatedOrder?.payState).toBe(2); // 已支付
|
|
|
}
|
|
|
});
|
|
|
|
|
|
- it('应该处理支付失败回调', async () => {
|
|
|
- // 使用统一的真实回调数据
|
|
|
+ it('应该处理支付失败回调并更新订单状态', async () => {
|
|
|
+ // 模拟支付失败的回调数据
|
|
|
+ const mockWxPay = {
|
|
|
+ verifySign: vi.fn().mockResolvedValue(true),
|
|
|
+ decipher_gcm: vi.fn().mockReturnValue(JSON.stringify({
|
|
|
+ out_trade_no: testPayment.outTradeNo,
|
|
|
+ trade_state: 'FAIL',
|
|
|
+ transaction_id: null,
|
|
|
+ amount: {
|
|
|
+ total: 1
|
|
|
+ }
|
|
|
+ }))
|
|
|
+ };
|
|
|
+ vi.mocked(WxPay).mockImplementation(() => mockWxPay as any);
|
|
|
+
|
|
|
const response = await client.payment.callback.$post({
|
|
|
- // 使用空的json参数,通过init传递原始请求体
|
|
|
json: {}
|
|
|
}, {
|
|
|
headers: callbackHeader,
|
|
|
@@ -133,15 +176,138 @@ describe('支付回调API集成测试', () => {
|
|
|
}
|
|
|
});
|
|
|
|
|
|
- // 由于真实数据是支付成功的,回调处理应该成功
|
|
|
expect(response.status).toBe(200);
|
|
|
|
|
|
if (response.status === 200) {
|
|
|
const result = await response.text();
|
|
|
expect(result).toBe('SUCCESS');
|
|
|
+
|
|
|
+ // 验证订单状态已更新为支付失败 (4)
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ const orderRepository = dataSource.getRepository(OrderMt);
|
|
|
+ const updatedOrder = await orderRepository.findOne({
|
|
|
+ where: { id: testOrder.id, tenantId: 1 }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(updatedOrder).toBeDefined();
|
|
|
+ expect(updatedOrder?.payState).toBe(4); // 支付失败
|
|
|
}
|
|
|
});
|
|
|
|
|
|
+ it('应该处理退款回调并更新订单状态', async () => {
|
|
|
+ // 模拟退款回调数据
|
|
|
+ const mockWxPay = {
|
|
|
+ verifySign: vi.fn().mockResolvedValue(true),
|
|
|
+ decipher_gcm: vi.fn().mockReturnValue(JSON.stringify({
|
|
|
+ out_trade_no: testPayment.outTradeNo,
|
|
|
+ trade_state: 'REFUND',
|
|
|
+ transaction_id: 'test_refund_transaction_id',
|
|
|
+ amount: {
|
|
|
+ total: 1
|
|
|
+ }
|
|
|
+ }))
|
|
|
+ };
|
|
|
+ vi.mocked(WxPay).mockImplementation(() => mockWxPay as any);
|
|
|
+
|
|
|
+ const response = await client.payment.callback.$post({
|
|
|
+ json: {}
|
|
|
+ }, {
|
|
|
+ headers: callbackHeader,
|
|
|
+ init: {
|
|
|
+ body: rawBody
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const result = await response.text();
|
|
|
+ expect(result).toBe('SUCCESS');
|
|
|
+
|
|
|
+ // 验证订单状态已更新为已退款 (3)
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ const orderRepository = dataSource.getRepository(OrderMt);
|
|
|
+ const updatedOrder = await orderRepository.findOne({
|
|
|
+ where: { id: testOrder.id, tenantId: 1 }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(updatedOrder).toBeDefined();
|
|
|
+ expect(updatedOrder?.payState).toBe(3); // 已退款
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该验证多租户数据隔离', async () => {
|
|
|
+ // 创建第二个租户的测试数据
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+
|
|
|
+ const userRepository = dataSource.getRepository(UserEntityMt);
|
|
|
+ const testUser2 = userRepository.create({
|
|
|
+ username: `test_user2_${Date.now()}`,
|
|
|
+ password: 'test_password',
|
|
|
+ nickname: '测试用户2',
|
|
|
+ openid: 'oJy1-16IIG18XZLl7G32k1hHMUFg2',
|
|
|
+ tenantId: 2
|
|
|
+ });
|
|
|
+ await userRepository.save(testUser2);
|
|
|
+
|
|
|
+ const orderRepository = dataSource.getRepository(OrderMt);
|
|
|
+ const testOrder2 = orderRepository.create({
|
|
|
+ tenantId: 2,
|
|
|
+ orderNo: `ORD${Date.now()}_2`,
|
|
|
+ userId: testUser2.id,
|
|
|
+ amount: 1,
|
|
|
+ costAmount: 0.5,
|
|
|
+ payAmount: 1,
|
|
|
+ orderType: 1,
|
|
|
+ payType: 2,
|
|
|
+ payState: 0,
|
|
|
+ state: 0,
|
|
|
+ addressId: 0,
|
|
|
+ merchantId: 0,
|
|
|
+ supplierId: 0,
|
|
|
+ createdBy: testUser2.id,
|
|
|
+ updatedBy: testUser2.id
|
|
|
+ });
|
|
|
+ await orderRepository.save(testOrder2);
|
|
|
+
|
|
|
+ const paymentRepository = dataSource.getRepository(PaymentMtEntity);
|
|
|
+ const testPayment2 = paymentRepository.create({
|
|
|
+ externalOrderId: testOrder2.id,
|
|
|
+ userId: testUser2.id,
|
|
|
+ totalAmount: 1,
|
|
|
+ description: '测试支付2',
|
|
|
+ paymentStatus: PaymentStatus.PROCESSING,
|
|
|
+ openid: testUser2.openid!,
|
|
|
+ outTradeNo: `ORDER_${testOrder2.id}_${Date.now()}`,
|
|
|
+ tenantId: 2
|
|
|
+ });
|
|
|
+ await paymentRepository.save(testPayment2);
|
|
|
+
|
|
|
+ // 处理租户1的支付回调
|
|
|
+ const response = await client.payment.callback.$post({
|
|
|
+ json: {}
|
|
|
+ }, {
|
|
|
+ headers: callbackHeader,
|
|
|
+ init: {
|
|
|
+ body: rawBody
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+
|
|
|
+ // 验证租户1的订单状态已更新
|
|
|
+ const updatedOrder1 = await orderRepository.findOne({
|
|
|
+ where: { id: testOrder.id, tenantId: 1 }
|
|
|
+ });
|
|
|
+ expect(updatedOrder1?.payState).toBe(2); // 已支付
|
|
|
+
|
|
|
+ // 验证租户2的订单状态未受影响
|
|
|
+ const updatedOrder2 = await orderRepository.findOne({
|
|
|
+ where: { id: testOrder2.id, tenantId: 2 }
|
|
|
+ });
|
|
|
+ expect(updatedOrder2?.payState).toBe(0); // 仍为未支付
|
|
|
+ });
|
|
|
+
|
|
|
it('应该处理无效的回调数据格式', async () => {
|
|
|
const response = await client.payment.callback.$post({
|
|
|
body: 'invalid json data'
|