import { describe, it, expect, beforeEach, vi } from 'vitest'; import { DataSource } from 'typeorm'; import { IntegrationTestDatabase, setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util'; import { PaymentMtService } from '../../src/services/payment.mt.service.js'; import { PaymentMtEntity } from '../../src/entities/payment.mt.entity.js'; import { PaymentStatus } from '../../src/entities/payment.types.js'; // Mock 微信支付SDK vi.mock('wechatpay-node-v3', () => { return { default: vi.fn().mockImplementation(() => ({ refunds: vi.fn().mockResolvedValue({ id: 'mock_refund_id_123', out_refund_no: 'REFUND_ORDER_123_1234567890', status: 'SUCCESS' }), verifySign: vi.fn().mockResolvedValue(true), decipher_gcm: vi.fn().mockReturnValue(JSON.stringify({ out_refund_no: 'REFUND_ORDER_123_1234567890', refund_status: 'SUCCESS' })) })) }; }); // Mock 系统配置服务 vi.mock('@d8d/core-module-mt/system-config-module-mt', () => ({ SystemConfigServiceMt: vi.fn().mockImplementation(() => ({ getConfigsByKeys: vi.fn().mockResolvedValue({ 'wx.payment.merchant.id': 'mock_merchant_id', 'wx.mini.app.id': 'mock_app_id', 'wx.payment.v3.key': 'mock_v3_key', 'wx.payment.notify.url': 'mock_notify_url', 'wx.payment.cert.serial.no': 'mock_cert_serial_no', 'wx.payment.public.key': 'mock_public_key', 'wx.payment.private.key': 'mock_private_key' }) })) })); // Mock 订单服务 vi.mock('@d8d/orders-module-mt', () => ({ OrderMtService: vi.fn().mockImplementation(() => ({})), OrderMt: vi.fn() })); // 设置集成测试钩子 setupIntegrationDatabaseHooksWithEntities([PaymentMtEntity]) describe('PaymentRefund Integration Tests', () => { let dataSource: DataSource; let paymentService: PaymentMtService; beforeEach(async () => { // 获取集成测试数据库连接 dataSource = await IntegrationTestDatabase.getDataSource(); paymentService = new PaymentMtService(dataSource); }); describe('refund method', () => { it('应该成功处理已支付订单的退款', async () => { // 准备测试数据 const payment = new PaymentMtEntity(); payment.tenantId = 1; payment.externalOrderId = 1001; payment.userId = 1; payment.totalAmount = 1000; // 10元 payment.description = '测试订单'; payment.paymentStatus = PaymentStatus.PAID; payment.outTradeNo = 'PAYMENT_1001_1234567890'; payment.openid = 'mock_openid'; payment.wechatTransactionId = 'mock_transaction_id'; await dataSource.getRepository(PaymentMtEntity).save(payment); // 执行退款 const refundResult = await paymentService.refund( 1, 'PAYMENT_1001_1234567890', 1000, '测试退款' ); // 验证退款结果 expect(refundResult).toMatchObject({ refundId: expect.stringContaining('REFUND_PAYMENT_1001_1234567890'), outRefundNo: expect.stringContaining('REFUND_PAYMENT_1001_1234567890'), refundStatus: 'SUCCESS', refundAmount: 1000, refundTime: expect.any(String) }); // 验证支付记录已更新 const updatedPayment = await dataSource.getRepository(PaymentMtEntity).findOne({ where: { outTradeNo: 'PAYMENT_1001_1234567890', tenantId: 1 } }); expect(updatedPayment).toBeDefined(); expect(updatedPayment?.refundStatus).toBe(PaymentStatus.REFUNDED); expect(updatedPayment?.refundAmount).toBe(1000); expect(updatedPayment?.refundTime).toBeInstanceOf(Date); }); it('应该对不存在的支付记录抛出错误', async () => { await expect( paymentService.refund(1, 'NON_EXISTENT_ORDER', 1000) ).rejects.toThrow('支付记录不存在'); }); it('应该对未支付订单抛出错误', async () => { const payment = new PaymentMtEntity(); payment.tenantId = 1; payment.externalOrderId = 1002; payment.userId = 1; payment.totalAmount = 1000; payment.description = '测试订单'; payment.paymentStatus = PaymentStatus.PENDING; payment.outTradeNo = 'PAYMENT_1002_1234567890'; payment.openid = 'mock_openid'; await dataSource.getRepository(PaymentMtEntity).save(payment); await expect( paymentService.refund(1, 'PAYMENT_1002_1234567890', 1000) ).rejects.toThrow('订单支付状态不正确'); }); it('应该对无效退款金额抛出错误', async () => { const payment = new PaymentMtEntity(); payment.tenantId = 1; payment.externalOrderId = 1003; payment.userId = 1; payment.totalAmount = 1000; payment.description = '测试订单'; payment.paymentStatus = PaymentStatus.PAID; payment.outTradeNo = 'PAYMENT_1003_1234567890'; payment.openid = 'mock_openid'; payment.wechatTransactionId = 'mock_transaction_id'; await dataSource.getRepository(PaymentMtEntity).save(payment); // 测试退款金额为0 await expect( paymentService.refund(1, 'PAYMENT_1003_1234567890', 0) ).rejects.toThrow('退款金额无效'); // 测试退款金额超过支付金额 await expect( paymentService.refund(1, 'PAYMENT_1003_1234567890', 2000) ).rejects.toThrow('退款金额无效'); }); }); describe('handleRefundCallback method', () => { it('应该成功处理退款回调', async () => { const mockCallbackData = { resource: { ciphertext: 'mock_ciphertext', associated_data: '', nonce: 'mock_nonce' } }; const mockHeaders = { 'wechatpay-timestamp': 'mock_timestamp', 'wechatpay-nonce': 'mock_nonce', 'wechatpay-signature': 'mock_signature', 'wechatpay-serial': 'mock_serial' }; const mockRawBody = 'mock_raw_body'; // 执行退款回调处理 await expect( paymentService.handleRefundCallback(mockCallbackData, mockHeaders, mockRawBody) ).resolves.not.toThrow(); }); }); describe('multi-tenant refund data isolation', () => { it('应该只退款特定租户的支付记录', async () => { // 创建租户1的支付记录 const payment1 = new PaymentMtEntity(); payment1.tenantId = 1; payment1.externalOrderId = 1004; payment1.userId = 1; payment1.totalAmount = 1000; payment1.description = '租户1订单'; payment1.paymentStatus = PaymentStatus.PAID; payment1.outTradeNo = 'PAYMENT_1004_1234567890'; payment1.openid = 'mock_openid'; payment1.wechatTransactionId = 'mock_transaction_id'; // 创建租户2的支付记录 const payment2 = new PaymentMtEntity(); payment2.tenantId = 2; payment2.externalOrderId = 1004; payment2.userId = 1; payment2.totalAmount = 1000; payment2.description = '租户2订单'; payment2.paymentStatus = PaymentStatus.PAID; payment2.outTradeNo = 'PAYMENT_1004_1234567890'; payment2.openid = 'mock_openid'; payment2.wechatTransactionId = 'mock_transaction_id'; await dataSource.getRepository(PaymentMtEntity).save([payment1, payment2]); // 为租户1执行退款 await paymentService.refund(1, 'PAYMENT_1004_1234567890', 1000); // 验证租户1的支付记录已更新 const tenant1Payment = await dataSource.getRepository(PaymentMtEntity).findOne({ where: { outTradeNo: 'PAYMENT_1004_1234567890', tenantId: 1 } }); expect(tenant1Payment?.refundStatus).toBe(PaymentStatus.REFUNDED); // 验证租户2的支付记录未受影响 const tenant2Payment = await dataSource.getRepository(PaymentMtEntity).findOne({ where: { outTradeNo: 'PAYMENT_1004_1234567890', tenantId: 2 } }); expect(tenant2Payment?.refundStatus).toBeNull(); }); }); });