Skip to content

Commit

Permalink
continue to add support to payment provider
Browse files Browse the repository at this point in the history
  • Loading branch information
adrien2p committed Jan 11, 2023
1 parent b98aa58 commit 9bf1410
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 17 deletions.
14 changes: 9 additions & 5 deletions packages/medusa/src/interfaces/payment-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,13 @@ export interface PaymentProcessor {

/**
* Capture an existing session
* @param context
* @param paymentSessionData
*/
capturePayment(
context: PaymentProcessorContext
): Promise<PaymentProcessorError | void>
paymentSessionData: Record<string, unknown>
): Promise<
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
>

/**
* Delete an existing session
Expand Down Expand Up @@ -140,8 +142,10 @@ export abstract class AbstractPaymentProcessor implements PaymentProcessor {
abstract init(): Promise<void>

abstract capturePayment(
context: PaymentProcessorContext
): Promise<PaymentProcessorError | void>
paymentSessionData: Record<string, unknown>
): Promise<
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
>

abstract authorizePayment(
paymentSessionData: Record<string, unknown>,
Expand Down
171 changes: 171 additions & 0 deletions packages/medusa/src/services/__tests__/payment-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PaymentProviderService from "../payment-provider"
import { defaultContainer, defaultPaymentSessionInputData, PaymentProcessor, } from "../__fixtures__/payment-provider"
import { testPayServiceMock } from "../__mocks__/test-pay"
import { EOL } from "os"
import { PaymentSessionStatus } from "../../models";

describe("PaymentProviderService", () => {
describe("retrieveProvider", () => {
Expand Down Expand Up @@ -810,4 +811,174 @@ describe("PaymentProviderService using AbstractPaymentProcessor", () => {
)
})
})

describe("cancelPayment", () => {
const externalId = "external-id"
const payment = {
id: "payment-id",
data: { id: externalId },
provider_id: paymentProviderId
}

const container = createContainer({}, defaultContainer)

const mockPaymentProcessor = new PaymentProcessor(container)
mockPaymentProcessor.cancelPayment = jest.fn().mockReturnValue(Promise.resolve())

container
.register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor))
.register(
"paymentRepository",
asValue(
MockRepository({
findOne: jest
.fn()
.mockImplementation(async () => payment),
})
)
)

const providerService = container.resolve(paymentServiceResolutionKey)

it("successfully cancel a payment", async () => {
await providerService.cancelPayment(payment)

const provider = container.resolve(paymentProcessorResolutionKey)

expect(provider.cancelPayment).toBeCalledTimes(1)
expect(provider.cancelPayment).toBeCalledWith(payment.data)
})

it("throw an error using the provider error response", async () => {
const errResponse = {
error: "Error",
code: 400,
details: "Error details",
}

const mockPaymentProcessor = new PaymentProcessor(container)
mockPaymentProcessor.cancelPayment = jest
.fn()
.mockReturnValue(Promise.resolve(errResponse))

container.register(
paymentProcessorResolutionKey,
asValue(mockPaymentProcessor)
)

const providerService = container.resolve(paymentServiceResolutionKey)

const err = await providerService
.cancelPayment(payment)
.catch((e) => e)

expect(err.message).toBe(
`${errResponse.error}:${EOL}${errResponse.details}`
)
})
})

describe("getStatus", () => {
const payment = {
data: { id: "id" },
provider_id: paymentProviderId
}

const container = createContainer({}, defaultContainer)

const mockPaymentProcessor = new PaymentProcessor(container)
mockPaymentProcessor.getPaymentStatus = jest
.fn()
.mockReturnValue(Promise.resolve(PaymentSessionStatus.PENDING))

container
.register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor))
.register(
"paymentRepository",
asValue(
MockRepository({
findOne: jest
.fn()
.mockImplementation(async () => payment),
})
)
)

const providerService = container.resolve(paymentServiceResolutionKey)

it("successfully cancel a payment", async () => {
await providerService.getStatus(payment)

const provider = container.resolve(paymentProcessorResolutionKey)

expect(provider.getPaymentStatus).toBeCalledTimes(1)
expect(provider.getPaymentStatus).toBeCalledWith(payment.data)
})
})

describe("capturePayment", () => {
const externalId = "external-id"
const payment = {
data: { id: externalId },
id: "payment-id",
provider_id: paymentProviderId
}

const container = createContainer({}, defaultContainer)

const mockPaymentProcessor = new PaymentProcessor(container)
mockPaymentProcessor.capturePayment = jest.fn().mockReturnValue(Promise.resolve(payment.data))

container
.register(paymentProcessorResolutionKey, asValue(mockPaymentProcessor))
.register(
"paymentRepository",
asValue(
MockRepository({
findOne: jest
.fn()
.mockImplementation(async () => payment),
})
)
)

const providerService = container.resolve(paymentServiceResolutionKey)

it("successfully capture a payment", async () => {
await providerService.capturePayment(payment)

const provider = container.resolve(paymentProcessorResolutionKey)

expect(provider.capturePayment).toBeCalledTimes(1)
expect(provider.capturePayment).toBeCalledWith(payment.data)
})

it("throw an error using the provider error response", async () => {
const errResponse = {
error: "Error",
code: 400,
details: "Error details",
}

const mockPaymentProcessor = new PaymentProcessor(container)
mockPaymentProcessor.capturePayment = jest
.fn()
.mockReturnValue(Promise.resolve(errResponse))

container.register(
paymentProcessorResolutionKey,
asValue(mockPaymentProcessor)
)

const providerService = container.resolve(paymentServiceResolutionKey)

const err = await providerService
.capturePayment(payment)
.catch((e) => e)

expect(err.message).toBe(
`${errResponse.error}:${EOL}${errResponse.details}`
)
})
})
})
48 changes: 36 additions & 12 deletions packages/medusa/src/services/payment-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,9 @@ export default class PaymentProviderService extends TransactionBaseService {
>(paymentSession.provider_id)

if (provider instanceof AbstractPaymentProcessor) {
const res = await provider.deletePayment(session.data)
if (res) {
this.throwFromPaymentProcessorError(res)
const error = await provider.deletePayment(session.data)
if (error) {
this.throwFromPaymentProcessorError(error)
}
} else {
await provider
Expand Down Expand Up @@ -415,9 +415,9 @@ export default class PaymentProviderService extends TransactionBaseService {
>(paymentSession.provider_id)

if (provider instanceof AbstractPaymentProcessor) {
const res = await provider.deletePayment(paymentSession.data)
if (res) {
this.throwFromPaymentProcessorError(res)
const error = await provider.deletePayment(paymentSession.data)
if (error) {
this.throwFromPaymentProcessorError(error)
}
} else {
await provider
Expand Down Expand Up @@ -577,6 +577,7 @@ export default class PaymentProviderService extends TransactionBaseService {

const provider = this.retrieveProvider(paymentSession.provider_id)

// TODO: Waiting discussion output before taking care of the processor support
session.data = await provider
.withTransaction(transactionManager)
.updatePaymentData(paymentSession.data, data)
Expand All @@ -595,9 +596,17 @@ export default class PaymentProviderService extends TransactionBaseService {
return await this.atomicPhase_(async (transactionManager) => {
const payment = await this.retrievePayment(paymentObj.id)
const provider = this.retrieveProvider(payment.provider_id)
payment.data = await provider
.withTransaction(transactionManager)
.cancelPayment(payment)

if (provider instanceof AbstractPaymentProcessor) {
const error = await provider.cancelPayment(payment.data)
if (error) {
this.throwFromPaymentProcessorError(error)
}
} else {
payment.data = await provider
.withTransaction(transactionManager)
.cancelPayment(payment)
}

const now = new Date()
payment.canceled_at = now.toISOString()
Expand All @@ -611,6 +620,11 @@ export default class PaymentProviderService extends TransactionBaseService {

async getStatus(payment: Payment): Promise<PaymentSessionStatus> {
const provider = this.retrieveProvider(payment.provider_id)

if (provider instanceof AbstractPaymentProcessor) {
return await provider.getPaymentStatus(payment.data)
}

return await provider.withTransaction(this.manager_).getStatus(payment.data)
}

Expand All @@ -620,9 +634,19 @@ export default class PaymentProviderService extends TransactionBaseService {
return await this.atomicPhase_(async (transactionManager) => {
const payment = await this.retrievePayment(paymentObj.id)
const provider = this.retrieveProvider(payment.provider_id)
payment.data = await provider
.withTransaction(transactionManager)
.capturePayment(payment)

if (provider instanceof AbstractPaymentProcessor) {
const res = await provider.capturePayment(payment.data)
if ("error" in res) {
this.throwFromPaymentProcessorError(res as PaymentProcessorError)
} else {
payment.data = res
}
} else {
payment.data = await provider
.withTransaction(transactionManager)
.capturePayment(payment)
}

const now = new Date()
payment.captured_at = now.toISOString()
Expand Down

0 comments on commit 9bf1410

Please sign in to comment.