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>
This commit is contained in:
co-authored by
Oliver Windall Juhl
parent
1903217705
commit
4264302f21
@@ -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
|
||||
@@ -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<string, unknown>) {
|
||||
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("/")
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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<string, unknown>) {
|
||||
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
|
||||
|
||||
@@ -119,6 +119,16 @@ export interface PaymentProcessor {
|
||||
getPaymentStatus(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<PaymentSessionStatus>
|
||||
|
||||
/**
|
||||
* Update the session data for a payment session
|
||||
*/
|
||||
updatePaymentData(
|
||||
sessionId: string,
|
||||
data: Record<string, unknown>
|
||||
): Promise<
|
||||
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||
>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,6 +205,13 @@ export abstract class AbstractPaymentProcessor implements PaymentProcessor {
|
||||
abstract updatePayment(
|
||||
context: PaymentProcessorContext
|
||||
): Promise<PaymentProcessorError | PaymentProcessorSessionResponse | void>
|
||||
|
||||
abstract updatePaymentData(
|
||||
sessionId: string,
|
||||
data: Record<string, unknown>
|
||||
): Promise<
|
||||
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||
>
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<string, unknown>): Promise<PaymentSessionStatus> {
|
||||
return Promise.resolve(PaymentSessionStatus.PENDING);
|
||||
getPaymentStatus(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<PaymentSessionStatus> {
|
||||
return Promise.resolve(PaymentSessionStatus.PENDING)
|
||||
}
|
||||
|
||||
init(): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
return Promise.resolve(undefined)
|
||||
}
|
||||
|
||||
initiatePayment(context: PaymentProcessorContext): Promise<PaymentProcessorError | PaymentProcessorSessionResponse> {
|
||||
return Promise.resolve({ } as PaymentProcessorSessionResponse);
|
||||
initiatePayment(
|
||||
context: PaymentProcessorContext
|
||||
): Promise<PaymentProcessorError | PaymentProcessorSessionResponse> {
|
||||
return Promise.resolve({} as PaymentProcessorSessionResponse)
|
||||
}
|
||||
|
||||
retrievePayment(paymentSessionData: Record<string, unknown>): Promise<PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]> {
|
||||
return Promise.resolve({ });
|
||||
retrievePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<
|
||||
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||
> {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
updatePayment(context: PaymentProcessorContext): Promise<PaymentProcessorError | void> {
|
||||
return Promise.resolve(undefined);
|
||||
updatePayment(
|
||||
context: PaymentProcessorContext
|
||||
): Promise<PaymentProcessorError | void> {
|
||||
return Promise.resolve(undefined)
|
||||
}
|
||||
|
||||
capturePayment(paymentSessionData: Record<string, unknown>): Promise<PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]> {
|
||||
return Promise.resolve({ });
|
||||
capturePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<
|
||||
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||
> {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
refundPayment(paymentSessionData: Record<string, unknown>): Promise<PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]> {
|
||||
return Promise.resolve({});
|
||||
refundPayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<
|
||||
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||
> {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
cancelPayment(paymentSessionData: Record<string, unknown>): Promise<PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]> {
|
||||
return Promise.resolve({});
|
||||
cancelPayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<
|
||||
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||
> {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
deletePayment(paymentSessionData: Record<string, unknown>): Promise<PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]> {
|
||||
return Promise.resolve({});
|
||||
deletePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<
|
||||
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||
> {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
updatePaymentData(
|
||||
sessionId: string,
|
||||
data: Record<string, unknown>
|
||||
): Promise<
|
||||
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||
> {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,4 +142,4 @@ export const defaultPaymentSessionInputData = {
|
||||
},
|
||||
currency_code: "usd",
|
||||
amount: 1000,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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_
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user