payment-refund.integration.test.ts 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. import { describe, it, expect, beforeEach, vi } from 'vitest';
  2. import { DataSource } from 'typeorm';
  3. import { PaymentMtService } from '../../src/services/payment.mt.service.js';
  4. import { PaymentMtEntity } from '../../src/entities/payment.mt.entity.js';
  5. import { PaymentStatus } from '../../src/entities/payment.types.js';
  6. // Mock 微信支付SDK
  7. vi.mock('wechatpay-node-v3', () => {
  8. return {
  9. default: vi.fn().mockImplementation(() => ({
  10. refunds: vi.fn().mockResolvedValue({
  11. id: 'mock_refund_id_123',
  12. out_refund_no: 'REFUND_ORDER_123_1234567890',
  13. status: 'SUCCESS'
  14. }),
  15. verifySign: vi.fn().mockResolvedValue(true),
  16. decipher_gcm: vi.fn().mockReturnValue(JSON.stringify({
  17. out_refund_no: 'REFUND_ORDER_123_1234567890',
  18. refund_status: 'SUCCESS'
  19. }))
  20. }))
  21. };
  22. });
  23. // Mock 系统配置服务
  24. vi.mock('@d8d/core-module-mt/system-config-module-mt', () => ({
  25. SystemConfigServiceMt: vi.fn().mockImplementation(() => ({
  26. getConfigsByKeys: vi.fn().mockResolvedValue({
  27. 'wx.payment.merchant.id': 'mock_merchant_id',
  28. 'wx.mini.app.id': 'mock_app_id',
  29. 'wx.payment.v3.key': 'mock_v3_key',
  30. 'wx.payment.notify.url': 'mock_notify_url',
  31. 'wx.payment.cert.serial.no': 'mock_cert_serial_no',
  32. 'wx.payment.public.key': 'mock_public_key',
  33. 'wx.payment.private.key': 'mock_private_key'
  34. })
  35. }))
  36. }));
  37. // Mock 订单服务
  38. vi.mock('@d8d/orders-module-mt', () => ({
  39. OrderMtService: vi.fn().mockImplementation(() => ({})),
  40. OrderMt: vi.fn()
  41. }));
  42. describe('PaymentRefund Integration Tests', () => {
  43. let dataSource: DataSource;
  44. let paymentService: PaymentMtService;
  45. beforeEach(async () => {
  46. // 创建内存数据库连接
  47. dataSource = new DataSource({
  48. type: 'sqlite',
  49. database: ':memory:',
  50. entities: [PaymentMtEntity],
  51. synchronize: true,
  52. logging: false
  53. });
  54. await dataSource.initialize();
  55. paymentService = new PaymentMtService(dataSource);
  56. });
  57. describe('refund method', () => {
  58. it('应该成功处理已支付订单的退款', async () => {
  59. // 准备测试数据
  60. const payment = new PaymentMtEntity();
  61. payment.tenantId = 1;
  62. payment.externalOrderId = 1001;
  63. payment.userId = 1;
  64. payment.totalAmount = 1000; // 10元
  65. payment.description = '测试订单';
  66. payment.paymentStatus = PaymentStatus.PAID;
  67. payment.outTradeNo = 'PAYMENT_1001_1234567890';
  68. payment.openid = 'mock_openid';
  69. payment.wechatTransactionId = 'mock_transaction_id';
  70. await dataSource.getRepository(PaymentMtEntity).save(payment);
  71. // 执行退款
  72. const refundResult = await paymentService.refund(
  73. 1,
  74. 'PAYMENT_1001_1234567890',
  75. 1000,
  76. '测试退款'
  77. );
  78. // 验证退款结果
  79. expect(refundResult).toMatchObject({
  80. refundId: expect.stringContaining('REFUND_PAYMENT_1001_1234567890'),
  81. outRefundNo: expect.stringContaining('REFUND_PAYMENT_1001_1234567890'),
  82. refundStatus: 'SUCCESS',
  83. refundAmount: 1000,
  84. refundTime: expect.any(String)
  85. });
  86. // 验证支付记录已更新
  87. const updatedPayment = await dataSource.getRepository(PaymentMtEntity).findOne({
  88. where: { outTradeNo: 'PAYMENT_1001_1234567890', tenantId: 1 }
  89. });
  90. expect(updatedPayment).toBeDefined();
  91. expect(updatedPayment?.refundStatus).toBe(PaymentStatus.REFUNDED);
  92. expect(updatedPayment?.refundAmount).toBe(1000);
  93. expect(updatedPayment?.refundTime).toBeInstanceOf(Date);
  94. });
  95. it('应该对不存在的支付记录抛出错误', async () => {
  96. await expect(
  97. paymentService.refund(1, 'NON_EXISTENT_ORDER', 1000)
  98. ).rejects.toThrow('支付记录不存在');
  99. });
  100. it('应该对未支付订单抛出错误', async () => {
  101. const payment = new PaymentMtEntity();
  102. payment.tenantId = 1;
  103. payment.externalOrderId = 1002;
  104. payment.userId = 1;
  105. payment.totalAmount = 1000;
  106. payment.description = '测试订单';
  107. payment.paymentStatus = PaymentStatus.PENDING;
  108. payment.outTradeNo = 'PAYMENT_1002_1234567890';
  109. payment.openid = 'mock_openid';
  110. await dataSource.getRepository(PaymentMtEntity).save(payment);
  111. await expect(
  112. paymentService.refund(1, 'PAYMENT_1002_1234567890', 1000)
  113. ).rejects.toThrow('订单支付状态不正确');
  114. });
  115. it('应该对无效退款金额抛出错误', async () => {
  116. const payment = new PaymentMtEntity();
  117. payment.tenantId = 1;
  118. payment.externalOrderId = 1003;
  119. payment.userId = 1;
  120. payment.totalAmount = 1000;
  121. payment.description = '测试订单';
  122. payment.paymentStatus = PaymentStatus.PAID;
  123. payment.outTradeNo = 'PAYMENT_1003_1234567890';
  124. payment.openid = 'mock_openid';
  125. payment.wechatTransactionId = 'mock_transaction_id';
  126. await dataSource.getRepository(PaymentMtEntity).save(payment);
  127. // 测试退款金额为0
  128. await expect(
  129. paymentService.refund(1, 'PAYMENT_1003_1234567890', 0)
  130. ).rejects.toThrow('退款金额无效');
  131. // 测试退款金额超过支付金额
  132. await expect(
  133. paymentService.refund(1, 'PAYMENT_1003_1234567890', 2000)
  134. ).rejects.toThrow('退款金额无效');
  135. });
  136. });
  137. describe('handleRefundCallback method', () => {
  138. it('应该成功处理退款回调', async () => {
  139. const mockCallbackData = {
  140. resource: {
  141. ciphertext: 'mock_ciphertext',
  142. associated_data: '',
  143. nonce: 'mock_nonce'
  144. }
  145. };
  146. const mockHeaders = {
  147. 'wechatpay-timestamp': 'mock_timestamp',
  148. 'wechatpay-nonce': 'mock_nonce',
  149. 'wechatpay-signature': 'mock_signature',
  150. 'wechatpay-serial': 'mock_serial'
  151. };
  152. const mockRawBody = 'mock_raw_body';
  153. // 执行退款回调处理
  154. await expect(
  155. paymentService.handleRefundCallback(mockCallbackData, mockHeaders, mockRawBody)
  156. ).resolves.not.toThrow();
  157. });
  158. });
  159. describe('multi-tenant refund data isolation', () => {
  160. it('应该只退款特定租户的支付记录', async () => {
  161. // 创建租户1的支付记录
  162. const payment1 = new PaymentMtEntity();
  163. payment1.tenantId = 1;
  164. payment1.externalOrderId = 1004;
  165. payment1.userId = 1;
  166. payment1.totalAmount = 1000;
  167. payment1.description = '租户1订单';
  168. payment1.paymentStatus = PaymentStatus.PAID;
  169. payment1.outTradeNo = 'PAYMENT_1004_1234567890';
  170. payment1.openid = 'mock_openid';
  171. payment1.wechatTransactionId = 'mock_transaction_id';
  172. // 创建租户2的支付记录
  173. const payment2 = new PaymentMtEntity();
  174. payment2.tenantId = 2;
  175. payment2.externalOrderId = 1004;
  176. payment2.userId = 1;
  177. payment2.totalAmount = 1000;
  178. payment2.description = '租户2订单';
  179. payment2.paymentStatus = PaymentStatus.PAID;
  180. payment2.outTradeNo = 'PAYMENT_1004_1234567890';
  181. payment2.openid = 'mock_openid';
  182. payment2.wechatTransactionId = 'mock_transaction_id';
  183. await dataSource.getRepository(PaymentMtEntity).save([payment1, payment2]);
  184. // 为租户1执行退款
  185. await paymentService.refund(1, 'PAYMENT_1004_1234567890', 1000);
  186. // 验证租户1的支付记录已更新
  187. const tenant1Payment = await dataSource.getRepository(PaymentMtEntity).findOne({
  188. where: { outTradeNo: 'PAYMENT_1004_1234567890', tenantId: 1 }
  189. });
  190. expect(tenant1Payment?.refundStatus).toBe(PaymentStatus.REFUNDED);
  191. // 验证租户2的支付记录未受影响
  192. const tenant2Payment = await dataSource.getRepository(PaymentMtEntity).findOne({
  193. where: { outTradeNo: 'PAYMENT_1004_1234567890', tenantId: 2 }
  194. });
  195. expect(tenant2Payment?.refundStatus).toBeUndefined();
  196. });
  197. });
  198. });