2
0

payment-refund.integration.test.ts 9.0 KB

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