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 { roundToTwo } from "./utils/utils"
|
||||||
import { CreateOrder, PaypalSdk } from "../core"
|
import { CreateOrder, PaypalSdk } from "../core"
|
||||||
import { Logger } from "@medusajs/types"
|
import { Logger } from "@medusajs/types"
|
||||||
|
import { MedusaError } from "@medusajs/utils"
|
||||||
|
|
||||||
class PayPalProviderService extends AbstractPaymentProcessor {
|
class PayPalProviderService extends AbstractPaymentProcessor {
|
||||||
static identifier = "paypal"
|
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) {
|
async retrieveOrderFromAuth(authorization) {
|
||||||
const link = authorization.links.find((l) => l.rel === "up")
|
const link = authorization.links.find((l) => l.rel === "up")
|
||||||
const parts = link.href.split("/")
|
const parts = link.href.split("/")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
EXISTING_CUSTOMER_EMAIL,
|
EXISTING_CUSTOMER_EMAIL,
|
||||||
FAIL_INTENT_ID,
|
FAIL_INTENT_ID,
|
||||||
PARTIALLY_FAIL_INTENT_ID,
|
PARTIALLY_FAIL_INTENT_ID,
|
||||||
|
STRIPE_ID,
|
||||||
WRONG_CUSTOMER_EMAIL,
|
WRONG_CUSTOMER_EMAIL,
|
||||||
} from "../../__mocks__/stripe"
|
} from "../../__mocks__/stripe"
|
||||||
import { PaymentIntentDataByStatus } from "../../__fixtures__/data"
|
import { PaymentIntentDataByStatus } from "../../__fixtures__/data"
|
||||||
@@ -212,3 +213,13 @@ export const updatePaymentContextFailWithDifferentAmount = {
|
|||||||
amount: 1000,
|
amount: 1000,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const updatePaymentDataWithAmountData = {
|
||||||
|
sessionId: STRIPE_ID,
|
||||||
|
amount: 2000,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updatePaymentDataWithoutAmountData = {
|
||||||
|
sessionId: STRIPE_ID,
|
||||||
|
customProp: "test",
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import {
|
|||||||
updatePaymentContextWithExistingCustomer,
|
updatePaymentContextWithExistingCustomer,
|
||||||
updatePaymentContextWithExistingCustomerStripeId,
|
updatePaymentContextWithExistingCustomerStripeId,
|
||||||
updatePaymentContextWithWrongEmail,
|
updatePaymentContextWithWrongEmail,
|
||||||
|
updatePaymentDataWithAmountData,
|
||||||
|
updatePaymentDataWithoutAmountData,
|
||||||
} from "../__fixtures__/data"
|
} from "../__fixtures__/data"
|
||||||
import {
|
import {
|
||||||
PARTIALLY_FAIL_INTENT_ID,
|
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,
|
PaymentIntentOptions,
|
||||||
StripeOptions,
|
StripeOptions,
|
||||||
} from "../types"
|
} from "../types"
|
||||||
|
import { MedusaError } from "@medusajs/utils"
|
||||||
|
|
||||||
abstract class StripeBase extends AbstractPaymentProcessor {
|
abstract class StripeBase extends AbstractPaymentProcessor {
|
||||||
static identifier = ""
|
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
|
* Constructs Stripe Webhook event
|
||||||
* @param {object} data - the data of the webhook request: req.body
|
* @param {object} data - the data of the webhook request: req.body
|
||||||
|
|||||||
@@ -119,6 +119,16 @@ export interface PaymentProcessor {
|
|||||||
getPaymentStatus(
|
getPaymentStatus(
|
||||||
paymentSessionData: Record<string, unknown>
|
paymentSessionData: Record<string, unknown>
|
||||||
): Promise<PaymentSessionStatus>
|
): 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(
|
abstract updatePayment(
|
||||||
context: PaymentProcessorContext
|
context: PaymentProcessorContext
|
||||||
): Promise<PaymentProcessorError | PaymentProcessorSessionResponse | void>
|
): 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 { asClass, asFunction, asValue, createContainer } from "awilix"
|
||||||
import { MockManager, MockRepository } from "medusa-test-utils"
|
import { MockManager, MockRepository } from "medusa-test-utils"
|
||||||
import PaymentProviderService from "../payment-provider";
|
import PaymentProviderService from "../payment-provider"
|
||||||
import { PaymentProviderServiceMock } from "../__mocks__/payment-provider";
|
import { PaymentProviderServiceMock } from "../__mocks__/payment-provider"
|
||||||
import { CustomerServiceMock } from "../__mocks__/customer";
|
import { CustomerServiceMock } from "../__mocks__/customer"
|
||||||
import { FlagRouter } from "../../utils/flag-router";
|
import { FlagRouter } from "../../utils/flag-router"
|
||||||
import Logger from "../../loaders/logger";
|
import Logger from "../../loaders/logger"
|
||||||
import {
|
import {
|
||||||
AbstractPaymentProcessor,
|
AbstractPaymentProcessor,
|
||||||
PaymentProcessorContext,
|
PaymentProcessorContext,
|
||||||
PaymentProcessorError,
|
PaymentProcessorError,
|
||||||
PaymentProcessorSessionResponse
|
PaymentProcessorSessionResponse,
|
||||||
} from "../../interfaces";
|
} from "../../interfaces"
|
||||||
import { PaymentSessionStatus } from "../../models";
|
import { PaymentSessionStatus } from "../../models"
|
||||||
import { PaymentServiceMock } from "../__mocks__/payment";
|
import { PaymentServiceMock } from "../__mocks__/payment"
|
||||||
|
|
||||||
export const defaultContainer = createContainer()
|
export const defaultContainer = createContainer()
|
||||||
defaultContainer.register("paymentProviderService", asClass(PaymentProviderService))
|
defaultContainer.register(
|
||||||
|
"paymentProviderService",
|
||||||
|
asClass(PaymentProviderService)
|
||||||
|
)
|
||||||
defaultContainer.register("paymentService", asValue(PaymentServiceMock))
|
defaultContainer.register("paymentService", asValue(PaymentServiceMock))
|
||||||
defaultContainer.register("manager", asValue(MockManager))
|
defaultContainer.register("manager", asValue(MockManager))
|
||||||
defaultContainer.register("paymentSessionRepository", asValue(MockRepository()))
|
defaultContainer.register("paymentSessionRepository", asValue(MockRepository()))
|
||||||
defaultContainer.register("paymentProviderRepository", asValue(PaymentProviderServiceMock))
|
defaultContainer.register(
|
||||||
|
"paymentProviderRepository",
|
||||||
|
asValue(PaymentProviderServiceMock)
|
||||||
|
)
|
||||||
defaultContainer.register("paymentRepository", asValue(MockRepository()))
|
defaultContainer.register("paymentRepository", asValue(MockRepository()))
|
||||||
defaultContainer.register("refundRepository", asValue(MockRepository()))
|
defaultContainer.register("refundRepository", asValue(MockRepository()))
|
||||||
defaultContainer.register("customerService", asValue(CustomerServiceMock))
|
defaultContainer.register("customerService", asValue(CustomerServiceMock))
|
||||||
defaultContainer.register("featureFlagRouter", asValue(new FlagRouter({})))
|
defaultContainer.register("featureFlagRouter", asValue(new FlagRouter({})))
|
||||||
defaultContainer.register("logger", asValue(Logger))
|
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 {
|
export class PaymentProcessor extends AbstractPaymentProcessor {
|
||||||
constructor(container) {
|
constructor(container) {
|
||||||
super(container);
|
super(container)
|
||||||
}
|
}
|
||||||
authorizePayment(context: PaymentProcessorContext): Promise<
|
authorizePayment(context: PaymentProcessorContext): Promise<
|
||||||
| PaymentProcessorError
|
| PaymentProcessorError
|
||||||
@@ -38,43 +47,78 @@ export class PaymentProcessor extends AbstractPaymentProcessor {
|
|||||||
data: PaymentProcessorSessionResponse["session_data"]
|
data: PaymentProcessorSessionResponse["session_data"]
|
||||||
}
|
}
|
||||||
> {
|
> {
|
||||||
return Promise.resolve({ } as any);
|
return Promise.resolve({} as any)
|
||||||
}
|
}
|
||||||
|
|
||||||
getPaymentStatus(paymentSessionData: Record<string, unknown>): Promise<PaymentSessionStatus> {
|
getPaymentStatus(
|
||||||
return Promise.resolve(PaymentSessionStatus.PENDING);
|
paymentSessionData: Record<string, unknown>
|
||||||
|
): Promise<PaymentSessionStatus> {
|
||||||
|
return Promise.resolve(PaymentSessionStatus.PENDING)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): Promise<void> {
|
init(): Promise<void> {
|
||||||
return Promise.resolve(undefined);
|
return Promise.resolve(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
initiatePayment(context: PaymentProcessorContext): Promise<PaymentProcessorError | PaymentProcessorSessionResponse> {
|
initiatePayment(
|
||||||
return Promise.resolve({ } as PaymentProcessorSessionResponse);
|
context: PaymentProcessorContext
|
||||||
|
): Promise<PaymentProcessorError | PaymentProcessorSessionResponse> {
|
||||||
|
return Promise.resolve({} as PaymentProcessorSessionResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
retrievePayment(paymentSessionData: Record<string, unknown>): Promise<PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]> {
|
retrievePayment(
|
||||||
return Promise.resolve({ });
|
paymentSessionData: Record<string, unknown>
|
||||||
|
): Promise<
|
||||||
|
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||||
|
> {
|
||||||
|
return Promise.resolve({})
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePayment(context: PaymentProcessorContext): Promise<PaymentProcessorError | void> {
|
updatePayment(
|
||||||
return Promise.resolve(undefined);
|
context: PaymentProcessorContext
|
||||||
|
): Promise<PaymentProcessorError | void> {
|
||||||
|
return Promise.resolve(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
capturePayment(paymentSessionData: Record<string, unknown>): Promise<PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]> {
|
capturePayment(
|
||||||
return Promise.resolve({ });
|
paymentSessionData: Record<string, unknown>
|
||||||
|
): Promise<
|
||||||
|
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||||
|
> {
|
||||||
|
return Promise.resolve({})
|
||||||
}
|
}
|
||||||
|
|
||||||
refundPayment(paymentSessionData: Record<string, unknown>): Promise<PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]> {
|
refundPayment(
|
||||||
return Promise.resolve({});
|
paymentSessionData: Record<string, unknown>
|
||||||
|
): Promise<
|
||||||
|
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||||
|
> {
|
||||||
|
return Promise.resolve({})
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelPayment(paymentSessionData: Record<string, unknown>): Promise<PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]> {
|
cancelPayment(
|
||||||
return Promise.resolve({});
|
paymentSessionData: Record<string, unknown>
|
||||||
|
): Promise<
|
||||||
|
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||||
|
> {
|
||||||
|
return Promise.resolve({})
|
||||||
}
|
}
|
||||||
|
|
||||||
deletePayment(paymentSessionData: Record<string, unknown>): Promise<PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]> {
|
deletePayment(
|
||||||
return Promise.resolve({});
|
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({})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { asValue, createContainer } from "awilix"
|
import { asValue, createContainer } from "awilix"
|
||||||
import { MockRepository } from "medusa-test-utils"
|
import { MockRepository } from "medusa-test-utils"
|
||||||
import PaymentProviderService from "../payment-provider"
|
|
||||||
import {
|
import {
|
||||||
defaultContainer,
|
defaultContainer,
|
||||||
defaultPaymentSessionInputData,
|
defaultPaymentSessionInputData,
|
||||||
@@ -8,7 +7,7 @@ import {
|
|||||||
} from "../__fixtures__/payment-provider"
|
} from "../__fixtures__/payment-provider"
|
||||||
import { testPayServiceMock } from "../__mocks__/test-pay"
|
import { testPayServiceMock } from "../__mocks__/test-pay"
|
||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
import { PaymentSessionStatus, RefundReason } from "../../models";
|
import { PaymentSessionStatus, RefundReason } from "../../models"
|
||||||
|
|
||||||
describe(`PaymentProviderService`, () => {
|
describe(`PaymentProviderService`, () => {
|
||||||
const container = createContainer({}, defaultContainer)
|
const container = createContainer({}, defaultContainer)
|
||||||
@@ -144,8 +143,8 @@ describe(`PaymentProviderService`, () => {
|
|||||||
amount: 100,
|
amount: 100,
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
cart: {
|
cart: {
|
||||||
id: "cart-test"
|
id: "cart-test",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -564,17 +563,21 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
provider_id: defaultPaymentSessionInputData.provider_id,
|
provider_id: defaultPaymentSessionInputData.provider_id,
|
||||||
payment_session: {
|
payment_session: {
|
||||||
id: sessionId,
|
id: sessionId,
|
||||||
data: { id: externalId }
|
data: { id: externalId },
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = createContainer({}, defaultContainer)
|
const container = createContainer({}, defaultContainer)
|
||||||
|
|
||||||
const mockPaymentProcessor = new PaymentProcessor(container)
|
const mockPaymentProcessor = new PaymentProcessor(container)
|
||||||
mockPaymentProcessor.retrievePayment = jest.fn().mockReturnValue(Promise.resolve({}))
|
mockPaymentProcessor.retrievePayment = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(Promise.resolve({}))
|
||||||
|
|
||||||
container
|
container.register(
|
||||||
.register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor))
|
paymentProcessorResolutionKey,
|
||||||
|
asValue(mockPaymentProcessor)
|
||||||
|
)
|
||||||
|
|
||||||
const providerService = container.resolve(paymentServiceResolutionKey)
|
const providerService = container.resolve(paymentServiceResolutionKey)
|
||||||
|
|
||||||
@@ -584,7 +587,9 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
const provider = container.resolve(paymentProcessorResolutionKey)
|
const provider = container.resolve(paymentProcessorResolutionKey)
|
||||||
|
|
||||||
expect(provider.retrievePayment).toBeCalledTimes(1)
|
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 () => {
|
it("throw an error using the provider error response", async () => {
|
||||||
@@ -621,14 +626,16 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
const paymentSession = {
|
const paymentSession = {
|
||||||
id: "test-session",
|
id: "test-session",
|
||||||
data: { id: externalId },
|
data: { id: externalId },
|
||||||
provider_id: paymentProviderId
|
provider_id: paymentProviderId,
|
||||||
}
|
}
|
||||||
const context = { ip: "0.0.0.0" }
|
const context = { ip: "0.0.0.0" }
|
||||||
|
|
||||||
const container = createContainer({}, defaultContainer)
|
const container = createContainer({}, defaultContainer)
|
||||||
|
|
||||||
const mockPaymentProcessor = new PaymentProcessor(container)
|
const mockPaymentProcessor = new PaymentProcessor(container)
|
||||||
mockPaymentProcessor.authorizePayment = jest.fn().mockReturnValue(Promise.resolve({}))
|
mockPaymentProcessor.authorizePayment = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(Promise.resolve({}))
|
||||||
|
|
||||||
container
|
container
|
||||||
.register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor))
|
.register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor))
|
||||||
@@ -636,9 +643,7 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
"paymentSessionRepository",
|
"paymentSessionRepository",
|
||||||
asValue(
|
asValue(
|
||||||
MockRepository({
|
MockRepository({
|
||||||
findOne: jest
|
findOne: jest.fn().mockImplementation(async () => ({ data: {} })),
|
||||||
.fn()
|
|
||||||
.mockImplementation(async () => ({ data: {} })),
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -651,7 +656,10 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
const provider = container.resolve(paymentProcessorResolutionKey)
|
const provider = container.resolve(paymentProcessorResolutionKey)
|
||||||
|
|
||||||
expect(provider.authorizePayment).toBeCalledTimes(1)
|
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 () => {
|
it("throw an error using the provider error response", async () => {
|
||||||
@@ -688,13 +696,15 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
const payment = {
|
const payment = {
|
||||||
id: "payment-id",
|
id: "payment-id",
|
||||||
data: { id: externalId },
|
data: { id: externalId },
|
||||||
provider_id: paymentProviderId
|
provider_id: paymentProviderId,
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = createContainer({}, defaultContainer)
|
const container = createContainer({}, defaultContainer)
|
||||||
|
|
||||||
const mockPaymentProcessor = new PaymentProcessor(container)
|
const mockPaymentProcessor = new PaymentProcessor(container)
|
||||||
mockPaymentProcessor.cancelPayment = jest.fn().mockReturnValue(Promise.resolve())
|
mockPaymentProcessor.cancelPayment = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(Promise.resolve())
|
||||||
|
|
||||||
container
|
container
|
||||||
.register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor))
|
.register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor))
|
||||||
@@ -702,9 +712,7 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
"paymentRepository",
|
"paymentRepository",
|
||||||
asValue(
|
asValue(
|
||||||
MockRepository({
|
MockRepository({
|
||||||
findOne: jest
|
findOne: jest.fn().mockImplementation(async () => payment),
|
||||||
.fn()
|
|
||||||
.mockImplementation(async () => payment),
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -739,9 +747,7 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
|
|
||||||
const providerService = container.resolve(paymentServiceResolutionKey)
|
const providerService = container.resolve(paymentServiceResolutionKey)
|
||||||
|
|
||||||
const err = await providerService
|
const err = await providerService.cancelPayment(payment).catch((e) => e)
|
||||||
.cancelPayment(payment)
|
|
||||||
.catch((e) => e)
|
|
||||||
|
|
||||||
expect(err.message).toBe(
|
expect(err.message).toBe(
|
||||||
`${errResponse.error}:${EOL}${errResponse.detail}`
|
`${errResponse.error}:${EOL}${errResponse.detail}`
|
||||||
@@ -752,7 +758,7 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
describe("getStatus", () => {
|
describe("getStatus", () => {
|
||||||
const payment = {
|
const payment = {
|
||||||
data: { id: "id" },
|
data: { id: "id" },
|
||||||
provider_id: paymentProviderId
|
provider_id: paymentProviderId,
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = createContainer({}, defaultContainer)
|
const container = createContainer({}, defaultContainer)
|
||||||
@@ -768,9 +774,7 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
"paymentRepository",
|
"paymentRepository",
|
||||||
asValue(
|
asValue(
|
||||||
MockRepository({
|
MockRepository({
|
||||||
findOne: jest
|
findOne: jest.fn().mockImplementation(async () => payment),
|
||||||
.fn()
|
|
||||||
.mockImplementation(async () => payment),
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -792,13 +796,15 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
const payment = {
|
const payment = {
|
||||||
data: { id: externalId },
|
data: { id: externalId },
|
||||||
id: "payment-id",
|
id: "payment-id",
|
||||||
provider_id: paymentProviderId
|
provider_id: paymentProviderId,
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = createContainer({}, defaultContainer)
|
const container = createContainer({}, defaultContainer)
|
||||||
|
|
||||||
const mockPaymentProcessor = new PaymentProcessor(container)
|
const mockPaymentProcessor = new PaymentProcessor(container)
|
||||||
mockPaymentProcessor.capturePayment = jest.fn().mockReturnValue(Promise.resolve(payment.data))
|
mockPaymentProcessor.capturePayment = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(Promise.resolve(payment.data))
|
||||||
|
|
||||||
container
|
container
|
||||||
.register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor))
|
.register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor))
|
||||||
@@ -806,9 +812,7 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
"paymentRepository",
|
"paymentRepository",
|
||||||
asValue(
|
asValue(
|
||||||
MockRepository({
|
MockRepository({
|
||||||
findOne: jest
|
findOne: jest.fn().mockImplementation(async () => payment),
|
||||||
.fn()
|
|
||||||
.mockImplementation(async () => payment),
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -843,9 +847,7 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
|
|
||||||
const providerService = container.resolve(paymentServiceResolutionKey)
|
const providerService = container.resolve(paymentServiceResolutionKey)
|
||||||
|
|
||||||
const err = await providerService
|
const err = await providerService.capturePayment(payment).catch((e) => e)
|
||||||
.capturePayment(payment)
|
|
||||||
.catch((e) => e)
|
|
||||||
|
|
||||||
expect(err.message).toBe(
|
expect(err.message).toBe(
|
||||||
`${errResponse.error}:${EOL}${errResponse.detail}`
|
`${errResponse.error}:${EOL}${errResponse.detail}`
|
||||||
@@ -858,33 +860,39 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
const payments = [{
|
const payments = [
|
||||||
|
{
|
||||||
id: "p1",
|
id: "p1",
|
||||||
captured_at: new Date(),
|
captured_at: new Date(),
|
||||||
data: { id: "id1" },
|
data: { id: "id1" },
|
||||||
amount: 1000,
|
amount: 1000,
|
||||||
amount_refunded: 0,
|
amount_refunded: 0,
|
||||||
provider_id: paymentProviderId,
|
provider_id: paymentProviderId,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
id: "p2",
|
id: "p2",
|
||||||
captured_at: new Date(),
|
captured_at: new Date(),
|
||||||
data: { id: "id2" },
|
data: { id: "id2" },
|
||||||
amount: 1000,
|
amount: 1000,
|
||||||
amount_refunded: 0,
|
amount_refunded: 0,
|
||||||
provider_id: paymentProviderId,
|
provider_id: paymentProviderId,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
id: "p3",
|
id: "p3",
|
||||||
captured_at: new Date(),
|
captured_at: new Date(),
|
||||||
data: { id: "id3" },
|
data: { id: "id3" },
|
||||||
amount: 1000,
|
amount: 1000,
|
||||||
amount_refunded: 1000, // already fully refunded
|
amount_refunded: 1000, // already fully refunded
|
||||||
provider_id: paymentProviderId,
|
provider_id: paymentProviderId,
|
||||||
}]
|
},
|
||||||
|
]
|
||||||
|
|
||||||
const container = createContainer({}, defaultContainer)
|
const container = createContainer({}, defaultContainer)
|
||||||
|
|
||||||
const mockPaymentProcessor = new PaymentProcessor(container)
|
const mockPaymentProcessor = new PaymentProcessor(container)
|
||||||
mockPaymentProcessor.refundPayment = jest.fn().mockImplementation(async (data) => data)
|
mockPaymentProcessor.refundPayment = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(async (data) => data)
|
||||||
|
|
||||||
container
|
container
|
||||||
.register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor))
|
.register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor))
|
||||||
@@ -892,9 +900,7 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
"paymentRepository",
|
"paymentRepository",
|
||||||
asValue(
|
asValue(
|
||||||
MockRepository({
|
MockRepository({
|
||||||
find: jest
|
find: jest.fn().mockImplementation(async () => payments),
|
||||||
.fn()
|
|
||||||
.mockImplementation(async () => payments),
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -903,17 +909,36 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
const paymentRepo = container.resolve("paymentRepository")
|
const paymentRepo = container.resolve("paymentRepository")
|
||||||
|
|
||||||
it("successfully refund the payments", async () => {
|
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)
|
const provider = container.resolve(paymentProcessorResolutionKey)
|
||||||
|
|
||||||
expect(provider.refundPayment).toBeCalledTimes(2)
|
expect(provider.refundPayment).toBeCalledTimes(2)
|
||||||
expect(provider.refundPayment).toHaveBeenNthCalledWith(1, payments[0].data, 1000)
|
expect(provider.refundPayment).toHaveBeenNthCalledWith(
|
||||||
expect(provider.refundPayment).toHaveBeenNthCalledWith(2, payments[1].data, 500)
|
1,
|
||||||
|
payments[0].data,
|
||||||
|
1000
|
||||||
|
)
|
||||||
|
expect(provider.refundPayment).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
payments[1].data,
|
||||||
|
500
|
||||||
|
)
|
||||||
|
|
||||||
expect(paymentRepo.save).toBeCalledTimes(2)
|
expect(paymentRepo.save).toBeCalledTimes(2)
|
||||||
expect(paymentRepo.save).toHaveBeenNthCalledWith(1, expect.objectContaining({ amount_refunded: 1000 }))
|
expect(paymentRepo.save).toHaveBeenNthCalledWith(
|
||||||
expect(paymentRepo.save).toHaveBeenNthCalledWith(2, expect.objectContaining({ amount_refunded: 500 }))
|
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 () => {
|
it("throw an error using the provider error response", async () => {
|
||||||
@@ -935,9 +960,7 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
|
|
||||||
const providerService = container.resolve(paymentServiceResolutionKey)
|
const providerService = container.resolve(paymentServiceResolutionKey)
|
||||||
|
|
||||||
const err = await providerService
|
const err = await providerService.refundPayment(payments).catch((e) => e)
|
||||||
.refundPayment(payments)
|
|
||||||
.catch((e) => e)
|
|
||||||
|
|
||||||
expect(err.message).toBe(
|
expect(err.message).toBe(
|
||||||
`${errResponse.error}:${EOL}${errResponse.detail}`
|
`${errResponse.error}:${EOL}${errResponse.detail}`
|
||||||
@@ -958,16 +981,25 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
const container = createContainer({}, defaultContainer)
|
const container = createContainer({}, defaultContainer)
|
||||||
|
|
||||||
const mockPaymentProcessor = new PaymentProcessor(container)
|
const mockPaymentProcessor = new PaymentProcessor(container)
|
||||||
mockPaymentProcessor.refundPayment = jest.fn().mockImplementation(async (data) => data)
|
mockPaymentProcessor.refundPayment = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(async (data) => data)
|
||||||
|
|
||||||
container
|
container.register(
|
||||||
.register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor))
|
paymentProcessorResolutionKey,
|
||||||
|
asValue(mockPaymentProcessor)
|
||||||
|
)
|
||||||
|
|
||||||
const providerService = container.resolve(paymentServiceResolutionKey)
|
const providerService = container.resolve(paymentServiceResolutionKey)
|
||||||
const paymentRepo = container.resolve("paymentRepository")
|
const paymentRepo = container.resolve("paymentRepository")
|
||||||
|
|
||||||
it("successfully refund the payments", async () => {
|
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)
|
const provider = container.resolve(paymentProcessorResolutionKey)
|
||||||
|
|
||||||
@@ -975,7 +1007,9 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
|
|||||||
expect(provider.refundPayment).toBeCalledWith(payment.data, 500)
|
expect(provider.refundPayment).toBeCalledWith(payment.data, 500)
|
||||||
|
|
||||||
expect(paymentRepo.save).toBeCalledTimes(1)
|
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 () => {
|
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)
|
const provider = this.retrieveProvider(paymentSession.provider_id)
|
||||||
|
|
||||||
|
let updatedData
|
||||||
if (provider instanceof AbstractPaymentProcessor) {
|
if (provider instanceof AbstractPaymentProcessor) {
|
||||||
throw new MedusaError(
|
const res = await provider.updatePaymentData(paymentSession.id, data)
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
if ("error" in res) {
|
||||||
`The payment provider ${paymentSession.provider_id} is of type PaymentProcessor. PaymentProcessors cannot update payment session data.`
|
this.throwFromPaymentProcessorError(res as PaymentProcessorError)
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
session.data = await provider
|
updatedData = res
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updatedData = await provider
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.updatePaymentData(paymentSession.data, data)
|
.updatePaymentData(paymentSession.data, data)
|
||||||
session.status = paymentSession.status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object.assign(session.data, updatedData)
|
||||||
|
session.status = paymentSession.status
|
||||||
|
|
||||||
const sessionRepo = transactionManager.withRepository(
|
const sessionRepo = transactionManager.withRepository(
|
||||||
this.paymentSessionRepository_
|
this.paymentSessionRepository_
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user