From 4264302f214f0717e8b3218a15d43def9122879c Mon Sep 17 00:00:00 2001 From: Adrien de Peretti Date: Mon, 10 Jul 2023 15:59:19 +0200 Subject: [PATCH] feat(medusa, stripe, paypal): Add update payment session support to the abstract payment processor API (#4442) * feat(medusa, stripe, paypal): Add update payment session support to the abstract payment processor API * continue to add updatePaymentData * feat: Add update payment data to paypal as well and add stripe tests * fix stripe updatePaymentData returned object type * fix tests * Create three-ravens-raise.md * fix paypal updatePayment data impl --------- Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com> --- .changeset/three-ravens-raise.md | 7 + .../src/services/paypal-provider.ts | 18 ++ .../src/core/__fixtures__/data.ts | 11 + .../src/core/__tests__/stripe-base.spec.ts | 51 ++++ .../src/core/stripe-base.ts | 20 ++ .../src/interfaces/payment-processor.ts | 17 ++ .../services/__fixtures__/payment-provider.ts | 108 +++++--- .../services/__tests__/payment-provider.ts | 234 ++++++++++-------- .../medusa/src/services/payment-provider.ts | 17 +- 9 files changed, 345 insertions(+), 138 deletions(-) create mode 100644 .changeset/three-ravens-raise.md diff --git a/.changeset/three-ravens-raise.md b/.changeset/three-ravens-raise.md new file mode 100644 index 0000000000..9cef63eda0 --- /dev/null +++ b/.changeset/three-ravens-raise.md @@ -0,0 +1,7 @@ +--- +"@medusajs/medusa": patch +"medusa-payment-paypal": patch +"medusa-payment-stripe": patch +--- + +feat(medusa, stripe, paypal): Add update payment session support to the abstract payment processor API diff --git a/packages/medusa-payment-paypal/src/services/paypal-provider.ts b/packages/medusa-payment-paypal/src/services/paypal-provider.ts index ca80bef8d8..03cb41f28e 100644 --- a/packages/medusa-payment-paypal/src/services/paypal-provider.ts +++ b/packages/medusa-payment-paypal/src/services/paypal-provider.ts @@ -17,6 +17,7 @@ import { humanizeAmount } from "medusa-core-utils" import { roundToTwo } from "./utils/utils" import { CreateOrder, PaypalSdk } from "../core" import { Logger } from "@medusajs/types" +import { MedusaError } from "@medusajs/utils" class PayPalProviderService extends AbstractPaymentProcessor { static identifier = "paypal" @@ -280,6 +281,23 @@ class PayPalProviderService extends AbstractPaymentProcessor { } } + async updatePaymentData(sessionId: string, data: Record) { + try { + // Prevent from updating the amount from here as it should go through + // the updatePayment method to perform the correct logic + if (data.amount) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + "Cannot update amount, use updatePayment instead" + ) + } + + return data + } catch (e) { + return this.buildError("An error occurred in updatePaymentData", e) + } + } + async retrieveOrderFromAuth(authorization) { const link = authorization.links.find((l) => l.rel === "up") const parts = link.href.split("/") diff --git a/packages/medusa-payment-stripe/src/core/__fixtures__/data.ts b/packages/medusa-payment-stripe/src/core/__fixtures__/data.ts index 3ef41086db..e462b4bafd 100644 --- a/packages/medusa-payment-stripe/src/core/__fixtures__/data.ts +++ b/packages/medusa-payment-stripe/src/core/__fixtures__/data.ts @@ -2,6 +2,7 @@ import { EXISTING_CUSTOMER_EMAIL, FAIL_INTENT_ID, PARTIALLY_FAIL_INTENT_ID, + STRIPE_ID, WRONG_CUSTOMER_EMAIL, } from "../../__mocks__/stripe" import { PaymentIntentDataByStatus } from "../../__fixtures__/data" @@ -212,3 +213,13 @@ export const updatePaymentContextFailWithDifferentAmount = { amount: 1000, }, } + +export const updatePaymentDataWithAmountData = { + sessionId: STRIPE_ID, + amount: 2000, +} + +export const updatePaymentDataWithoutAmountData = { + sessionId: STRIPE_ID, + customProp: "test", +} diff --git a/packages/medusa-payment-stripe/src/core/__tests__/stripe-base.spec.ts b/packages/medusa-payment-stripe/src/core/__tests__/stripe-base.spec.ts index be4d8e3592..6eb2611723 100644 --- a/packages/medusa-payment-stripe/src/core/__tests__/stripe-base.spec.ts +++ b/packages/medusa-payment-stripe/src/core/__tests__/stripe-base.spec.ts @@ -26,6 +26,8 @@ import { updatePaymentContextWithExistingCustomer, updatePaymentContextWithExistingCustomerStripeId, updatePaymentContextWithWrongEmail, + updatePaymentDataWithAmountData, + updatePaymentDataWithoutAmountData, } from "../__fixtures__/data" import { PARTIALLY_FAIL_INTENT_ID, @@ -571,4 +573,53 @@ describe("StripeTest", () => { }) }) }) + + describe("updatePaymentData", function () { + let stripeTest + + beforeAll(async () => { + const scopedContainer = { ...container } + stripeTest = new StripeTest(scopedContainer, { api_key: "test" }) + }) + + beforeEach(() => { + jest.clearAllMocks() + }) + + it("should succeed to update the payment data", async () => { + const result = await stripeTest.updatePaymentData( + updatePaymentDataWithoutAmountData.sessionId, + { ...updatePaymentDataWithoutAmountData, sessionId: undefined } + ) + + expect(StripeMock.paymentIntents.update).toHaveBeenCalled() + expect(StripeMock.paymentIntents.update).toHaveBeenCalledWith( + updatePaymentDataWithoutAmountData.sessionId, + { + customProp: updatePaymentDataWithoutAmountData.customProp, + } + ) + + expect(result).toEqual( + expect.objectContaining({ + customProp: updatePaymentDataWithoutAmountData.customProp, + }) + ) + }) + + it("should fail to update the payment data if the amount is present", async () => { + const result = await stripeTest.updatePaymentData( + updatePaymentDataWithAmountData.sessionId, + { ...updatePaymentDataWithAmountData, sessionId: undefined } + ) + + expect(StripeMock.paymentIntents.update).not.toHaveBeenCalled() + + expect(result).toEqual({ + error: "An error occurred in updatePaymentData", + code: undefined, + detail: "Cannot update amount, use updatePayment instead", + }) + }) + }) }) diff --git a/packages/medusa-payment-stripe/src/core/stripe-base.ts b/packages/medusa-payment-stripe/src/core/stripe-base.ts index d24df3bb52..9a18d06f5c 100644 --- a/packages/medusa-payment-stripe/src/core/stripe-base.ts +++ b/packages/medusa-payment-stripe/src/core/stripe-base.ts @@ -14,6 +14,7 @@ import { PaymentIntentOptions, StripeOptions, } from "../types" +import { MedusaError } from "@medusajs/utils" abstract class StripeBase extends AbstractPaymentProcessor { static identifier = "" @@ -281,6 +282,25 @@ abstract class StripeBase extends AbstractPaymentProcessor { } } + async updatePaymentData(sessionId: string, data: Record) { + try { + // Prevent from updating the amount from here as it should go through + // the updatePayment method to perform the correct logic + if (data.amount) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + "Cannot update amount, use updatePayment instead" + ) + } + + return (await this.stripe_.paymentIntents.update(sessionId, { + ...data, + })) as unknown as PaymentProcessorSessionResponse["session_data"] + } catch (e) { + return this.buildError("An error occurred in updatePaymentData", e) + } + } + /** * Constructs Stripe Webhook event * @param {object} data - the data of the webhook request: req.body diff --git a/packages/medusa/src/interfaces/payment-processor.ts b/packages/medusa/src/interfaces/payment-processor.ts index 96982d44de..a2d6adf7ff 100644 --- a/packages/medusa/src/interfaces/payment-processor.ts +++ b/packages/medusa/src/interfaces/payment-processor.ts @@ -119,6 +119,16 @@ export interface PaymentProcessor { getPaymentStatus( paymentSessionData: Record ): Promise + + /** + * Update the session data for a payment session + */ + updatePaymentData( + sessionId: string, + data: Record + ): Promise< + PaymentProcessorError | PaymentProcessorSessionResponse["session_data"] + > } /** @@ -195,6 +205,13 @@ export abstract class AbstractPaymentProcessor implements PaymentProcessor { abstract updatePayment( context: PaymentProcessorContext ): Promise + + abstract updatePaymentData( + sessionId: string, + data: Record + ): Promise< + PaymentProcessorError | PaymentProcessorSessionResponse["session_data"] + > } /** diff --git a/packages/medusa/src/services/__fixtures__/payment-provider.ts b/packages/medusa/src/services/__fixtures__/payment-provider.ts index 9c826a6c74..8c9e9a3736 100644 --- a/packages/medusa/src/services/__fixtures__/payment-provider.ts +++ b/packages/medusa/src/services/__fixtures__/payment-provider.ts @@ -1,35 +1,44 @@ import { asClass, asFunction, asValue, createContainer } from "awilix" import { MockManager, MockRepository } from "medusa-test-utils" -import PaymentProviderService from "../payment-provider"; -import { PaymentProviderServiceMock } from "../__mocks__/payment-provider"; -import { CustomerServiceMock } from "../__mocks__/customer"; -import { FlagRouter } from "../../utils/flag-router"; -import Logger from "../../loaders/logger"; +import PaymentProviderService from "../payment-provider" +import { PaymentProviderServiceMock } from "../__mocks__/payment-provider" +import { CustomerServiceMock } from "../__mocks__/customer" +import { FlagRouter } from "../../utils/flag-router" +import Logger from "../../loaders/logger" import { AbstractPaymentProcessor, PaymentProcessorContext, PaymentProcessorError, - PaymentProcessorSessionResponse -} from "../../interfaces"; -import { PaymentSessionStatus } from "../../models"; -import { PaymentServiceMock } from "../__mocks__/payment"; + PaymentProcessorSessionResponse, +} from "../../interfaces" +import { PaymentSessionStatus } from "../../models" +import { PaymentServiceMock } from "../__mocks__/payment" export const defaultContainer = createContainer() -defaultContainer.register("paymentProviderService", asClass(PaymentProviderService)) +defaultContainer.register( + "paymentProviderService", + asClass(PaymentProviderService) +) defaultContainer.register("paymentService", asValue(PaymentServiceMock)) defaultContainer.register("manager", asValue(MockManager)) defaultContainer.register("paymentSessionRepository", asValue(MockRepository())) -defaultContainer.register("paymentProviderRepository", asValue(PaymentProviderServiceMock)) +defaultContainer.register( + "paymentProviderRepository", + asValue(PaymentProviderServiceMock) +) defaultContainer.register("paymentRepository", asValue(MockRepository())) defaultContainer.register("refundRepository", asValue(MockRepository())) defaultContainer.register("customerService", asValue(CustomerServiceMock)) defaultContainer.register("featureFlagRouter", asValue(new FlagRouter({}))) defaultContainer.register("logger", asValue(Logger)) -defaultContainer.register("pp_payment_processor", asFunction((cradle) => new PaymentProcessor(cradle))) +defaultContainer.register( + "pp_payment_processor", + asFunction((cradle) => new PaymentProcessor(cradle)) +) export class PaymentProcessor extends AbstractPaymentProcessor { constructor(container) { - super(container); + super(container) } authorizePayment(context: PaymentProcessorContext): Promise< | PaymentProcessorError @@ -38,43 +47,78 @@ export class PaymentProcessor extends AbstractPaymentProcessor { data: PaymentProcessorSessionResponse["session_data"] } > { - return Promise.resolve({ } as any); + return Promise.resolve({} as any) } - getPaymentStatus(paymentSessionData: Record): Promise { - return Promise.resolve(PaymentSessionStatus.PENDING); + getPaymentStatus( + paymentSessionData: Record + ): Promise { + return Promise.resolve(PaymentSessionStatus.PENDING) } init(): Promise { - return Promise.resolve(undefined); + return Promise.resolve(undefined) } - initiatePayment(context: PaymentProcessorContext): Promise { - return Promise.resolve({ } as PaymentProcessorSessionResponse); + initiatePayment( + context: PaymentProcessorContext + ): Promise { + return Promise.resolve({} as PaymentProcessorSessionResponse) } - retrievePayment(paymentSessionData: Record): Promise { - return Promise.resolve({ }); + retrievePayment( + paymentSessionData: Record + ): Promise< + PaymentProcessorError | PaymentProcessorSessionResponse["session_data"] + > { + return Promise.resolve({}) } - updatePayment(context: PaymentProcessorContext): Promise { - return Promise.resolve(undefined); + updatePayment( + context: PaymentProcessorContext + ): Promise { + return Promise.resolve(undefined) } - capturePayment(paymentSessionData: Record): Promise { - return Promise.resolve({ }); + capturePayment( + paymentSessionData: Record + ): Promise< + PaymentProcessorError | PaymentProcessorSessionResponse["session_data"] + > { + return Promise.resolve({}) } - refundPayment(paymentSessionData: Record): Promise { - return Promise.resolve({}); + refundPayment( + paymentSessionData: Record + ): Promise< + PaymentProcessorError | PaymentProcessorSessionResponse["session_data"] + > { + return Promise.resolve({}) } - cancelPayment(paymentSessionData: Record): Promise { - return Promise.resolve({}); + cancelPayment( + paymentSessionData: Record + ): Promise< + PaymentProcessorError | PaymentProcessorSessionResponse["session_data"] + > { + return Promise.resolve({}) } - deletePayment(paymentSessionData: Record): Promise { - return Promise.resolve({}); + deletePayment( + paymentSessionData: Record + ): Promise< + PaymentProcessorError | PaymentProcessorSessionResponse["session_data"] + > { + return Promise.resolve({}) + } + + updatePaymentData( + sessionId: string, + data: Record + ): Promise< + PaymentProcessorError | PaymentProcessorSessionResponse["session_data"] + > { + return Promise.resolve({}) } } @@ -98,4 +142,4 @@ export const defaultPaymentSessionInputData = { }, currency_code: "usd", amount: 1000, -} \ No newline at end of file +} diff --git a/packages/medusa/src/services/__tests__/payment-provider.ts b/packages/medusa/src/services/__tests__/payment-provider.ts index ce3a7d42a2..a08c915842 100644 --- a/packages/medusa/src/services/__tests__/payment-provider.ts +++ b/packages/medusa/src/services/__tests__/payment-provider.ts @@ -1,6 +1,5 @@ import { asValue, createContainer } from "awilix" import { MockRepository } from "medusa-test-utils" -import PaymentProviderService from "../payment-provider" import { defaultContainer, defaultPaymentSessionInputData, @@ -8,7 +7,7 @@ import { } from "../__fixtures__/payment-provider" import { testPayServiceMock } from "../__mocks__/test-pay" import { EOL } from "os" -import { PaymentSessionStatus, RefundReason } from "../../models"; +import { PaymentSessionStatus, RefundReason } from "../../models" describe(`PaymentProviderService`, () => { const container = createContainer({}, defaultContainer) @@ -144,8 +143,8 @@ describe(`PaymentProviderService`, () => { amount: 100, currency_code: "usd", cart: { - id: "cart-test" - } + id: "cart-test", + }, } ) @@ -564,17 +563,21 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => { provider_id: defaultPaymentSessionInputData.provider_id, payment_session: { id: sessionId, - data: { id: externalId } - } + data: { id: externalId }, + }, } const container = createContainer({}, defaultContainer) const mockPaymentProcessor = new PaymentProcessor(container) - mockPaymentProcessor.retrievePayment = jest.fn().mockReturnValue(Promise.resolve({})) + mockPaymentProcessor.retrievePayment = jest + .fn() + .mockReturnValue(Promise.resolve({})) - container - .register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor)) + container.register( + paymentProcessorResolutionKey, + asValue(mockPaymentProcessor) + ) const providerService = container.resolve(paymentServiceResolutionKey) @@ -584,7 +587,9 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => { const provider = container.resolve(paymentProcessorResolutionKey) expect(provider.retrievePayment).toBeCalledTimes(1) - expect(provider.retrievePayment).toBeCalledWith(paymentInput.payment_session.data) + expect(provider.retrievePayment).toBeCalledWith( + paymentInput.payment_session.data + ) }) it("throw an error using the provider error response", async () => { @@ -621,27 +626,27 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => { const paymentSession = { id: "test-session", data: { id: externalId }, - provider_id: paymentProviderId + provider_id: paymentProviderId, } const context = { ip: "0.0.0.0" } const container = createContainer({}, defaultContainer) const mockPaymentProcessor = new PaymentProcessor(container) - mockPaymentProcessor.authorizePayment = jest.fn().mockReturnValue(Promise.resolve({})) + mockPaymentProcessor.authorizePayment = jest + .fn() + .mockReturnValue(Promise.resolve({})) container .register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor)) .register( - "paymentSessionRepository", - asValue( - MockRepository({ - findOne: jest - .fn() - .mockImplementation(async () => ({ data: {} })), - }) - ) + "paymentSessionRepository", + asValue( + MockRepository({ + findOne: jest.fn().mockImplementation(async () => ({ data: {} })), + }) ) + ) const providerService = container.resolve(paymentServiceResolutionKey) @@ -651,7 +656,10 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => { const provider = container.resolve(paymentProcessorResolutionKey) expect(provider.authorizePayment).toBeCalledTimes(1) - expect(provider.authorizePayment).toBeCalledWith(paymentSession.data, context) + expect(provider.authorizePayment).toBeCalledWith( + paymentSession.data, + context + ) }) it("throw an error using the provider error response", async () => { @@ -688,26 +696,26 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => { const payment = { id: "payment-id", data: { id: externalId }, - provider_id: paymentProviderId + provider_id: paymentProviderId, } const container = createContainer({}, defaultContainer) const mockPaymentProcessor = new PaymentProcessor(container) - mockPaymentProcessor.cancelPayment = jest.fn().mockReturnValue(Promise.resolve()) + mockPaymentProcessor.cancelPayment = jest + .fn() + .mockReturnValue(Promise.resolve()) container .register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor)) .register( - "paymentRepository", - asValue( - MockRepository({ - findOne: jest - .fn() - .mockImplementation(async () => payment), - }) - ) + "paymentRepository", + asValue( + MockRepository({ + findOne: jest.fn().mockImplementation(async () => payment), + }) ) + ) const providerService = container.resolve(paymentServiceResolutionKey) @@ -739,9 +747,7 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => { const providerService = container.resolve(paymentServiceResolutionKey) - const err = await providerService - .cancelPayment(payment) - .catch((e) => e) + const err = await providerService.cancelPayment(payment).catch((e) => e) expect(err.message).toBe( `${errResponse.error}:${EOL}${errResponse.detail}` @@ -752,7 +758,7 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => { describe("getStatus", () => { const payment = { data: { id: "id" }, - provider_id: paymentProviderId + provider_id: paymentProviderId, } const container = createContainer({}, defaultContainer) @@ -765,15 +771,13 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => { container .register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor)) .register( - "paymentRepository", - asValue( - MockRepository({ - findOne: jest - .fn() - .mockImplementation(async () => payment), - }) - ) + "paymentRepository", + asValue( + MockRepository({ + findOne: jest.fn().mockImplementation(async () => payment), + }) ) + ) const providerService = container.resolve(paymentServiceResolutionKey) @@ -792,26 +796,26 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => { const payment = { data: { id: externalId }, id: "payment-id", - provider_id: paymentProviderId + provider_id: paymentProviderId, } const container = createContainer({}, defaultContainer) const mockPaymentProcessor = new PaymentProcessor(container) - mockPaymentProcessor.capturePayment = jest.fn().mockReturnValue(Promise.resolve(payment.data)) + mockPaymentProcessor.capturePayment = jest + .fn() + .mockReturnValue(Promise.resolve(payment.data)) container .register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor)) .register( - "paymentRepository", - asValue( - MockRepository({ - findOne: jest - .fn() - .mockImplementation(async () => payment), - }) - ) + "paymentRepository", + asValue( + MockRepository({ + findOne: jest.fn().mockImplementation(async () => payment), + }) ) + ) const providerService = container.resolve(paymentServiceResolutionKey) @@ -843,9 +847,7 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => { const providerService = container.resolve(paymentServiceResolutionKey) - const err = await providerService - .capturePayment(payment) - .catch((e) => e) + const err = await providerService.capturePayment(payment).catch((e) => e) expect(err.message).toBe( `${errResponse.error}:${EOL}${errResponse.detail}` @@ -858,62 +860,85 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => { jest.clearAllMocks() }) - const payments = [{ - id: "p1", - captured_at: new Date(), - data: { id: "id1" }, - amount: 1000, - amount_refunded: 0, - provider_id: paymentProviderId, - }, { - id: "p2", - captured_at: new Date(), - data: { id: "id2" }, - amount: 1000, - amount_refunded: 0, - provider_id: paymentProviderId, - }, { - id: "p3", - captured_at: new Date(), - data: { id: "id3" }, - amount: 1000, - amount_refunded: 1000, // already fully refunded - provider_id: paymentProviderId, - }] + const payments = [ + { + id: "p1", + captured_at: new Date(), + data: { id: "id1" }, + amount: 1000, + amount_refunded: 0, + provider_id: paymentProviderId, + }, + { + id: "p2", + captured_at: new Date(), + data: { id: "id2" }, + amount: 1000, + amount_refunded: 0, + provider_id: paymentProviderId, + }, + { + id: "p3", + captured_at: new Date(), + data: { id: "id3" }, + amount: 1000, + amount_refunded: 1000, // already fully refunded + provider_id: paymentProviderId, + }, + ] const container = createContainer({}, defaultContainer) const mockPaymentProcessor = new PaymentProcessor(container) - mockPaymentProcessor.refundPayment = jest.fn().mockImplementation(async (data) => data) + mockPaymentProcessor.refundPayment = jest + .fn() + .mockImplementation(async (data) => data) container .register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor)) .register( - "paymentRepository", - asValue( - MockRepository({ - find: jest - .fn() - .mockImplementation(async () => payments), - }) - ) + "paymentRepository", + asValue( + MockRepository({ + find: jest.fn().mockImplementation(async () => payments), + }) ) + ) const providerService = container.resolve(paymentServiceResolutionKey) const paymentRepo = container.resolve("paymentRepository") it("successfully refund the payments", async () => { - await providerService.refundPayment(payments, 1500, RefundReason.OTHER, "note") + await providerService.refundPayment( + payments, + 1500, + RefundReason.OTHER, + "note" + ) const provider = container.resolve(paymentProcessorResolutionKey) expect(provider.refundPayment).toBeCalledTimes(2) - expect(provider.refundPayment).toHaveBeenNthCalledWith(1, payments[0].data, 1000) - expect(provider.refundPayment).toHaveBeenNthCalledWith(2, payments[1].data, 500) + expect(provider.refundPayment).toHaveBeenNthCalledWith( + 1, + payments[0].data, + 1000 + ) + expect(provider.refundPayment).toHaveBeenNthCalledWith( + 2, + payments[1].data, + 500 + ) expect(paymentRepo.save).toBeCalledTimes(2) - expect(paymentRepo.save).toHaveBeenNthCalledWith(1, expect.objectContaining({ amount_refunded: 1000 })) - expect(paymentRepo.save).toHaveBeenNthCalledWith(2, expect.objectContaining({ amount_refunded: 500 })) + expect(paymentRepo.save).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ amount_refunded: 1000 }) + ) + expect(paymentRepo.save).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ amount_refunded: 500 }) + ) }) it("throw an error using the provider error response", async () => { @@ -935,9 +960,7 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => { const providerService = container.resolve(paymentServiceResolutionKey) - const err = await providerService - .refundPayment(payments) - .catch((e) => e) + const err = await providerService.refundPayment(payments).catch((e) => e) expect(err.message).toBe( `${errResponse.error}:${EOL}${errResponse.detail}` @@ -958,16 +981,25 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => { const container = createContainer({}, defaultContainer) const mockPaymentProcessor = new PaymentProcessor(container) - mockPaymentProcessor.refundPayment = jest.fn().mockImplementation(async (data) => data) + mockPaymentProcessor.refundPayment = jest + .fn() + .mockImplementation(async (data) => data) - container - .register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor)) + container.register( + paymentProcessorResolutionKey, + asValue(mockPaymentProcessor) + ) const providerService = container.resolve(paymentServiceResolutionKey) const paymentRepo = container.resolve("paymentRepository") it("successfully refund the payments", async () => { - await providerService.refundFromPayment(payment, 500, RefundReason.OTHER, "note") + await providerService.refundFromPayment( + payment, + 500, + RefundReason.OTHER, + "note" + ) const provider = container.resolve(paymentProcessorResolutionKey) @@ -975,7 +1007,9 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => { expect(provider.refundPayment).toBeCalledWith(payment.data, 500) expect(paymentRepo.save).toBeCalledTimes(1) - expect(paymentRepo.save).toBeCalledWith(expect.objectContaining({ amount_refunded: 500 })) + expect(paymentRepo.save).toBeCalledWith( + expect.objectContaining({ amount_refunded: 500 }) + ) }) it("throw an error using the provider error response", async () => { diff --git a/packages/medusa/src/services/payment-provider.ts b/packages/medusa/src/services/payment-provider.ts index d50c6b00a1..ffe8223cd3 100644 --- a/packages/medusa/src/services/payment-provider.ts +++ b/packages/medusa/src/services/payment-provider.ts @@ -575,18 +575,23 @@ export default class PaymentProviderService extends TransactionBaseService { const provider = this.retrieveProvider(paymentSession.provider_id) + let updatedData if (provider instanceof AbstractPaymentProcessor) { - throw new MedusaError( - MedusaError.Types.NOT_ALLOWED, - `The payment provider ${paymentSession.provider_id} is of type PaymentProcessor. PaymentProcessors cannot update payment session data.` - ) + const res = await provider.updatePaymentData(paymentSession.id, data) + if ("error" in res) { + this.throwFromPaymentProcessorError(res as PaymentProcessorError) + } else { + updatedData = res + } } else { - session.data = await provider + updatedData = await provider .withTransaction(transactionManager) .updatePaymentData(paymentSession.data, data) - session.status = paymentSession.status } + Object.assign(session.data, updatedData) + session.status = paymentSession.status + const sessionRepo = transactionManager.withRepository( this.paymentSessionRepository_ )