feat(medusa): Load PaymentProcessors + integrate in PaymentProviderService (#2978)
* feat: Add payment process support into the loader and payment provider * WIP * feat: continue payment provider alignment * fix tests and defer payment service resolution * continue to add support to payment provider * continue to add support to payment provider * fix fixtures * chore: add updateSessionData unsupported error * chore: Adress feedback * chore: Adress feedback * chore: fix default loader * cleanup * cleanup * fix unit tests * Create purple-sloths-confess.md * address feedback * minor changes * fix unit test --------- Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
1 parent
1c40346e9e
commit
f43e9f0f20
13 files changed
+1653
-472
No files matched your search
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(medusa): Load PaymentProcessors
|
||||||
|
- Add loading of PaymentProcessors
|
||||||
|
- Add PaymentProcessor support in the payment-provider
|
||||||
|
- Add backward compatibility for the PaymentService
|
||||||
@@ -10,4 +10,5 @@ export * from "./models/base-entity"
|
|||||||
export * from "./models/soft-deletable-entity"
|
export * from "./models/soft-deletable-entity"
|
||||||
export * from "./search-service"
|
export * from "./search-service"
|
||||||
export * from "./payment-service"
|
export * from "./payment-service"
|
||||||
|
export * from "./payment-processor"
|
||||||
export * from "./services"
|
export * from "./services"
|
||||||
@@ -6,21 +6,21 @@ export type PaymentProcessorContext = {
|
|||||||
email: string
|
email: string
|
||||||
currency_code: string
|
currency_code: string
|
||||||
amount: number
|
amount: number
|
||||||
resource_id?: string
|
resource_id: string
|
||||||
customer?: Customer
|
customer?: Customer
|
||||||
context: Record<string, unknown>
|
context: Record<string, unknown>
|
||||||
paymentSessionData: Record<string, unknown>
|
paymentSessionData: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PaymentProcessorSessionResponse = {
|
export type PaymentProcessorSessionResponse = {
|
||||||
update_requests: { customer_metadata: Record<string, unknown> }
|
update_requests?: { customer_metadata?: Record<string, unknown> }
|
||||||
session_data: Record<string, unknown>
|
session_data: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaymentProcessorError {
|
export interface PaymentProcessorError {
|
||||||
error: string
|
error: string
|
||||||
code: number
|
code?: string
|
||||||
details: any
|
detail?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,42 +51,60 @@ export interface PaymentProcessor {
|
|||||||
*/
|
*/
|
||||||
updatePayment(
|
updatePayment(
|
||||||
context: PaymentProcessorContext
|
context: PaymentProcessorContext
|
||||||
): Promise<PaymentProcessorError | void>
|
): Promise<PaymentProcessorError | PaymentProcessorSessionResponse | void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refund an existing session
|
* Refund an existing session
|
||||||
* @param context
|
* @param paymentSessionData
|
||||||
|
* @param refundAmount
|
||||||
*/
|
*/
|
||||||
refundPayment(
|
refundPayment(
|
||||||
context: PaymentProcessorContext
|
paymentSessionData: Record<string, unknown>,
|
||||||
): Promise<PaymentProcessorError | void>
|
refundAmount: number
|
||||||
|
): Promise<
|
||||||
|
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||||
|
>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authorize an existing session if it is not already authorized
|
* Authorize an existing session if it is not already authorized
|
||||||
|
* @param paymentSessionData
|
||||||
* @param context
|
* @param context
|
||||||
*/
|
*/
|
||||||
authorizePayment(
|
authorizePayment(
|
||||||
context: PaymentProcessorContext
|
paymentSessionData: Record<string, unknown>,
|
||||||
): Promise<PaymentProcessorError | void>
|
context: Record<string, unknown>
|
||||||
|
): Promise<
|
||||||
|
| PaymentProcessorError
|
||||||
|
| {
|
||||||
|
status: PaymentSessionStatus
|
||||||
|
data: PaymentProcessorSessionResponse["session_data"]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Capture an existing session
|
* Capture an existing session
|
||||||
* @param context
|
* @param paymentSessionData
|
||||||
*/
|
*/
|
||||||
capturePayment(
|
capturePayment(
|
||||||
context: PaymentProcessorContext
|
paymentSessionData: Record<string, unknown>
|
||||||
): Promise<PaymentProcessorError | void>
|
): Promise<
|
||||||
|
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||||
|
>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete an existing session
|
* Delete an existing session
|
||||||
*/
|
*/
|
||||||
deletePayment(paymentId: string): Promise<PaymentProcessorError | void>
|
deletePayment(
|
||||||
|
paymentSessionData: Record<string, unknown>
|
||||||
|
): Promise<
|
||||||
|
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||||
|
>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve an existing session
|
* Retrieve an existing session
|
||||||
*/
|
*/
|
||||||
retrievePayment(
|
retrievePayment(
|
||||||
paymentId: string
|
paymentSessionData: Record<string, unknown>
|
||||||
): Promise<
|
): Promise<
|
||||||
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||||
>
|
>
|
||||||
@@ -94,12 +112,18 @@ export interface PaymentProcessor {
|
|||||||
/**
|
/**
|
||||||
* Cancel an existing session
|
* Cancel an existing session
|
||||||
*/
|
*/
|
||||||
cancelPayment(paymentId: string): Promise<PaymentProcessorError | void>
|
cancelPayment(
|
||||||
|
paymentSessionData: Record<string, unknown>
|
||||||
|
): Promise<
|
||||||
|
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||||
|
>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the status of the session
|
* Return the status of the session
|
||||||
*/
|
*/
|
||||||
getPaymentStatus(paymentId: string): Promise<PaymentSessionStatus>
|
getPaymentStatus(
|
||||||
|
paymentSessionData: Record<string, unknown>
|
||||||
|
): Promise<PaymentSessionStatus>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -111,7 +135,7 @@ export abstract class AbstractPaymentProcessor implements PaymentProcessor {
|
|||||||
protected readonly config?: Record<string, unknown> // eslint-disable-next-line @typescript-eslint/no-empty-function
|
protected readonly config?: Record<string, unknown> // eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
protected static identifier: string
|
public static identifier: string
|
||||||
|
|
||||||
public getIdentifier(): string {
|
public getIdentifier(): string {
|
||||||
const ctr = this.constructor as typeof AbstractPaymentProcessor
|
const ctr = this.constructor as typeof AbstractPaymentProcessor
|
||||||
@@ -126,40 +150,58 @@ export abstract class AbstractPaymentProcessor implements PaymentProcessor {
|
|||||||
abstract init(): Promise<void>
|
abstract init(): Promise<void>
|
||||||
|
|
||||||
abstract capturePayment(
|
abstract capturePayment(
|
||||||
context: PaymentProcessorContext
|
paymentSessionData: Record<string, unknown>
|
||||||
): Promise<PaymentProcessorError | void>
|
): Promise<
|
||||||
|
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||||
|
>
|
||||||
|
|
||||||
abstract authorizePayment(
|
abstract authorizePayment(
|
||||||
context: PaymentProcessorContext
|
paymentSessionData: Record<string, unknown>,
|
||||||
): Promise<PaymentProcessorError | void>
|
context: Record<string, unknown>
|
||||||
|
): Promise<
|
||||||
|
| PaymentProcessorError
|
||||||
|
| {
|
||||||
|
status: PaymentSessionStatus
|
||||||
|
data: PaymentProcessorSessionResponse["session_data"]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
abstract cancelPayment(
|
abstract cancelPayment(
|
||||||
paymentId: string
|
paymentSessionData: Record<string, unknown>
|
||||||
): Promise<PaymentProcessorError | void>
|
): Promise<
|
||||||
|
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||||
|
>
|
||||||
|
|
||||||
abstract initiatePayment(
|
abstract initiatePayment(
|
||||||
context: PaymentProcessorContext
|
context: PaymentProcessorContext
|
||||||
): Promise<PaymentProcessorError | PaymentProcessorSessionResponse>
|
): Promise<PaymentProcessorError | PaymentProcessorSessionResponse>
|
||||||
|
|
||||||
abstract deletePayment(
|
abstract deletePayment(
|
||||||
paymentId: string
|
paymentSessionData: Record<string, unknown>
|
||||||
): Promise<PaymentProcessorError | void>
|
): Promise<
|
||||||
|
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||||
|
>
|
||||||
|
|
||||||
abstract getPaymentStatus(paymentId: string): Promise<PaymentSessionStatus>
|
abstract getPaymentStatus(
|
||||||
|
paymentSessionData: Record<string, unknown>
|
||||||
|
): Promise<PaymentSessionStatus>
|
||||||
|
|
||||||
abstract refundPayment(
|
abstract refundPayment(
|
||||||
context: PaymentProcessorContext
|
paymentSessionData: Record<string, unknown>,
|
||||||
): Promise<PaymentProcessorError | void>
|
refundAmount: number
|
||||||
|
): Promise<
|
||||||
|
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||||
|
>
|
||||||
|
|
||||||
abstract retrievePayment(
|
abstract retrievePayment(
|
||||||
paymentId: string
|
paymentSessionData: Record<string, unknown>
|
||||||
): Promise<
|
): Promise<
|
||||||
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]
|
||||||
>
|
>
|
||||||
|
|
||||||
abstract updatePayment(
|
abstract updatePayment(
|
||||||
context: PaymentProcessorContext
|
context: PaymentProcessorContext
|
||||||
): Promise<PaymentProcessorError | void>
|
): Promise<PaymentProcessorError | PaymentProcessorSessionResponse | void>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -169,3 +211,13 @@ export abstract class AbstractPaymentProcessor implements PaymentProcessor {
|
|||||||
export function isPaymentProcessor(obj: unknown): boolean {
|
export function isPaymentProcessor(obj: unknown): boolean {
|
||||||
return obj instanceof AbstractPaymentProcessor
|
return obj instanceof AbstractPaymentProcessor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to determine if an object is a processor error
|
||||||
|
* @param obj
|
||||||
|
*/
|
||||||
|
export function isPaymentProcessorError(
|
||||||
|
obj: any
|
||||||
|
): obj is PaymentProcessorError {
|
||||||
|
return obj && typeof obj === "object" && (obj.error || obj.code || obj.detail)
|
||||||
|
}
|
||||||
@@ -21,11 +21,13 @@ export type PaymentContext = {
|
|||||||
email: string
|
email: string
|
||||||
shipping_address: Address | null
|
shipping_address: Address | null
|
||||||
shipping_methods: ShippingMethod[]
|
shipping_methods: ShippingMethod[]
|
||||||
|
billing_address?: Address | null
|
||||||
}
|
}
|
||||||
currency_code: string
|
currency_code: string
|
||||||
amount: number
|
amount: number
|
||||||
resource_id?: string
|
resource_id: string
|
||||||
customer?: Customer
|
customer?: Customer
|
||||||
|
paymentSessionData: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PaymentSessionResponse = {
|
export type PaymentSessionResponse = {
|
||||||
@@ -148,7 +150,7 @@ export abstract class AbstractPaymentService
|
|||||||
super(container, config)
|
super(container, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static identifier: string
|
public static identifier: string
|
||||||
|
|
||||||
public getIdentifier(): string {
|
public getIdentifier(): string {
|
||||||
if (!(this.constructor as typeof AbstractPaymentService).identifier) {
|
if (!(this.constructor as typeof AbstractPaymentService).identifier) {
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { asValue, createContainer } from "awilix"
|
import { asValue, createContainer } from "awilix"
|
||||||
import { MockManager, MockRepository } from "medusa-test-utils"
|
import { MockManager, MockRepository } from "medusa-test-utils"
|
||||||
import { StoreServiceMock } from "../../services/__mocks__/store"
|
import { StoreServiceMock } from "../../services/__mocks__/store"
|
||||||
import { ShippingProfileServiceMock } from "../../services/__mocks__/shipping-profile"
|
import {
|
||||||
|
ShippingProfileServiceMock
|
||||||
|
} from "../../services/__mocks__/shipping-profile"
|
||||||
import Logger from "../logger"
|
import Logger from "../logger"
|
||||||
import featureFlagsLoader from "../feature-flags"
|
import featureFlagsLoader from "../feature-flags"
|
||||||
import { default as defaultLoader } from "../defaults"
|
import { default as defaultLoader } from "../defaults"
|
||||||
import { SalesChannelServiceMock } from "../../services/__mocks__/sales-channel"
|
import { SalesChannelServiceMock } from "../../services/__mocks__/sales-channel"
|
||||||
import { PaymentProviderServiceMock } from "../../services/__mocks__/payment-provider"
|
import {
|
||||||
|
PaymentProviderServiceMock
|
||||||
|
} from "../../services/__mocks__/payment-provider"
|
||||||
|
|
||||||
describe("default", () => {
|
describe("default", () => {
|
||||||
describe("sales channel default", () => {
|
describe("sales channel default", () => {
|
||||||
@@ -40,14 +44,23 @@ describe("default", () => {
|
|||||||
paymentProviderService: asValue(PaymentProviderServiceMock),
|
paymentProviderService: asValue(PaymentProviderServiceMock),
|
||||||
notificationProviders: asValue([]),
|
notificationProviders: asValue([]),
|
||||||
notificationService: asValue({
|
notificationService: asValue({
|
||||||
|
withTransaction: function () {
|
||||||
|
return this
|
||||||
|
},
|
||||||
registerInstalledProviders: jest.fn(),
|
registerInstalledProviders: jest.fn(),
|
||||||
}),
|
}),
|
||||||
fulfillmentProviders: asValue([]),
|
fulfillmentProviders: asValue([]),
|
||||||
fulfillmentProviderService: asValue({
|
fulfillmentProviderService: asValue({
|
||||||
|
withTransaction: function () {
|
||||||
|
return this
|
||||||
|
},
|
||||||
registerInstalledProviders: jest.fn(),
|
registerInstalledProviders: jest.fn(),
|
||||||
}),
|
}),
|
||||||
taxProviders: asValue([]),
|
taxProviders: asValue([]),
|
||||||
taxProviderService: asValue({
|
taxProviderService: asValue({
|
||||||
|
withTransaction: function () {
|
||||||
|
return this
|
||||||
|
},
|
||||||
registerInstalledProviders: jest.fn(),
|
registerInstalledProviders: jest.fn(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -21,7 +21,11 @@ import {
|
|||||||
import { CurrencyRepository } from "../repositories/currency"
|
import { CurrencyRepository } from "../repositories/currency"
|
||||||
import { FlagRouter } from "../utils/flag-router"
|
import { FlagRouter } from "../utils/flag-router"
|
||||||
import SalesChannelFeatureFlag from "./feature-flags/sales-channels"
|
import SalesChannelFeatureFlag from "./feature-flags/sales-channels"
|
||||||
import { AbstractPaymentService, AbstractTaxService } from "../interfaces"
|
import {
|
||||||
|
AbstractPaymentProcessor,
|
||||||
|
AbstractPaymentService,
|
||||||
|
AbstractTaxService,
|
||||||
|
} from "../interfaces"
|
||||||
|
|
||||||
const silentResolution = <T>(
|
const silentResolution = <T>(
|
||||||
container: AwilixContainer,
|
container: AwilixContainer,
|
||||||
@@ -122,20 +126,109 @@ export default async ({
|
|||||||
|
|
||||||
await entityManager.transaction(async (manager: EntityManager) => {
|
await entityManager.transaction(async (manager: EntityManager) => {
|
||||||
await storeService.withTransaction(manager).create()
|
await storeService.withTransaction(manager).create()
|
||||||
|
const profileServiceTx = profileService.withTransaction(manager)
|
||||||
|
|
||||||
const payProviders =
|
const context = { container, manager, logger }
|
||||||
silentResolution<(typeof BasePaymentService | AbstractPaymentService)[]>(
|
|
||||||
|
await Promise.all([
|
||||||
|
registerPaymentProvider(context),
|
||||||
|
registerPaymentProcessor(context),
|
||||||
|
registerNotificationProvider(context),
|
||||||
|
registerFulfillmentProvider(context),
|
||||||
|
registerTaxProvider(context),
|
||||||
|
profileServiceTx.createDefault(),
|
||||||
|
profileServiceTx.createGiftCardDefault(),
|
||||||
|
(async () => {
|
||||||
|
const isSalesChannelEnabled = featureFlagRouter.isFeatureEnabled(
|
||||||
|
SalesChannelFeatureFlag.key
|
||||||
|
)
|
||||||
|
if (isSalesChannelEnabled) {
|
||||||
|
return await salesChannelService
|
||||||
|
.withTransaction(manager)
|
||||||
|
.createDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
})(),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function registerPaymentProvider({
|
||||||
|
manager,
|
||||||
container,
|
container,
|
||||||
"paymentProviders",
|
logger,
|
||||||
logger
|
}: {
|
||||||
) || []
|
container: AwilixContainer
|
||||||
const payIds = payProviders.map((p) => p.getIdentifier())
|
manager: EntityManager
|
||||||
|
logger: Logger
|
||||||
|
}): Promise<void> {
|
||||||
|
const payProviders = (
|
||||||
|
silentResolution<
|
||||||
|
(
|
||||||
|
| typeof BasePaymentService
|
||||||
|
| AbstractPaymentService
|
||||||
|
| AbstractPaymentProcessor
|
||||||
|
)[]
|
||||||
|
>(container, "paymentProviders", logger) || []
|
||||||
|
).filter((provider) => !(provider instanceof AbstractPaymentProcessor))
|
||||||
|
|
||||||
|
const payIds = payProviders.map((paymentProvider) => {
|
||||||
|
return paymentProvider.getIdentifier()
|
||||||
|
})
|
||||||
|
|
||||||
const pProviderService = container.resolve<PaymentProviderService>(
|
const pProviderService = container.resolve<PaymentProviderService>(
|
||||||
"paymentProviderService"
|
"paymentProviderService"
|
||||||
)
|
)
|
||||||
await pProviderService.registerInstalledProviders(payIds)
|
await pProviderService
|
||||||
|
.withTransaction(manager)
|
||||||
|
.registerInstalledProviders(payIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function registerPaymentProcessor({
|
||||||
|
manager,
|
||||||
|
container,
|
||||||
|
logger,
|
||||||
|
}: {
|
||||||
|
container: AwilixContainer
|
||||||
|
manager: EntityManager
|
||||||
|
logger: Logger
|
||||||
|
}): Promise<void> {
|
||||||
|
const payProviders = (
|
||||||
|
silentResolution<
|
||||||
|
(
|
||||||
|
| typeof BasePaymentService
|
||||||
|
| AbstractPaymentService
|
||||||
|
| AbstractPaymentProcessor
|
||||||
|
)[]
|
||||||
|
>(container, "paymentProviders", logger) || []
|
||||||
|
).filter((provider) => provider instanceof AbstractPaymentProcessor)
|
||||||
|
|
||||||
|
const payIds: string[] = []
|
||||||
|
await Promise.all(
|
||||||
|
payProviders.map((paymentProvider) => {
|
||||||
|
payIds.push(paymentProvider.getIdentifier())
|
||||||
|
return paymentProvider.init()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const pProviderService = container.resolve<PaymentProviderService>(
|
||||||
|
"paymentProviderService"
|
||||||
|
)
|
||||||
|
await pProviderService
|
||||||
|
.withTransaction(manager)
|
||||||
|
.registerInstalledProviders(payIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function registerNotificationProvider({
|
||||||
|
manager,
|
||||||
|
container,
|
||||||
|
logger,
|
||||||
|
}: {
|
||||||
|
container: AwilixContainer
|
||||||
|
manager: EntityManager
|
||||||
|
logger: Logger
|
||||||
|
}): Promise<void> {
|
||||||
const notiProviders =
|
const notiProviders =
|
||||||
silentResolution<typeof BaseNotificationService[]>(
|
silentResolution<typeof BaseNotificationService[]>(
|
||||||
container,
|
container,
|
||||||
@@ -147,8 +240,20 @@ export default async ({
|
|||||||
const nProviderService = container.resolve<NotificationService>(
|
const nProviderService = container.resolve<NotificationService>(
|
||||||
"notificationService"
|
"notificationService"
|
||||||
)
|
)
|
||||||
await nProviderService.registerInstalledProviders(notiIds)
|
await nProviderService
|
||||||
|
.withTransaction(manager)
|
||||||
|
.registerInstalledProviders(notiIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function registerFulfillmentProvider({
|
||||||
|
manager,
|
||||||
|
container,
|
||||||
|
logger,
|
||||||
|
}: {
|
||||||
|
container: AwilixContainer
|
||||||
|
manager: EntityManager
|
||||||
|
logger: Logger
|
||||||
|
}): Promise<void> {
|
||||||
const fulfilProviders =
|
const fulfilProviders =
|
||||||
silentResolution<typeof BaseFulfillmentService[]>(
|
silentResolution<typeof BaseFulfillmentService[]>(
|
||||||
container,
|
container,
|
||||||
@@ -160,28 +265,28 @@ export default async ({
|
|||||||
const fProviderService = container.resolve<FulfillmentProviderService>(
|
const fProviderService = container.resolve<FulfillmentProviderService>(
|
||||||
"fulfillmentProviderService"
|
"fulfillmentProviderService"
|
||||||
)
|
)
|
||||||
await fProviderService.registerInstalledProviders(fulfilIds)
|
await fProviderService
|
||||||
|
.withTransaction(manager)
|
||||||
|
.registerInstalledProviders(fulfilIds)
|
||||||
|
}
|
||||||
|
|
||||||
const taxProviders =
|
async function registerTaxProvider({
|
||||||
silentResolution<AbstractTaxService[]>(
|
manager,
|
||||||
container,
|
container,
|
||||||
"taxProviders",
|
logger,
|
||||||
logger
|
}: {
|
||||||
) || []
|
container: AwilixContainer
|
||||||
|
manager: EntityManager
|
||||||
|
logger: Logger
|
||||||
|
}): Promise<void> {
|
||||||
|
const taxProviders =
|
||||||
|
silentResolution<AbstractTaxService[]>(container, "taxProviders", logger) ||
|
||||||
|
[]
|
||||||
const taxIds = taxProviders.map((p) => p.getIdentifier())
|
const taxIds = taxProviders.map((p) => p.getIdentifier())
|
||||||
|
|
||||||
const tProviderService =
|
const tProviderService =
|
||||||
container.resolve<TaxProviderService>("taxProviderService")
|
container.resolve<TaxProviderService>("taxProviderService")
|
||||||
await tProviderService.registerInstalledProviders(taxIds)
|
await tProviderService
|
||||||
|
.withTransaction(manager)
|
||||||
await profileService.withTransaction(manager).createDefault()
|
.registerInstalledProviders(taxIds)
|
||||||
await profileService.withTransaction(manager).createGiftCardDefault()
|
|
||||||
|
|
||||||
const isSalesChannelEnabled = featureFlagRouter.isFeatureEnabled(
|
|
||||||
SalesChannelFeatureFlag.key
|
|
||||||
)
|
|
||||||
if (isSalesChannelEnabled) {
|
|
||||||
await salesChannelService.withTransaction(manager).createDefault()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { ClassConstructor, MedusaContainer } from "../../types/global"
|
||||||
|
import {
|
||||||
|
AbstractPaymentProcessor,
|
||||||
|
AbstractPaymentService,
|
||||||
|
isPaymentProcessor,
|
||||||
|
isPaymentService,
|
||||||
|
} from "../../interfaces"
|
||||||
|
import { aliasTo, asFunction } from "awilix"
|
||||||
|
|
||||||
|
type Context = {
|
||||||
|
container: MedusaContainer
|
||||||
|
pluginDetails: Record<string, unknown>
|
||||||
|
registrationName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerPaymentServiceFromClass(
|
||||||
|
klass: ClassConstructor<AbstractPaymentService>,
|
||||||
|
context: Context
|
||||||
|
): void {
|
||||||
|
if (!isPaymentService(klass.prototype)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { container, pluginDetails, registrationName } = context
|
||||||
|
|
||||||
|
container.registerAdd(
|
||||||
|
"paymentProviders",
|
||||||
|
asFunction((cradle) => new klass(cradle, pluginDetails.options))
|
||||||
|
)
|
||||||
|
|
||||||
|
container.register({
|
||||||
|
[registrationName]: asFunction(
|
||||||
|
(cradle) => new klass(cradle, pluginDetails.options)
|
||||||
|
),
|
||||||
|
[`pp_${(klass as unknown as typeof AbstractPaymentService).identifier}`]:
|
||||||
|
aliasTo(registrationName),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerPaymentProcessorFromClass(
|
||||||
|
klass: ClassConstructor<AbstractPaymentProcessor>,
|
||||||
|
context: Context
|
||||||
|
): void {
|
||||||
|
if (!isPaymentProcessor(klass.prototype)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { container, pluginDetails, registrationName } = context
|
||||||
|
|
||||||
|
container.registerAdd(
|
||||||
|
"paymentProviders",
|
||||||
|
asFunction((cradle) => new klass(cradle, pluginDetails.options))
|
||||||
|
)
|
||||||
|
|
||||||
|
container.register({
|
||||||
|
[registrationName]: asFunction(
|
||||||
|
(cradle) => new klass(cradle, pluginDetails.options)
|
||||||
|
),
|
||||||
|
[`pp_${(klass as unknown as typeof AbstractPaymentProcessor).identifier}`]:
|
||||||
|
aliasTo(registrationName),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
isCartCompletionStrategy,
|
isCartCompletionStrategy,
|
||||||
isFileService,
|
isFileService,
|
||||||
isNotificationService,
|
isNotificationService,
|
||||||
isPaymentService,
|
|
||||||
isPriceSelectionStrategy,
|
isPriceSelectionStrategy,
|
||||||
isSearchService,
|
isSearchService,
|
||||||
isTaxCalculationStrategy,
|
isTaxCalculationStrategy,
|
||||||
@@ -35,6 +34,10 @@ import {
|
|||||||
} from "../types/global"
|
} from "../types/global"
|
||||||
import formatRegistrationName from "../utils/format-registration-name"
|
import formatRegistrationName from "../utils/format-registration-name"
|
||||||
import logger from "./logger"
|
import logger from "./logger"
|
||||||
|
import {
|
||||||
|
registerPaymentProcessorFromClass,
|
||||||
|
registerPaymentServiceFromClass,
|
||||||
|
} from "./helpers/plugins"
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
rootDirectory: string
|
rootDirectory: string
|
||||||
@@ -371,22 +374,12 @@ export async function registerServices(
|
|||||||
throw new Error(message)
|
throw new Error(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPaymentService(loaded.prototype)) {
|
const context = { container, pluginDetails, registrationName: name }
|
||||||
// Register our payment providers to paymentProviders
|
|
||||||
container.registerAdd(
|
|
||||||
"paymentProviders",
|
|
||||||
asFunction((cradle) => new loaded(cradle, pluginDetails.options))
|
|
||||||
)
|
|
||||||
|
|
||||||
// Add the service directly to the container in order to make simple
|
registerPaymentServiceFromClass(loaded, context)
|
||||||
// resolution if we already know which payment provider we need to use
|
registerPaymentProcessorFromClass(loaded, context)
|
||||||
container.register({
|
|
||||||
[name]: asFunction(
|
if (loaded.prototype instanceof OauthService) {
|
||||||
(cradle) => new loaded(cradle, pluginDetails.options)
|
|
||||||
),
|
|
||||||
[`pp_${loaded.identifier}`]: aliasTo(name),
|
|
||||||
})
|
|
||||||
} else if (loaded.prototype instanceof OauthService) {
|
|
||||||
const appDetails = loaded.getAppDetails(pluginDetails.options)
|
const appDetails = loaded.getAppDetails(pluginDetails.options)
|
||||||
|
|
||||||
const oauthService =
|
const oauthService =
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
import { asClass, 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 {
|
||||||
|
AbstractPaymentProcessor,
|
||||||
|
PaymentProcessorContext,
|
||||||
|
PaymentProcessorError,
|
||||||
|
PaymentProcessorSessionResponse
|
||||||
|
} from "../../interfaces";
|
||||||
|
import { PaymentSessionStatus } from "../../models";
|
||||||
|
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("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))
|
||||||
@@ -16,3 +25,77 @@ 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)))
|
||||||
|
|
||||||
|
export class PaymentProcessor extends AbstractPaymentProcessor {
|
||||||
|
constructor(container) {
|
||||||
|
super(container);
|
||||||
|
}
|
||||||
|
authorizePayment(context: PaymentProcessorContext): Promise<
|
||||||
|
| PaymentProcessorError
|
||||||
|
| {
|
||||||
|
status: PaymentSessionStatus
|
||||||
|
data: PaymentProcessorSessionResponse["session_data"]
|
||||||
|
}
|
||||||
|
> {
|
||||||
|
return Promise.resolve({ } as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPaymentStatus(paymentSessionData: Record<string, unknown>): Promise<PaymentSessionStatus> {
|
||||||
|
return Promise.resolve(PaymentSessionStatus.PENDING);
|
||||||
|
}
|
||||||
|
|
||||||
|
init(): Promise<void> {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
initiatePayment(context: PaymentProcessorContext): Promise<PaymentProcessorError | PaymentProcessorSessionResponse> {
|
||||||
|
return Promise.resolve({ } as PaymentProcessorSessionResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
retrievePayment(paymentSessionData: Record<string, unknown>): Promise<PaymentProcessorError | PaymentProcessorSessionResponse["session_data"]> {
|
||||||
|
return Promise.resolve({ });
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePayment(context: PaymentProcessorContext): Promise<PaymentProcessorError | void> {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
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({});
|
||||||
|
}
|
||||||
|
|
||||||
|
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({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultPaymentSessionInputData = {
|
||||||
|
provider_id: "payment_processor",
|
||||||
|
cart: {
|
||||||
|
context: {},
|
||||||
|
id: "cart-test",
|
||||||
|
email: "test@medusajs.com",
|
||||||
|
shipping_address: {},
|
||||||
|
shipping_methods: [],
|
||||||
|
billing_address: {
|
||||||
|
first_name: "Virgil",
|
||||||
|
last_name: "Van Dijk",
|
||||||
|
address_1: "24 Dunks Drive",
|
||||||
|
city: "Los Angeles",
|
||||||
|
country_code: "US",
|
||||||
|
province: "CA",
|
||||||
|
postal_code: "93011",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 1000,
|
||||||
|
}
|
||||||
@@ -1,360 +0,0 @@
|
|||||||
import { asValue, createContainer } from "awilix"
|
|
||||||
import { MockRepository } from "medusa-test-utils"
|
|
||||||
import PaymentProviderService from "../payment-provider"
|
|
||||||
import { defaultContainer } from "../__fixtures__/payment-provider"
|
|
||||||
import { testPayServiceMock } from "../__mocks__/test-pay"
|
|
||||||
|
|
||||||
describe("PaymentProviderService", () => {
|
|
||||||
describe("retrieveProvider", () => {
|
|
||||||
const container = createContainer({}, defaultContainer)
|
|
||||||
container.register("pp_default_provider", asValue("good"))
|
|
||||||
|
|
||||||
const providerService = container.resolve("paymentProviderService")
|
|
||||||
|
|
||||||
it("successfully retrieves payment provider", () => {
|
|
||||||
const provider = providerService.retrieveProvider("default_provider")
|
|
||||||
expect(provider).toEqual("good")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("fails when payment provider not found", () => {
|
|
||||||
try {
|
|
||||||
providerService.retrieveProvider("unregistered")
|
|
||||||
} catch (err) {
|
|
||||||
expect(err.message).toEqual(
|
|
||||||
"Could not find a payment provider with id: unregistered"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("createSession", () => {
|
|
||||||
const container = createContainer({}, defaultContainer)
|
|
||||||
container.register(
|
|
||||||
"pp_default_provider",
|
|
||||||
asValue({
|
|
||||||
withTransaction: function () {
|
|
||||||
return this
|
|
||||||
},
|
|
||||||
createPayment: jest.fn().mockReturnValue(Promise.resolve({})),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const providerService = container.resolve("paymentProviderService")
|
|
||||||
|
|
||||||
it("successfully creates session", async () => {
|
|
||||||
await providerService.createSession("default_provider", {
|
|
||||||
object: "cart",
|
|
||||||
region: {
|
|
||||||
currency_code: "usd",
|
|
||||||
},
|
|
||||||
total: 100,
|
|
||||||
})
|
|
||||||
|
|
||||||
const defaultProvider = container.resolve("pp_default_provider")
|
|
||||||
|
|
||||||
expect(defaultProvider.createPayment).toBeCalledTimes(1)
|
|
||||||
expect(defaultProvider.createPayment).toBeCalledWith({
|
|
||||||
amount: 100,
|
|
||||||
object: "cart",
|
|
||||||
total: 100,
|
|
||||||
region: {
|
|
||||||
currency_code: "usd",
|
|
||||||
},
|
|
||||||
cart: {
|
|
||||||
context: undefined,
|
|
||||||
email: undefined,
|
|
||||||
id: undefined,
|
|
||||||
shipping_address: undefined,
|
|
||||||
shipping_methods: undefined,
|
|
||||||
},
|
|
||||||
currency_code: "usd",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("updateSession", () => {
|
|
||||||
const container = createContainer({}, defaultContainer)
|
|
||||||
container.register(
|
|
||||||
"paymentSessionRepository",
|
|
||||||
asValue(
|
|
||||||
MockRepository({
|
|
||||||
findOne: () =>
|
|
||||||
Promise.resolve({
|
|
||||||
id: "session",
|
|
||||||
provider_id: "default_provider",
|
|
||||||
data: {
|
|
||||||
id: "1234",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
container.register(
|
|
||||||
"pp_default_provider",
|
|
||||||
asValue({
|
|
||||||
withTransaction: function () {
|
|
||||||
return this
|
|
||||||
},
|
|
||||||
updatePayment: jest.fn().mockReturnValue(Promise.resolve({})),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const providerService = container.resolve("paymentProviderService")
|
|
||||||
|
|
||||||
it("successfully creates session", async () => {
|
|
||||||
await providerService.updateSession(
|
|
||||||
{
|
|
||||||
id: "session",
|
|
||||||
provider_id: "default_provider",
|
|
||||||
data: {
|
|
||||||
id: "1234",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
object: "cart",
|
|
||||||
total: 100,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultProvider = container.resolve("pp_default_provider")
|
|
||||||
|
|
||||||
expect(defaultProvider.updatePayment).toBeCalledTimes(1)
|
|
||||||
expect(defaultProvider.updatePayment).toBeCalledWith(
|
|
||||||
{ id: "1234" },
|
|
||||||
{
|
|
||||||
object: "cart",
|
|
||||||
amount: 100,
|
|
||||||
total: 100,
|
|
||||||
cart: {
|
|
||||||
context: undefined,
|
|
||||||
email: undefined,
|
|
||||||
id: undefined,
|
|
||||||
shipping_address: undefined,
|
|
||||||
shipping_methods: undefined,
|
|
||||||
},
|
|
||||||
currency_code: undefined,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe(`PaymentProviderService`, () => {
|
|
||||||
const container = createContainer({}, defaultContainer)
|
|
||||||
container.register("pp_default_provider", asValue(testPayServiceMock))
|
|
||||||
container.register(
|
|
||||||
"paymentSessionRepository",
|
|
||||||
asValue(
|
|
||||||
MockRepository({
|
|
||||||
findOne: () =>
|
|
||||||
Promise.resolve({
|
|
||||||
id: "session",
|
|
||||||
provider_id: "default_provider",
|
|
||||||
data: {
|
|
||||||
id: "1234",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
container.register(
|
|
||||||
"paymentRepository",
|
|
||||||
asValue(
|
|
||||||
MockRepository({
|
|
||||||
findOne: () =>
|
|
||||||
Promise.resolve({
|
|
||||||
id: "pay_jadazdjk",
|
|
||||||
provider_id: "default_provider",
|
|
||||||
data: {
|
|
||||||
id: "1234",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
find: () =>
|
|
||||||
Promise.resolve([
|
|
||||||
{
|
|
||||||
id: "pay_jadazdjk",
|
|
||||||
provider_id: "default_provider",
|
|
||||||
data: {
|
|
||||||
id: "1234",
|
|
||||||
},
|
|
||||||
captured_at: new Date(),
|
|
||||||
amount: 100,
|
|
||||||
amount_refunded: 0,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
const providerService = container.resolve("paymentProviderService")
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("successfully retrieves payment provider", () => {
|
|
||||||
const provider = providerService.retrieveProvider("default_provider")
|
|
||||||
expect(provider.identifier).toEqual("test-pay")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("successfully creates session", async () => {
|
|
||||||
await providerService.createSession("default_provider", {
|
|
||||||
object: "cart",
|
|
||||||
region: {
|
|
||||||
currency_code: "usd",
|
|
||||||
},
|
|
||||||
total: 100,
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(testPayServiceMock.createPayment).toBeCalledTimes(1)
|
|
||||||
expect(testPayServiceMock.createPayment).toBeCalledWith({
|
|
||||||
amount: 100,
|
|
||||||
object: "cart",
|
|
||||||
total: 100,
|
|
||||||
region: {
|
|
||||||
currency_code: "usd",
|
|
||||||
},
|
|
||||||
cart: {
|
|
||||||
context: undefined,
|
|
||||||
email: undefined,
|
|
||||||
id: undefined,
|
|
||||||
shipping_address: undefined,
|
|
||||||
shipping_methods: undefined,
|
|
||||||
},
|
|
||||||
currency_code: "usd",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("successfully update session", async () => {
|
|
||||||
await providerService.updateSession(
|
|
||||||
{
|
|
||||||
id: "session",
|
|
||||||
provider_id: "default_provider",
|
|
||||||
data: {
|
|
||||||
id: "1234",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
object: "cart",
|
|
||||||
total: 100,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(testPayServiceMock.updatePayment).toBeCalledTimes(1)
|
|
||||||
expect(testPayServiceMock.updatePayment).toBeCalledWith(
|
|
||||||
{ id: "1234" },
|
|
||||||
{
|
|
||||||
amount: 100,
|
|
||||||
object: "cart",
|
|
||||||
total: 100,
|
|
||||||
cart: {
|
|
||||||
context: undefined,
|
|
||||||
email: undefined,
|
|
||||||
id: undefined,
|
|
||||||
shipping_address: undefined,
|
|
||||||
shipping_methods: undefined,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("successfully refresh session", async () => {
|
|
||||||
await providerService.refreshSession(
|
|
||||||
{
|
|
||||||
id: "session",
|
|
||||||
provider_id: "default_provider",
|
|
||||||
data: {
|
|
||||||
id: "1234",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provider_id: "default_provider",
|
|
||||||
amount: 100,
|
|
||||||
currency_code: "usd",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(testPayServiceMock.deletePayment).toBeCalledTimes(1)
|
|
||||||
expect(testPayServiceMock.createPayment).toBeCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("successfully delete session", async () => {
|
|
||||||
await providerService.deleteSession({
|
|
||||||
id: "session",
|
|
||||||
provider_id: "default_provider",
|
|
||||||
data: {
|
|
||||||
id: "1234",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(testPayServiceMock.deletePayment).toBeCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("successfully delete session", async () => {
|
|
||||||
await providerService.deleteSession({
|
|
||||||
id: "session",
|
|
||||||
provider_id: "default_provider",
|
|
||||||
data: {
|
|
||||||
id: "1234",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(testPayServiceMock.deletePayment).toBeCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("successfully authorize payment", async () => {
|
|
||||||
await providerService.authorizePayment(
|
|
||||||
{
|
|
||||||
id: "session",
|
|
||||||
provider_id: "default_provider",
|
|
||||||
data: {
|
|
||||||
id: "1234",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(testPayServiceMock.authorizePayment).toBeCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("successfully update session data", async () => {
|
|
||||||
await providerService.updateSessionData(
|
|
||||||
{
|
|
||||||
id: "session",
|
|
||||||
provider_id: "default_provider",
|
|
||||||
data: {
|
|
||||||
id: "1234",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(testPayServiceMock.updatePaymentData).toBeCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("successfully cancel payment", async () => {
|
|
||||||
await providerService.cancelPayment({
|
|
||||||
id: "pay_jadazdjk",
|
|
||||||
})
|
|
||||||
expect(testPayServiceMock.cancelPayment).toBeCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("successfully capture payment", async () => {
|
|
||||||
await providerService.capturePayment({
|
|
||||||
id: "pay_jadazdjk",
|
|
||||||
})
|
|
||||||
expect(testPayServiceMock.capturePayment).toBeCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("successfully refund payment", async () => {
|
|
||||||
await providerService.refundPayment(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
id: "pay_jadazdjk",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
50
|
|
||||||
)
|
|
||||||
expect(testPayServiceMock.refundPayment).toBeCalledTimes(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -2,8 +2,11 @@ import { isDefined, MedusaError } from "medusa-core-utils"
|
|||||||
import { BasePaymentService } from "medusa-interfaces"
|
import { BasePaymentService } from "medusa-interfaces"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import {
|
import {
|
||||||
|
AbstractPaymentProcessor,
|
||||||
AbstractPaymentService,
|
AbstractPaymentService,
|
||||||
|
isPaymentProcessorError,
|
||||||
PaymentContext,
|
PaymentContext,
|
||||||
|
PaymentProcessorError,
|
||||||
PaymentSessionResponse,
|
PaymentSessionResponse,
|
||||||
TransactionBaseService,
|
TransactionBaseService,
|
||||||
} from "../interfaces"
|
} from "../interfaces"
|
||||||
@@ -26,6 +29,7 @@ import { buildQuery, isString } from "../utils"
|
|||||||
import { FlagRouter } from "../utils/flag-router"
|
import { FlagRouter } from "../utils/flag-router"
|
||||||
import { CustomerService } from "./index"
|
import { CustomerService } from "./index"
|
||||||
import PaymentService from "./payment"
|
import PaymentService from "./payment"
|
||||||
|
import { EOL } from "os"
|
||||||
|
|
||||||
type PaymentProviderKey = `pp_${string}` | "systemPaymentProviderService"
|
type PaymentProviderKey = `pp_${string}` | "systemPaymentProviderService"
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
@@ -53,6 +57,10 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
protected readonly paymentProviderRepository_: typeof PaymentProviderRepository
|
protected readonly paymentProviderRepository_: typeof PaymentProviderRepository
|
||||||
protected readonly paymentRepository_: typeof PaymentRepository
|
protected readonly paymentRepository_: typeof PaymentRepository
|
||||||
|
protected get paymentService_(): PaymentService {
|
||||||
|
// defer resolution. then it will use the cached resolved service
|
||||||
|
return this.container_.paymentService
|
||||||
|
}
|
||||||
protected readonly refundRepository_: typeof RefundRepository
|
protected readonly refundRepository_: typeof RefundRepository
|
||||||
protected readonly customerService_: CustomerService
|
protected readonly customerService_: CustomerService
|
||||||
protected readonly logger_: Logger
|
protected readonly logger_: Logger
|
||||||
@@ -98,15 +106,27 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
return await ppRepo.find()
|
return await ppRepo.find()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a payment entity with the given id.
|
||||||
|
* @param paymentId
|
||||||
|
* @param relations
|
||||||
|
*/
|
||||||
async retrievePayment(
|
async retrievePayment(
|
||||||
id: string,
|
paymentId: string,
|
||||||
relations: string[] = []
|
relations: string[] = []
|
||||||
): Promise<Payment | never> {
|
): Promise<Payment | never> {
|
||||||
|
if (!isDefined(paymentId)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`"paymentId" must be defined`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const paymentRepo = this.activeManager_.withRepository(
|
const paymentRepo = this.activeManager_.withRepository(
|
||||||
this.paymentRepository_
|
this.paymentRepository_
|
||||||
)
|
)
|
||||||
const query = {
|
const query = {
|
||||||
where: { id },
|
where: { id: paymentId },
|
||||||
relations: [] as string[],
|
relations: [] as string[],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,13 +139,18 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
if (!payment) {
|
if (!payment) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.NOT_FOUND,
|
MedusaError.Types.NOT_FOUND,
|
||||||
`Payment with ${id} was not found`
|
`Payment with ${paymentId} was not found`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return payment
|
return payment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all the payments according to the given selector and config.
|
||||||
|
* @param selector
|
||||||
|
* @param config
|
||||||
|
*/
|
||||||
async listPayments(
|
async listPayments(
|
||||||
selector: Selector<Payment>,
|
selector: Selector<Payment>,
|
||||||
config: FindConfig<Payment> = {
|
config: FindConfig<Payment> = {
|
||||||
@@ -139,35 +164,54 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
return await payRepo.find(query)
|
return await payRepo.find(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the payment session for the given id.
|
||||||
|
* @param paymentSessionId
|
||||||
|
* @param relations
|
||||||
|
*/
|
||||||
async retrieveSession(
|
async retrieveSession(
|
||||||
id: string,
|
paymentSessionId: string,
|
||||||
relations: string[] = []
|
relations: string[] = []
|
||||||
): Promise<PaymentSession | never> {
|
): Promise<PaymentSession | never> {
|
||||||
|
if (!isDefined(paymentSessionId)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`"paymentSessionId" must be defined`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const sessionRepo = this.activeManager_.withRepository(
|
const sessionRepo = this.activeManager_.withRepository(
|
||||||
this.paymentSessionRepository_
|
this.paymentSessionRepository_
|
||||||
)
|
)
|
||||||
|
|
||||||
const query = {
|
const query = buildQuery({ id: paymentSessionId }, { relations })
|
||||||
where: { id },
|
|
||||||
relations: [] as string[],
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relations.length) {
|
|
||||||
query.relations = relations
|
|
||||||
}
|
|
||||||
|
|
||||||
const session = await sessionRepo.findOne(query)
|
const session = await sessionRepo.findOne(query)
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.NOT_FOUND,
|
MedusaError.Types.NOT_FOUND,
|
||||||
`Payment Session with ${id} was not found`
|
`Payment Session with ${paymentSessionId} was not found`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* @param providerId
|
||||||
|
* @param cart
|
||||||
|
*/
|
||||||
|
async createSession(providerId: string, cart: Cart): Promise<PaymentSession>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a payment session with the given provider.
|
||||||
|
* @param sessionInput
|
||||||
|
*/
|
||||||
|
async createSession(
|
||||||
|
sessionInput: PaymentSessionInput
|
||||||
|
): Promise<PaymentSession>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a payment session with the given provider.
|
* Creates a payment session with the given provider.
|
||||||
* @param providerIdOrSessionInput - the id of the provider to create payment with or the input data
|
* @param providerIdOrSessionInput - the id of the provider to create payment with or the input data
|
||||||
@@ -184,11 +228,14 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
const providerId = isString(providerIdOrSessionInput)
|
const providerId = isString(providerIdOrSessionInput)
|
||||||
? providerIdOrSessionInput
|
? providerIdOrSessionInput
|
||||||
: providerIdOrSessionInput.provider_id
|
: providerIdOrSessionInput.provider_id
|
||||||
|
|
||||||
const data = (
|
const data = (
|
||||||
isString(providerIdOrSessionInput) ? cart : providerIdOrSessionInput
|
isString(providerIdOrSessionInput) ? cart : providerIdOrSessionInput
|
||||||
) as Cart | PaymentSessionInput
|
) as Cart | PaymentSessionInput
|
||||||
|
|
||||||
const provider = this.retrieveProvider<AbstractPaymentService>(providerId)
|
const provider = this.retrieveProvider<
|
||||||
|
AbstractPaymentService | AbstractPaymentProcessor
|
||||||
|
>(providerId)
|
||||||
const context = this.buildPaymentProcessorContext(data)
|
const context = this.buildPaymentProcessorContext(data)
|
||||||
|
|
||||||
if (!isDefined(context.currency_code) || !isDefined(context.amount)) {
|
if (!isDefined(context.currency_code) || !isDefined(context.amount)) {
|
||||||
@@ -198,9 +245,28 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const paymentResponse = await provider
|
let paymentResponse
|
||||||
|
if (provider instanceof AbstractPaymentProcessor) {
|
||||||
|
paymentResponse = await provider.initiatePayment({
|
||||||
|
amount: context.amount,
|
||||||
|
context: context.context,
|
||||||
|
currency_code: context.currency_code,
|
||||||
|
customer: context.customer,
|
||||||
|
email: context.email,
|
||||||
|
billing_address: context.billing_address,
|
||||||
|
resource_id: context.resource_id,
|
||||||
|
paymentSessionData: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
if ("error" in paymentResponse) {
|
||||||
|
this.throwFromPaymentProcessorError(paymentResponse)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Added to stay backward compatible
|
||||||
|
paymentResponse = await provider
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.createPayment(context)
|
.createPayment(context)
|
||||||
|
}
|
||||||
|
|
||||||
const sessionData = paymentResponse.session_data ?? paymentResponse
|
const sessionData = paymentResponse.session_data ?? paymentResponse
|
||||||
|
|
||||||
@@ -242,10 +308,21 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
): Promise<PaymentSession> {
|
): Promise<PaymentSession> {
|
||||||
return this.atomicPhase_(async (transactionManager) => {
|
return this.atomicPhase_(async (transactionManager) => {
|
||||||
const session = await this.retrieveSession(paymentSession.id)
|
const session = await this.retrieveSession(paymentSession.id)
|
||||||
const provider = this.retrieveProvider<AbstractPaymentService>(
|
|
||||||
paymentSession.provider_id
|
const provider = this.retrieveProvider<
|
||||||
)
|
AbstractPaymentService | AbstractPaymentProcessor
|
||||||
await provider.withTransaction(transactionManager).deletePayment(session)
|
>(paymentSession.provider_id)
|
||||||
|
|
||||||
|
if (provider instanceof AbstractPaymentProcessor) {
|
||||||
|
const error = await provider.deletePayment(session.data)
|
||||||
|
if (isPaymentProcessorError(error)) {
|
||||||
|
this.throwFromPaymentProcessorError(error)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await provider
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.deletePayment(session)
|
||||||
|
}
|
||||||
|
|
||||||
const sessionRepo = transactionManager.withRepository(
|
const sessionRepo = transactionManager.withRepository(
|
||||||
this.paymentSessionRepository_
|
this.paymentSessionRepository_
|
||||||
@@ -271,16 +348,42 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
sessionInput: Cart | PaymentSessionInput
|
sessionInput: Cart | PaymentSessionInput
|
||||||
): Promise<PaymentSession> {
|
): Promise<PaymentSession> {
|
||||||
return await this.atomicPhase_(async (transactionManager) => {
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
const provider = this.retrieveProvider(paymentSession.provider_id)
|
const provider = this.retrieveProvider<
|
||||||
|
AbstractPaymentService | AbstractPaymentProcessor
|
||||||
|
>(paymentSession.provider_id)
|
||||||
|
|
||||||
const context = this.buildPaymentProcessorContext(sessionInput)
|
const context = this.buildPaymentProcessorContext(sessionInput)
|
||||||
|
|
||||||
const paymentResponse = await provider
|
let paymentResponse
|
||||||
|
if (provider instanceof AbstractPaymentProcessor) {
|
||||||
|
paymentResponse =
|
||||||
|
(await provider.updatePayment({
|
||||||
|
amount: context.amount,
|
||||||
|
context: context.context,
|
||||||
|
currency_code: context.currency_code,
|
||||||
|
customer: context.customer,
|
||||||
|
email: context.email,
|
||||||
|
billing_address: context.billing_address,
|
||||||
|
resource_id: context.resource_id,
|
||||||
|
paymentSessionData: paymentSession.data,
|
||||||
|
})) ?? {}
|
||||||
|
|
||||||
|
if (paymentResponse && "error" in paymentResponse) {
|
||||||
|
this.throwFromPaymentProcessorError(paymentResponse)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
paymentResponse = await provider
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.updatePayment(paymentSession.data, context)
|
.updatePayment(paymentSession.data, context)
|
||||||
|
}
|
||||||
|
|
||||||
const sessionData = paymentResponse.session_data ?? paymentResponse
|
const sessionData = paymentResponse.session_data ?? paymentResponse
|
||||||
|
|
||||||
|
// If no update occurs, return the original session
|
||||||
|
if (!sessionData) {
|
||||||
|
return await this.retrieveSession(paymentSession.id)
|
||||||
|
}
|
||||||
|
|
||||||
await this.processUpdateRequestsData(
|
await this.processUpdateRequestsData(
|
||||||
{
|
{
|
||||||
customer: { id: context.customer?.id },
|
customer: { id: context.customer?.id },
|
||||||
@@ -309,10 +412,20 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const provider = this.retrieveProvider(paymentSession.provider_id)
|
const provider = this.retrieveProvider<
|
||||||
|
AbstractPaymentService | AbstractPaymentProcessor
|
||||||
|
>(paymentSession.provider_id)
|
||||||
|
|
||||||
|
if (provider instanceof AbstractPaymentProcessor) {
|
||||||
|
const error = await provider.deletePayment(paymentSession.data)
|
||||||
|
if (isPaymentProcessorError(error)) {
|
||||||
|
this.throwFromPaymentProcessorError(error)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
await provider
|
await provider
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.deletePayment(paymentSession)
|
.deletePayment(paymentSession)
|
||||||
|
}
|
||||||
|
|
||||||
const sessionRepo = transactionManager.withRepository(
|
const sessionRepo = transactionManager.withRepository(
|
||||||
this.paymentSessionRepository_
|
this.paymentSessionRepository_
|
||||||
@@ -324,15 +437,20 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds a provider given an id
|
* Finds a provider given an id
|
||||||
* @param {string} providerId - the id of the provider to get
|
* @param providerId - the id of the provider to get
|
||||||
* @return {PaymentService} the payment provider
|
* @return the payment provider
|
||||||
*/
|
*/
|
||||||
retrieveProvider<
|
retrieveProvider<
|
||||||
TProvider extends AbstractPaymentService | typeof BasePaymentService
|
TProvider extends
|
||||||
|
| AbstractPaymentService
|
||||||
|
| typeof BasePaymentService
|
||||||
|
| AbstractPaymentProcessor
|
||||||
>(
|
>(
|
||||||
providerId: string
|
providerId: string
|
||||||
): TProvider extends AbstractPaymentService
|
): TProvider extends AbstractPaymentService
|
||||||
? AbstractPaymentService
|
? AbstractPaymentService
|
||||||
|
: TProvider extends AbstractPaymentProcessor
|
||||||
|
? AbstractPaymentProcessor
|
||||||
: typeof BasePaymentService {
|
: typeof BasePaymentService {
|
||||||
try {
|
try {
|
||||||
let provider
|
let provider
|
||||||
@@ -356,10 +474,25 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
const { payment_session, currency_code, amount, provider_id } = data
|
const { payment_session, currency_code, amount, provider_id } = data
|
||||||
const providerId = provider_id ?? payment_session.provider_id
|
const providerId = provider_id ?? payment_session.provider_id
|
||||||
|
|
||||||
const provider = this.retrieveProvider<AbstractPaymentService>(providerId)
|
const provider = this.retrieveProvider<
|
||||||
const paymentData = await provider
|
AbstractPaymentService | AbstractPaymentProcessor
|
||||||
|
>(providerId)
|
||||||
|
|
||||||
|
let paymentData: Record<string, unknown> = {}
|
||||||
|
|
||||||
|
if (provider instanceof AbstractPaymentProcessor) {
|
||||||
|
const res = await provider.retrievePayment(payment_session.data)
|
||||||
|
if ("error" in res) {
|
||||||
|
this.throwFromPaymentProcessorError(res as PaymentProcessorError)
|
||||||
|
} else {
|
||||||
|
// Use else to avoid casting the object and infer the type instead
|
||||||
|
paymentData = res
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
paymentData = await provider
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.getPaymentData(payment_session)
|
.getPaymentData(payment_session)
|
||||||
|
}
|
||||||
|
|
||||||
const paymentRepo = transactionManager.withRepository(
|
const paymentRepo = transactionManager.withRepository(
|
||||||
this.paymentRepository_
|
this.paymentRepository_
|
||||||
@@ -382,8 +515,7 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
data: { order_id?: string; swap_id?: string }
|
data: { order_id?: string; swap_id?: string }
|
||||||
): Promise<Payment> {
|
): Promise<Payment> {
|
||||||
return await this.atomicPhase_(async (transactionManager) => {
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
const paymentService = this.container_.paymentService
|
return await this.paymentService_
|
||||||
return await paymentService
|
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.update(paymentId, data)
|
.update(paymentId, data)
|
||||||
})
|
})
|
||||||
@@ -403,14 +535,28 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const provider = this.retrieveProvider(paymentSession.provider_id)
|
const provider = this.retrieveProvider(paymentSession.provider_id)
|
||||||
|
|
||||||
|
if (provider instanceof AbstractPaymentProcessor) {
|
||||||
|
const res = await provider.authorizePayment(
|
||||||
|
paymentSession.data,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
if ("error" in res) {
|
||||||
|
this.throwFromPaymentProcessorError(res)
|
||||||
|
} else {
|
||||||
|
// Use else to avoid casting the object and infer the type instead
|
||||||
|
session.data = res.data
|
||||||
|
session.status = res.status
|
||||||
|
}
|
||||||
|
} else {
|
||||||
const { status, data } = await provider
|
const { status, data } = await provider
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.authorizePayment(session, context)
|
.authorizePayment(session, context)
|
||||||
|
|
||||||
session.data = data
|
session.data = data
|
||||||
session.status = status
|
session.status = status
|
||||||
|
}
|
||||||
|
|
||||||
if (status === PaymentSessionStatus.AUTHORIZED) {
|
if (session.status === PaymentSessionStatus.AUTHORIZED) {
|
||||||
session.payment_authorized_at = new Date()
|
session.payment_authorized_at = new Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,10 +576,17 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
|
|
||||||
const provider = this.retrieveProvider(paymentSession.provider_id)
|
const provider = this.retrieveProvider(paymentSession.provider_id)
|
||||||
|
|
||||||
|
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.`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
session.data = await provider
|
session.data = await provider
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.updatePaymentData(paymentSession.data, data)
|
.updatePaymentData(paymentSession.data, data)
|
||||||
session.status = paymentSession.status
|
session.status = paymentSession.status
|
||||||
|
}
|
||||||
|
|
||||||
const sessionRepo = transactionManager.withRepository(
|
const sessionRepo = transactionManager.withRepository(
|
||||||
this.paymentSessionRepository_
|
this.paymentSessionRepository_
|
||||||
@@ -448,9 +601,17 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
return await this.atomicPhase_(async (transactionManager) => {
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
const payment = await this.retrievePayment(paymentObj.id)
|
const payment = await this.retrievePayment(paymentObj.id)
|
||||||
const provider = this.retrieveProvider(payment.provider_id)
|
const provider = this.retrieveProvider(payment.provider_id)
|
||||||
|
|
||||||
|
if (provider instanceof AbstractPaymentProcessor) {
|
||||||
|
const error = await provider.cancelPayment(payment.data)
|
||||||
|
if (isPaymentProcessorError(error)) {
|
||||||
|
this.throwFromPaymentProcessorError(error)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
payment.data = await provider
|
payment.data = await provider
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.cancelPayment(payment)
|
.cancelPayment(payment)
|
||||||
|
}
|
||||||
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
payment.canceled_at = now.toISOString()
|
payment.canceled_at = now.toISOString()
|
||||||
@@ -464,6 +625,10 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
|
|
||||||
async getStatus(payment: Payment): Promise<PaymentSessionStatus> {
|
async getStatus(payment: Payment): Promise<PaymentSessionStatus> {
|
||||||
const provider = this.retrieveProvider(payment.provider_id)
|
const provider = this.retrieveProvider(payment.provider_id)
|
||||||
|
if (provider instanceof AbstractPaymentProcessor) {
|
||||||
|
return await provider.getPaymentStatus(payment.data)
|
||||||
|
}
|
||||||
|
|
||||||
return await provider
|
return await provider
|
||||||
.withTransaction(this.activeManager_)
|
.withTransaction(this.activeManager_)
|
||||||
.getStatus(payment.data)
|
.getStatus(payment.data)
|
||||||
@@ -475,9 +640,20 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
return await this.atomicPhase_(async (transactionManager) => {
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
const payment = await this.retrievePayment(paymentObj.id)
|
const payment = await this.retrievePayment(paymentObj.id)
|
||||||
const provider = this.retrieveProvider(payment.provider_id)
|
const provider = this.retrieveProvider(payment.provider_id)
|
||||||
|
|
||||||
|
if (provider instanceof AbstractPaymentProcessor) {
|
||||||
|
const res = await provider.capturePayment(payment.data)
|
||||||
|
if ("error" in res) {
|
||||||
|
this.throwFromPaymentProcessorError(res as PaymentProcessorError)
|
||||||
|
} else {
|
||||||
|
// Use else to avoid casting the object and infer the type instead
|
||||||
|
payment.data = res
|
||||||
|
}
|
||||||
|
} else {
|
||||||
payment.data = await provider
|
payment.data = await provider
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.capturePayment(payment)
|
.capturePayment(payment)
|
||||||
|
}
|
||||||
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
payment.captured_at = now.toISOString()
|
payment.captured_at = now.toISOString()
|
||||||
@@ -536,9 +712,23 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
const refundAmount = Math.min(currentRefundable, balance)
|
const refundAmount = Math.min(currentRefundable, balance)
|
||||||
|
|
||||||
const provider = this.retrieveProvider(paymentToRefund.provider_id)
|
const provider = this.retrieveProvider(paymentToRefund.provider_id)
|
||||||
|
|
||||||
|
if (provider instanceof AbstractPaymentProcessor) {
|
||||||
|
const res = await provider.refundPayment(
|
||||||
|
paymentToRefund.data,
|
||||||
|
refundAmount
|
||||||
|
)
|
||||||
|
if (isPaymentProcessorError(res)) {
|
||||||
|
this.throwFromPaymentProcessorError(res as PaymentProcessorError)
|
||||||
|
} else {
|
||||||
|
// Use else to avoid casting the object and infer the type instead
|
||||||
|
paymentToRefund.data = res
|
||||||
|
}
|
||||||
|
} else {
|
||||||
paymentToRefund.data = await provider
|
paymentToRefund.data = await provider
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.refundPayment(paymentToRefund, refundAmount)
|
.refundPayment(paymentToRefund, refundAmount)
|
||||||
|
}
|
||||||
|
|
||||||
paymentToRefund.amount_refunded += refundAmount
|
paymentToRefund.amount_refunded += refundAmount
|
||||||
await paymentRepo.save(paymentToRefund)
|
await paymentRepo.save(paymentToRefund)
|
||||||
@@ -591,9 +781,20 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const provider = this.retrieveProvider(payment.provider_id)
|
const provider = this.retrieveProvider(payment.provider_id)
|
||||||
|
|
||||||
|
if (provider instanceof AbstractPaymentProcessor) {
|
||||||
|
const res = await provider.refundPayment(payment.data, amount)
|
||||||
|
if (isPaymentProcessorError(res)) {
|
||||||
|
this.throwFromPaymentProcessorError(res as PaymentProcessorError)
|
||||||
|
} else {
|
||||||
|
// Use else to avoid casting the object and infer the type instead
|
||||||
|
payment.data = res
|
||||||
|
}
|
||||||
|
} else {
|
||||||
payment.data = await provider
|
payment.data = await provider
|
||||||
.withTransaction(manager)
|
.withTransaction(manager)
|
||||||
.refundPayment(payment, amount)
|
.refundPayment(payment, amount)
|
||||||
|
}
|
||||||
|
|
||||||
payment.amount_refunded += amount
|
payment.amount_refunded += amount
|
||||||
|
|
||||||
@@ -652,19 +853,21 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
context.cart = {
|
context.cart = {
|
||||||
context: cart.context,
|
context: cart.context,
|
||||||
shipping_address: cart.shipping_address,
|
shipping_address: cart.shipping_address,
|
||||||
|
billing_address: cart.billing_address,
|
||||||
id: cart.id,
|
id: cart.id,
|
||||||
email: cart.email,
|
email: cart.email,
|
||||||
shipping_methods: cart.shipping_methods,
|
shipping_methods: cart.shipping_methods,
|
||||||
}
|
}
|
||||||
context.amount = cart.total!
|
context.amount = cart.total!
|
||||||
context.currency_code = cart.region?.currency_code
|
context.currency_code = cart.region?.currency_code
|
||||||
|
context.resource_id = cart.id
|
||||||
Object.assign(context, cart)
|
Object.assign(context, cart)
|
||||||
} else {
|
} else {
|
||||||
const data = cartOrData as PaymentSessionInput
|
const data = cartOrData as PaymentSessionInput
|
||||||
context.cart = data.cart
|
context.cart = data.cart
|
||||||
context.amount = data.amount
|
context.amount = data.amount
|
||||||
context.currency_code = data.currency_code
|
context.currency_code = data.currency_code
|
||||||
context.resource_id = data.resource_id
|
context.resource_id = data.resource_id ?? data.cart.id
|
||||||
Object.assign(context, cart)
|
Object.assign(context, cart)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -743,4 +946,12 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private throwFromPaymentProcessorError(errObj: PaymentProcessorError) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`${errObj.error}${errObj.detail ? `:${EOL}${errObj.detail}` : ""}`,
|
||||||
|
errObj.code
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -18,11 +18,13 @@ export type PaymentSessionInput = {
|
|||||||
email: string
|
email: string
|
||||||
shipping_address: Address | null
|
shipping_address: Address | null
|
||||||
shipping_methods: ShippingMethod[]
|
shipping_methods: ShippingMethod[]
|
||||||
|
billing_address?: Address | null
|
||||||
}
|
}
|
||||||
customer?: Customer | null
|
customer?: Customer | null
|
||||||
currency_code: string
|
currency_code: string
|
||||||
amount: number
|
amount: number
|
||||||
resource_id?: string
|
resource_id?: string
|
||||||
|
paymentSessionData?: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CreatePaymentInput = {
|
export type CreatePaymentInput = {
|
||||||
|
|||||||
Reference in new issue
Block a user