diff --git a/packages/medusa/src/interfaces/payment-processor.ts b/packages/medusa/src/interfaces/payment-processor.ts index fbf6ff8182ec6..797f5313b2321 100644 --- a/packages/medusa/src/interfaces/payment-processor.ts +++ b/packages/medusa/src/interfaces/payment-processor.ts @@ -79,11 +79,13 @@ export interface PaymentProcessor { /** * Capture an existing session - * @param context + * @param paymentSessionData */ capturePayment( - context: PaymentProcessorContext - ): Promise + paymentSessionData: Record + ): Promise< + PaymentProcessorError | PaymentProcessorSessionResponse["session_data"] + > /** * Delete an existing session @@ -140,8 +142,10 @@ export abstract class AbstractPaymentProcessor implements PaymentProcessor { abstract init(): Promise abstract capturePayment( - context: PaymentProcessorContext - ): Promise + paymentSessionData: Record + ): Promise< + PaymentProcessorError | PaymentProcessorSessionResponse["session_data"] + > abstract authorizePayment( paymentSessionData: Record, diff --git a/packages/medusa/src/services/__tests__/payment-provider.ts b/packages/medusa/src/services/__tests__/payment-provider.ts index 5c7ec5c30d473..e1bff52abcdbb 100644 --- a/packages/medusa/src/services/__tests__/payment-provider.ts +++ b/packages/medusa/src/services/__tests__/payment-provider.ts @@ -4,6 +4,7 @@ import PaymentProviderService from "../payment-provider" import { defaultContainer, defaultPaymentSessionInputData, PaymentProcessor, } from "../__fixtures__/payment-provider" import { testPayServiceMock } from "../__mocks__/test-pay" import { EOL } from "os" +import { PaymentSessionStatus } from "../../models"; describe("PaymentProviderService", () => { describe("retrieveProvider", () => { @@ -810,4 +811,174 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => { ) }) }) + + describe("cancelPayment", () => { + const externalId = "external-id" + const payment = { + id: "payment-id", + data: { id: externalId }, + provider_id: paymentProviderId + } + + const container = createContainer({}, defaultContainer) + + const mockPaymentProcessor = new PaymentProcessor(container) + mockPaymentProcessor.cancelPayment = jest.fn().mockReturnValue(Promise.resolve()) + + container + .register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor)) + .register( + "paymentRepository", + asValue( + MockRepository({ + findOne: jest + .fn() + .mockImplementation(async () => payment), + }) + ) + ) + + const providerService = container.resolve(paymentServiceResolutionKey) + + it("successfully cancel a payment", async () => { + await providerService.cancelPayment(payment) + + const provider = container.resolve(paymentProcessorResolutionKey) + + expect(provider.cancelPayment).toBeCalledTimes(1) + expect(provider.cancelPayment).toBeCalledWith(payment.data) + }) + + it("throw an error using the provider error response", async () => { + const errResponse = { + error: "Error", + code: 400, + details: "Error details", + } + + const mockPaymentProcessor = new PaymentProcessor(container) + mockPaymentProcessor.cancelPayment = jest + .fn() + .mockReturnValue(Promise.resolve(errResponse)) + + container.register( + paymentProcessorResolutionKey, + asValue(mockPaymentProcessor) + ) + + const providerService = container.resolve(paymentServiceResolutionKey) + + const err = await providerService + .cancelPayment(payment) + .catch((e) => e) + + expect(err.message).toBe( + `${errResponse.error}:${EOL}${errResponse.details}` + ) + }) + }) + + describe("getStatus", () => { + const payment = { + data: { id: "id" }, + provider_id: paymentProviderId + } + + const container = createContainer({}, defaultContainer) + + const mockPaymentProcessor = new PaymentProcessor(container) + mockPaymentProcessor.getPaymentStatus = jest + .fn() + .mockReturnValue(Promise.resolve(PaymentSessionStatus.PENDING)) + + container + .register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor)) + .register( + "paymentRepository", + asValue( + MockRepository({ + findOne: jest + .fn() + .mockImplementation(async () => payment), + }) + ) + ) + + const providerService = container.resolve(paymentServiceResolutionKey) + + it("successfully cancel a payment", async () => { + await providerService.getStatus(payment) + + const provider = container.resolve(paymentProcessorResolutionKey) + + expect(provider.getPaymentStatus).toBeCalledTimes(1) + expect(provider.getPaymentStatus).toBeCalledWith(payment.data) + }) + }) + + describe("capturePayment", () => { + const externalId = "external-id" + const payment = { + data: { id: externalId }, + id: "payment-id", + provider_id: paymentProviderId + } + + const container = createContainer({}, defaultContainer) + + const mockPaymentProcessor = new PaymentProcessor(container) + mockPaymentProcessor.capturePayment = jest.fn().mockReturnValue(Promise.resolve(payment.data)) + + container + .register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor)) + .register( + "paymentRepository", + asValue( + MockRepository({ + findOne: jest + .fn() + .mockImplementation(async () => payment), + }) + ) + ) + + const providerService = container.resolve(paymentServiceResolutionKey) + + it("successfully capture a payment", async () => { + await providerService.capturePayment(payment) + + const provider = container.resolve(paymentProcessorResolutionKey) + + expect(provider.capturePayment).toBeCalledTimes(1) + expect(provider.capturePayment).toBeCalledWith(payment.data) + }) + + it("throw an error using the provider error response", async () => { + const errResponse = { + error: "Error", + code: 400, + details: "Error details", + } + + const mockPaymentProcessor = new PaymentProcessor(container) + mockPaymentProcessor.capturePayment = jest + .fn() + .mockReturnValue(Promise.resolve(errResponse)) + + container.register( + paymentProcessorResolutionKey, + asValue(mockPaymentProcessor) + ) + + const providerService = container.resolve(paymentServiceResolutionKey) + + const err = await providerService + .capturePayment(payment) + .catch((e) => e) + + expect(err.message).toBe( + `${errResponse.error}:${EOL}${errResponse.details}` + ) + }) + }) }) diff --git a/packages/medusa/src/services/payment-provider.ts b/packages/medusa/src/services/payment-provider.ts index c9edc11c4ec19..2279c29f70ffb 100644 --- a/packages/medusa/src/services/payment-provider.ts +++ b/packages/medusa/src/services/payment-provider.ts @@ -317,9 +317,9 @@ export default class PaymentProviderService extends TransactionBaseService { >(paymentSession.provider_id) if (provider instanceof AbstractPaymentProcessor) { - const res = await provider.deletePayment(session.data) - if (res) { - this.throwFromPaymentProcessorError(res) + const error = await provider.deletePayment(session.data) + if (error) { + this.throwFromPaymentProcessorError(error) } } else { await provider @@ -415,9 +415,9 @@ export default class PaymentProviderService extends TransactionBaseService { >(paymentSession.provider_id) if (provider instanceof AbstractPaymentProcessor) { - const res = await provider.deletePayment(paymentSession.data) - if (res) { - this.throwFromPaymentProcessorError(res) + const error = await provider.deletePayment(paymentSession.data) + if (error) { + this.throwFromPaymentProcessorError(error) } } else { await provider @@ -577,6 +577,7 @@ export default class PaymentProviderService extends TransactionBaseService { const provider = this.retrieveProvider(paymentSession.provider_id) + // TODO: Waiting discussion output before taking care of the processor support session.data = await provider .withTransaction(transactionManager) .updatePaymentData(paymentSession.data, data) @@ -595,9 +596,17 @@ export default class PaymentProviderService extends TransactionBaseService { return await this.atomicPhase_(async (transactionManager) => { const payment = await this.retrievePayment(paymentObj.id) const provider = this.retrieveProvider(payment.provider_id) - payment.data = await provider - .withTransaction(transactionManager) - .cancelPayment(payment) + + if (provider instanceof AbstractPaymentProcessor) { + const error = await provider.cancelPayment(payment.data) + if (error) { + this.throwFromPaymentProcessorError(error) + } + } else { + payment.data = await provider + .withTransaction(transactionManager) + .cancelPayment(payment) + } const now = new Date() payment.canceled_at = now.toISOString() @@ -611,6 +620,11 @@ export default class PaymentProviderService extends TransactionBaseService { async getStatus(payment: Payment): Promise { const provider = this.retrieveProvider(payment.provider_id) + + if (provider instanceof AbstractPaymentProcessor) { + return await provider.getPaymentStatus(payment.data) + } + return await provider.withTransaction(this.manager_).getStatus(payment.data) } @@ -620,9 +634,19 @@ export default class PaymentProviderService extends TransactionBaseService { return await this.atomicPhase_(async (transactionManager) => { const payment = await this.retrievePayment(paymentObj.id) const provider = this.retrieveProvider(payment.provider_id) - payment.data = await provider - .withTransaction(transactionManager) - .capturePayment(payment) + + if (provider instanceof AbstractPaymentProcessor) { + const res = await provider.capturePayment(payment.data) + if ("error" in res) { + this.throwFromPaymentProcessorError(res as PaymentProcessorError) + } else { + payment.data = res + } + } else { + payment.data = await provider + .withTransaction(transactionManager) + .capturePayment(payment) + } const now = new Date() payment.captured_at = now.toISOString()