|
|
@@ -4,17 +4,24 @@ 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 { MerchantMt } from '@d8d/merchant-module-mt';
|
|
|
+import { SupplierMt } from '@d8d/supplier-module-mt';
|
|
|
+import { DeliveryAddressMt } from '@d8d/delivery-address-module-mt';
|
|
|
+import { AreaEntityMt } from '@d8d/geo-areas-mt';
|
|
|
+import { SystemConfigMt } from '@d8d/core-module-mt/system-config-module-mt/entities';
|
|
|
import { config } from 'dotenv';
|
|
|
import { resolve } from 'path';
|
|
|
// 导入微信支付SDK用于模拟
|
|
|
import WxPay from 'wechatpay-node-v3';
|
|
|
+// 导入测试数据工厂
|
|
|
+import { PaymentTestFactory } from '../factories/payment-test.factory.js';
|
|
|
|
|
|
// 在测试环境中加载环境变量
|
|
|
config({ path: resolve(process.cwd(), '.env.test') });
|
|
|
@@ -22,12 +29,19 @@ config({ path: resolve(process.cwd(), '.env.test') });
|
|
|
vi.mock('wechatpay-node-v3')
|
|
|
|
|
|
// 设置集成测试钩子
|
|
|
-setupIntegrationDatabaseHooksWithEntities([PaymentEntity, UserEntity, File, Role])
|
|
|
+setupIntegrationDatabaseHooksWithEntities([PaymentMtEntity, UserEntityMt, FileMt, RoleMt, OrderMt, MerchantMt, SupplierMt, DeliveryAddressMt, AreaEntityMt, SystemConfigMt])
|
|
|
|
|
|
-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 testFactory: PaymentTestFactory;
|
|
|
+ let testData: {
|
|
|
+ user: UserEntityMt;
|
|
|
+ merchant: MerchantMt;
|
|
|
+ supplier: SupplierMt;
|
|
|
+ address: DeliveryAddressMt;
|
|
|
+ order: OrderMt;
|
|
|
+ payment: PaymentMtEntity;
|
|
|
+ };
|
|
|
|
|
|
// 使用真实的微信支付回调数据 - 直接使用原始请求体字符串
|
|
|
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,35 +54,14 @@ describe('支付回调API集成测试', () => {
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
// 创建测试客户端
|
|
|
- client = testClient(PaymentRoutes);
|
|
|
+ client = testClient(PaymentMtRoutes);
|
|
|
|
|
|
- // 创建测试用户
|
|
|
+ // 创建测试数据工厂
|
|
|
const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ testFactory = new PaymentTestFactory(dataSource);
|
|
|
|
|
|
- const userRepository = dataSource.getRepository(UserEntity);
|
|
|
- testUser = userRepository.create({
|
|
|
- username: `test_user_${Date.now()}`,
|
|
|
- password: 'test_password',
|
|
|
- nickname: '测试用户',
|
|
|
- openid: 'oJy1-16IIG18XZLl7G32k1hHMUFg'
|
|
|
- });
|
|
|
- await userRepository.save(testUser);
|
|
|
-
|
|
|
- // 创建测试支付记录,使用与真实回调数据一致的金额
|
|
|
- const paymentRepository = dataSource.getRepository(PaymentEntity);
|
|
|
- testPayment = paymentRepository.create({
|
|
|
- externalOrderId: 13, // 与真实回调数据一致
|
|
|
- userId: testUser.id,
|
|
|
- totalAmount: 1, // 1分钱,与真实回调数据一致
|
|
|
- description: '测试支付',
|
|
|
- paymentStatus: PaymentStatus.PROCESSING, // 设置为处理中状态,模拟已发起支付
|
|
|
- openid: testUser.openid!,
|
|
|
- outTradeNo: `ORDER_13_${Date.now()}`
|
|
|
- });
|
|
|
- await paymentRepository.save(testPayment);
|
|
|
-
|
|
|
- // 手动更新支付记录ID为13,与真实回调数据一致
|
|
|
- await dataSource.query('UPDATE payments SET external_order_id = 13 WHERE id = $1', [testPayment.id]);
|
|
|
+ // 创建完整的测试数据
|
|
|
+ testData = await testFactory.createCompleteTestData(1);
|
|
|
|
|
|
// 设置微信支付SDK的全局mock
|
|
|
const mockWxPay = {
|
|
|
@@ -81,7 +74,7 @@ describe('支付回调API集成测试', () => {
|
|
|
}),
|
|
|
verifySign: vi.fn().mockResolvedValue(true),
|
|
|
decipher_gcm: vi.fn().mockReturnValue(JSON.stringify({
|
|
|
- out_trade_no: testPayment.outTradeNo, // 使用数据库中保存的 outTradeNo
|
|
|
+ out_trade_no: testData.payment.outTradeNo, // 使用数据库中保存的 outTradeNo
|
|
|
trade_state: 'SUCCESS',
|
|
|
transaction_id: 'test_transaction_id',
|
|
|
amount: {
|
|
|
@@ -101,7 +94,7 @@ describe('支付回调API集成测试', () => {
|
|
|
});
|
|
|
|
|
|
describe('POST /payment/callback - 支付回调', () => {
|
|
|
- it('应该成功处理支付成功回调', async () => {
|
|
|
+ it('应该成功处理支付成功回调并更新订单状态', async () => {
|
|
|
const response = await client.payment.callback.$post({
|
|
|
// 使用空的json参数,通过init传递原始请求体
|
|
|
json: {}
|
|
|
@@ -118,13 +111,77 @@ 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: testData.order.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: testData.payment.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: {}
|
|
|
+ }, {
|
|
|
+ headers: callbackHeader,
|
|
|
+ init: {
|
|
|
+ body: rawBody
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ 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: testData.order.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: testData.payment.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参数,通过init传递原始请求体
|
|
|
json: {}
|
|
|
}, {
|
|
|
headers: callbackHeader,
|
|
|
@@ -133,15 +190,87 @@ describe('支付回调API集成测试', () => {
|
|
|
}
|
|
|
});
|
|
|
|
|
|
- // 由于真实数据是支付成功的,回调处理应该成功
|
|
|
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: testData.order.id, tenantId: 1 }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(updatedOrder).toBeDefined();
|
|
|
+ expect(updatedOrder?.payState).toBe(3); // 已退款
|
|
|
}
|
|
|
});
|
|
|
|
|
|
+ it('应该验证多租户数据隔离', async () => {
|
|
|
+ // 创建第二个租户的测试数据
|
|
|
+ const multiTenantData = await testFactory.createMultiTenantTestData();
|
|
|
+ const tenant1Data = multiTenantData.tenant1;
|
|
|
+ const tenant2Data = multiTenantData.tenant2;
|
|
|
+
|
|
|
+ // 为租户1创建特定的回调数据,使用租户1支付记录的outTradeNo
|
|
|
+ const tenant1RawBody = JSON.stringify({
|
|
|
+ "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"
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 模拟微信支付SDK解密,返回租户1的商户订单号
|
|
|
+ const mockWxPay = {
|
|
|
+ verifySign: vi.fn().mockResolvedValue(true),
|
|
|
+ decipher_gcm: vi.fn().mockReturnValue(JSON.stringify({
|
|
|
+ out_trade_no: tenant1Data.payment.outTradeNo, // 使用租户1的支付记录outTradeNo
|
|
|
+ trade_state: 'SUCCESS',
|
|
|
+ transaction_id: 'test_transaction_id',
|
|
|
+ amount: {
|
|
|
+ total: 1
|
|
|
+ }
|
|
|
+ }))
|
|
|
+ };
|
|
|
+ vi.mocked(WxPay).mockImplementation(() => mockWxPay as any);
|
|
|
+
|
|
|
+ // 处理租户1的支付回调
|
|
|
+ const response = await client.payment.callback.$post({
|
|
|
+ json: {}
|
|
|
+ }, {
|
|
|
+ headers: callbackHeader,
|
|
|
+ init: {
|
|
|
+ body: tenant1RawBody
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+
|
|
|
+ // 验证租户1的订单状态已更新
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ const orderRepository = dataSource.getRepository(OrderMt);
|
|
|
+ const updatedOrder1 = await orderRepository.findOne({
|
|
|
+ where: { id: tenant1Data.order.id, tenantId: 1 }
|
|
|
+ });
|
|
|
+ expect(updatedOrder1?.payState).toBe(2); // 已支付
|
|
|
+
|
|
|
+ // 验证租户2的订单状态未受影响
|
|
|
+ const updatedOrder2 = await orderRepository.findOne({
|
|
|
+ where: { id: tenant2Data.order.id, tenantId: 2 }
|
|
|
+ });
|
|
|
+ expect(updatedOrder2?.payState).toBe(0); // 仍为未支付
|
|
|
+ });
|
|
|
+
|
|
|
it('应该处理无效的回调数据格式', async () => {
|
|
|
const response = await client.payment.callback.$post({
|
|
|
body: 'invalid json data'
|