diff --git a/packages/medusa-payment-paypal/src/__mocks__/@paypal/checkout-server-sdk.js b/packages/medusa-payment-paypal/src/__mocks__/@paypal/checkout-server-sdk.js index 8c394649bb..379ee7e24c 100644 --- a/packages/medusa-payment-paypal/src/__mocks__/@paypal/checkout-server-sdk.js +++ b/packages/medusa-payment-paypal/src/__mocks__/@paypal/checkout-server-sdk.js @@ -27,7 +27,11 @@ export const PayPalMock = { payments: { AuthorizationsGetRequest: jest.fn().mockImplementation(() => {}), - AuthorizationsVoidRequest: jest.fn().mockImplementation(() => {}), + AuthorizationsVoidRequest: jest.fn().mockImplementation(() => { + return { + status: "VOIDED" + } + }), AuthorizationsCaptureRequest: jest.fn().mockImplementation(() => { return { result: { @@ -41,6 +45,8 @@ export const PayPalMock = { result: { id: "test", }, + status: "COMPLETED", + invoice_id: 'invoice_id', body: null, requestBody: function (d) { this.body = d @@ -74,11 +80,29 @@ export const PayPalMock = { }, } }), - OrdersGetRequest: jest.fn().mockImplementation(() => { - return { - result: { - id: "test", - }, + OrdersGetRequest: jest.fn().mockImplementation((id) => { + switch (id) { + case "test-refund": + return { + result: { + id: "test-refund", + status: "COMPLETED", + invoice_id: "invoice_id" + } + } + case "test-voided": + return { + result: { + id: "test-voided", + status: "VOIDED" + } + } + default: + return { + result: { + id: "test", + }, + } } }), }, diff --git a/packages/medusa-payment-paypal/src/services/__tests__/paypal-provider.js b/packages/medusa-payment-paypal/src/services/__tests__/paypal-provider.js index a57368a757..7dd4afd470 100644 --- a/packages/medusa-payment-paypal/src/services/__tests__/paypal-provider.js +++ b/packages/medusa-payment-paypal/src/services/__tests__/paypal-provider.js @@ -310,7 +310,7 @@ describe("PaypalProviderService", () => { "test_cap" ) expect(PayPalMock.orders.OrdersGetRequest).toHaveBeenCalledWith("test") - expect(PayPalClientMock.execute).toHaveBeenCalledTimes(2) + expect(PayPalClientMock.execute).toHaveBeenCalledTimes(3) expect(result.id).toEqual("test") }) @@ -338,9 +338,41 @@ describe("PaypalProviderService", () => { PayPalMock.payments.AuthorizationsVoidRequest ).toHaveBeenCalledWith("test_auth") expect(PayPalMock.orders.OrdersGetRequest).toHaveBeenCalledWith("test") - expect(PayPalClientMock.execute).toHaveBeenCalledTimes(2) + expect(PayPalClientMock.execute).toHaveBeenCalledTimes(3) expect(result.id).toEqual("test") }) + + it("should return the order if already canceled", async () => { + result = await paypalProviderService.cancelPayment({ + currency_code: "eur", + data: { id: "test-voided" }, + }) + + expect(PayPalMock.payments.AuthorizationsVoidRequest).not.toHaveBeenCalled() + expect(PayPalMock.payments.CapturesRefundRequest).not.toHaveBeenCalled() + expect(PayPalMock.orders.OrdersGetRequest).toHaveBeenCalledWith("test-voided") + expect(PayPalClientMock.execute).toHaveBeenCalledTimes(1) + + expect(result.id).toEqual("test-voided") + expect(result.status).toEqual("VOIDED") + }) + + it("should return the order if already fully refund", async () => { + result = await paypalProviderService.cancelPayment({ + currency_code: "eur", + data: { + id: "test-refund", + }, + }) + + expect(PayPalMock.payments.AuthorizationsVoidRequest).not.toHaveBeenCalled() + expect(PayPalMock.payments.CapturesRefundRequest).not.toHaveBeenCalled() + expect(PayPalMock.orders.OrdersGetRequest).toHaveBeenCalledWith("test-refund") + expect(PayPalClientMock.execute).toHaveBeenCalledTimes(1) + + expect(result.id).toEqual("test-refund") + expect(result.status).toEqual("COMPLETED") + }) }) }) diff --git a/packages/medusa-payment-paypal/src/services/paypal-provider.js b/packages/medusa-payment-paypal/src/services/paypal-provider.js index 4631a8c612..690d0e1ed8 100644 --- a/packages/medusa-payment-paypal/src/services/paypal-provider.js +++ b/packages/medusa-payment-paypal/src/services/paypal-provider.js @@ -1,4 +1,3 @@ -import _ from "lodash" import { humanizeAmount, zeroDecimalCurrencies } from "medusa-core-utils" import PayPal from "@paypal/checkout-server-sdk" import { PaymentService } from "medusa-interfaces" @@ -72,7 +71,6 @@ class PayPalProviderService extends PaymentService { return "requires_more" case "VOIDED": return "canceled" - // return "captured" default: return status } @@ -284,11 +282,18 @@ class PayPalProviderService extends PaymentService { } /** - * Cancels payment for Stripe payment intent. - * @param {object} paymentData - payment method data from cart + * Cancels payment for paypal payment. + * @param {Payment} payment - payment object * @returns {Promise} canceled payment intent */ async cancelPayment(payment) { + const order = await this.retrievePayment(payment.data) + const isAlreadyCanceled = order.status === "VOIDED" + const isCanceledAndFullyRefund = order.status === "COMPLETED" && !!order.invoice_id + if (isAlreadyCanceled || isCanceledAndFullyRefund) { + return order + } + try { const { purchase_units } = payment.data if (payment.captured_at) { @@ -303,7 +308,7 @@ class PayPalProviderService extends PaymentService { await this.paypal_.execute(request) } - return this.retrievePayment(payment.data) + return await this.retrievePayment(payment.data) } catch (error) { throw error }