diff --git a/integration-tests/http/__tests__/payment/admin/payment.spec.ts b/integration-tests/http/__tests__/payment/admin/payment.spec.ts index f672f6ec6ab97..13f473336e24e 100644 --- a/integration-tests/http/__tests__/payment/admin/payment.spec.ts +++ b/integration-tests/http/__tests__/payment/admin/payment.spec.ts @@ -170,6 +170,104 @@ medusaIntegrationTestRunner({ ) }) + it("should issue multiple refunds", async () => { + await api.post( + `/admin/payments/${payment.id}/capture`, + undefined, + adminHeaders + ) + + const refundReason = ( + await api.post(`/admin/refund-reasons`, { label: "test" }, adminHeaders) + ).data.refund_reason + + await api.post( + `/admin/payments/${payment.id}/refund`, + { + amount: 250, + refund_reason_id: refundReason.id, + note: "Do not like it", + }, + adminHeaders + ) + + await api.post( + `/admin/payments/${payment.id}/refund`, + { + amount: 250, + refund_reason_id: refundReason.id, + note: "Do not like it", + }, + adminHeaders + ) + + const refundedPayment = ( + await api.get(`/admin/payments/${payment.id}`, adminHeaders) + ).data.payment + + expect(refundedPayment).toEqual( + expect.objectContaining({ + id: payment.id, + currency_code: "usd", + amount: 1000, + captured_at: expect.any(String), + captures: [ + expect.objectContaining({ + amount: 1000, + }), + ], + refunds: [ + expect.objectContaining({ + amount: 250, + note: "Do not like it", + }), + expect.objectContaining({ + amount: 250, + note: "Do not like it", + }), + ], + }) + ) + }) + + it("should throw if refund exceeds captured total", async () => { + await api.post( + `/admin/payments/${payment.id}/capture`, + undefined, + adminHeaders + ) + + const refundReason = ( + await api.post(`/admin/refund-reasons`, { label: "test" }, adminHeaders) + ).data.refund_reason + + await api.post( + `/admin/payments/${payment.id}/refund`, + { + amount: 250, + refund_reason_id: refundReason.id, + note: "Do not like it", + }, + adminHeaders + ) + + const e = await api + .post( + `/admin/payments/${payment.id}/refund`, + { + amount: 1000, + refund_reason_id: refundReason.id, + note: "Do not like it", + }, + adminHeaders + ) + .catch((e) => e) + + expect(e.response.data.message).toEqual( + "You cannot refund more than what is captured on the payment." + ) + }) + it("should not update payment collection of other orders", async () => { await setupTaxStructure(container.resolve(ModuleRegistrationName.TAX)) await seedStorefrontDefaults(container, "dkk") diff --git a/packages/modules/payment/integration-tests/__tests__/services/payment-module/index.spec.ts b/packages/modules/payment/integration-tests/__tests__/services/payment-module/index.spec.ts index 4e0989a136900..afc8e637448e4 100644 --- a/packages/modules/payment/integration-tests/__tests__/services/payment-module/index.spec.ts +++ b/packages/modules/payment/integration-tests/__tests__/services/payment-module/index.spec.ts @@ -685,6 +685,52 @@ moduleIntegrationTestRunner({ ) }) + it("should fully refund a payment through two refunds", async () => { + await service.capturePayment({ + amount: 100, + payment_id: "pay-id-2", + }) + + const refundedPaymentOne = await service.refundPayment({ + amount: 50, + payment_id: "pay-id-2", + }) + + const refundedPaymentTwo = await service.refundPayment({ + amount: 50, + payment_id: "pay-id-2", + }) + + expect(refundedPaymentOne).toEqual( + expect.objectContaining({ + id: "pay-id-2", + amount: 100, + refunds: [ + expect.objectContaining({ + created_by: null, + amount: 50, + }), + ], + }) + ) + expect(refundedPaymentTwo).toEqual( + expect.objectContaining({ + id: "pay-id-2", + amount: 100, + refunds: [ + expect.objectContaining({ + created_by: null, + amount: 50, + }), + expect.objectContaining({ + created_by: null, + amount: 50, + }), + ], + }) + ) + }) + it("should throw if refund is greater than captured amount", async () => { await service.capturePayment({ amount: 50, diff --git a/packages/modules/payment/src/services/payment-module.ts b/packages/modules/payment/src/services/payment-module.ts index 35ef3ef68b60f..98f36cdcceb67 100644 --- a/packages/modules/payment/src/services/payment-module.ts +++ b/packages/modules/payment/src/services/payment-module.ts @@ -717,10 +717,25 @@ export default class PaymentModuleService data: CreateRefundDTO, @MedusaContext() sharedContext: Context = {} ): Promise { - const payment = await this.refundPayment_(data, sharedContext) + const payment = await this.paymentService_.retrieve( + data.payment_id, + { + select: [ + "id", + "data", + "provider_id", + "payment_collection_id", + "amount", + "raw_amount", + ], + relations: ["captures.raw_amount", "refunds.raw_amount"], + }, + sharedContext + ) + const refund = await this.refundPayment_(payment, data, sharedContext) try { - await this.refundPaymentFromProvider_(payment, sharedContext) + await this.refundPaymentFromProvider_(payment, refund, sharedContext) } catch (error) { await super.deleteRefunds(data.payment_id, sharedContext) throw error @@ -740,25 +755,10 @@ export default class PaymentModuleService @InjectTransactionManager("baseRepository_") private async refundPayment_( + payment: Payment, data: CreateRefundDTO, @MedusaContext() sharedContext: Context = {} - ): Promise { - const payment = await this.paymentService_.retrieve( - data.payment_id, - { - select: [ - "id", - "data", - "provider_id", - "payment_collection_id", - "amount", - "raw_amount", - ], - relations: ["captures.raw_amount", "refunds.raw_amount"], - }, - sharedContext - ) - + ): Promise { if (!data.amount) { data.amount = payment.amount as BigNumberInput } @@ -771,10 +771,7 @@ export default class PaymentModuleService return MathBN.add(refundedAmount, next.raw_amount) }, MathBN.convert(0)) - const totalRefundedAmount = MathBN.add( - refundedAmount, - data.amount - ) + const totalRefundedAmount = MathBN.add(refundedAmount, data.amount) if (MathBN.lt(capturedAmount, totalRefundedAmount)) { throw new MedusaError( @@ -783,7 +780,7 @@ export default class PaymentModuleService ) } - await this.refundService_.create( + const refund = await this.refundService_.create( { payment: data.payment_id, amount: data.amount, @@ -794,12 +791,13 @@ export default class PaymentModuleService sharedContext ) - return payment + return refund } @InjectManager("baseRepository_") private async refundPaymentFromProvider_( payment: Payment, + refund: Refund, @MedusaContext() sharedContext: Context = {} ) { const paymentData = await this.paymentProviderService_.refundPayment( @@ -807,7 +805,7 @@ export default class PaymentModuleService data: payment.data!, provider_id: payment.provider_id, }, - payment.raw_amount + refund.raw_amount ) await this.paymentService_.update(