feat: Region PaymentProvider link (#6577)
**What** - Introduce link between Region and PaymentProvider - Introduce API endpoint `GET /store/regions/:id/payment-providers` for retrieving providers by region - Add tests for both
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
import { ModuleRegistrationName, Modules } from "@medusajs/modules-sdk"
|
||||
import { IPaymentModuleService, IRegionModuleService } from "@medusajs/types"
|
||||
import path from "path"
|
||||
import { startBootstrapApp } from "../../../environment-helpers/bootstrap-app"
|
||||
import { getContainer } from "../../../environment-helpers/use-container"
|
||||
import { initDb, useDb } from "../../../environment-helpers/use-db"
|
||||
|
||||
jest.setTimeout(50000)
|
||||
|
||||
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
||||
|
||||
describe("Region and Payment Providers", () => {
|
||||
let dbConnection
|
||||
let appContainer
|
||||
let shutdownServer
|
||||
let regionModule: IRegionModuleService
|
||||
let paymentModule: IPaymentModuleService
|
||||
let remoteQuery
|
||||
let remoteLink
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||
dbConnection = await initDb({ cwd, env } as any)
|
||||
shutdownServer = await startBootstrapApp({ cwd, env })
|
||||
appContainer = getContainer()
|
||||
regionModule = appContainer.resolve(ModuleRegistrationName.REGION)
|
||||
paymentModule = appContainer.resolve(ModuleRegistrationName.PAYMENT)
|
||||
remoteQuery = appContainer.resolve("remoteQuery")
|
||||
remoteLink = appContainer.resolve("remoteLink")
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
await db.teardown()
|
||||
})
|
||||
|
||||
it("should query region and payment provider link with remote query", async () => {
|
||||
const region = await regionModule.create({
|
||||
name: "North America",
|
||||
currency_code: "usd",
|
||||
})
|
||||
|
||||
await remoteLink.create([
|
||||
{
|
||||
[Modules.REGION]: {
|
||||
region_id: region.id,
|
||||
},
|
||||
[Modules.PAYMENT]: {
|
||||
payment_provider_id: "pp_system_default",
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const links = await remoteQuery({
|
||||
region: {
|
||||
fields: ["id"],
|
||||
payment_providers: {
|
||||
fields: ["id"],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const otherLink = await remoteQuery({
|
||||
payment_providers: {
|
||||
fields: ["id"],
|
||||
regions: {
|
||||
fields: ["id"],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(links).toHaveLength(1)
|
||||
expect(links).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: region.id,
|
||||
payment_providers: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: "pp_system_default",
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
expect(otherLink).toHaveLength(1)
|
||||
expect(otherLink).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: "pp_system_default",
|
||||
regions: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: region.id,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
74
integration-tests/modules/__tests__/payment/payments.spec.ts
Normal file
74
integration-tests/modules/__tests__/payment/payments.spec.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { ModuleRegistrationName, Modules } from "@medusajs/modules-sdk"
|
||||
import { IRegionModuleService } from "@medusajs/types"
|
||||
import path from "path"
|
||||
import { startBootstrapApp } from "../../../environment-helpers/bootstrap-app"
|
||||
import { useApi } from "../../../environment-helpers/use-api"
|
||||
import { getContainer } from "../../../environment-helpers/use-container"
|
||||
import { initDb, useDb } from "../../../environment-helpers/use-db"
|
||||
|
||||
jest.setTimeout(50000)
|
||||
|
||||
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
||||
|
||||
describe("Payments", () => {
|
||||
let dbConnection
|
||||
let appContainer
|
||||
let shutdownServer
|
||||
let regionService: IRegionModuleService
|
||||
let remoteLink
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||
dbConnection = await initDb({ cwd, env } as any)
|
||||
shutdownServer = await startBootstrapApp({ cwd, env })
|
||||
appContainer = getContainer()
|
||||
regionService = appContainer.resolve(ModuleRegistrationName.REGION)
|
||||
remoteLink = appContainer.resolve("remoteLink")
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
await db.teardown()
|
||||
})
|
||||
|
||||
it("should list payment providers", async () => {
|
||||
const region = await regionService.create({
|
||||
name: "Test Region",
|
||||
currency_code: "usd",
|
||||
})
|
||||
|
||||
const api = useApi() as any
|
||||
let response = await api.get(
|
||||
`/store/regions/${region.id}/payment-providers`
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.payment_providers).toEqual([])
|
||||
|
||||
await remoteLink.create([
|
||||
{
|
||||
[Modules.REGION]: {
|
||||
region_id: region.id,
|
||||
},
|
||||
[Modules.PAYMENT]: {
|
||||
payment_provider_id: "pp_system_default",
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
response = await api.get(`/store/regions/${region.id}/payment-providers`)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.payment_providers).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "pp_system_default",
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -10,3 +10,5 @@ export * from "./product-shipping-profile"
|
||||
export * from "./product-variant-inventory-item"
|
||||
export * from "./product-variant-price-set"
|
||||
export * from "./publishable-api-key-sales-channel"
|
||||
export * from "./region-payment-provider"
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { LINKS } from "../links"
|
||||
|
||||
export const RegionPaymentProvider: ModuleJoinerConfig = {
|
||||
serviceName: LINKS.RegionPaymentProvider,
|
||||
isLink: true,
|
||||
databaseConfig: {
|
||||
tableName: "region_payment_provider",
|
||||
idPrefix: "regpp",
|
||||
},
|
||||
alias: [
|
||||
{
|
||||
name: ["region_payment_provider", "region_payment_providers"],
|
||||
args: {
|
||||
entity: "LinkRegionPaymentProvider",
|
||||
},
|
||||
},
|
||||
],
|
||||
primaryKeys: ["id", "region_id", "payment_provider_id"],
|
||||
relationships: [
|
||||
{
|
||||
serviceName: Modules.REGION,
|
||||
primaryKey: "id",
|
||||
foreignKey: "region_id",
|
||||
alias: "region",
|
||||
},
|
||||
{
|
||||
serviceName: Modules.PAYMENT,
|
||||
primaryKey: "id",
|
||||
foreignKey: "payment_provider_id",
|
||||
alias: "payment_provider",
|
||||
args: { methodSuffix: "PaymentProviders" },
|
||||
},
|
||||
],
|
||||
extends: [
|
||||
{
|
||||
serviceName: Modules.REGION,
|
||||
fieldAlias: {
|
||||
payment_providers: "payment_provider_link.payment_provider",
|
||||
},
|
||||
relationship: {
|
||||
serviceName: LINKS.RegionPaymentProvider,
|
||||
primaryKey: "region_id",
|
||||
foreignKey: "id",
|
||||
alias: "payment_provider_link",
|
||||
isList: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
serviceName: Modules.PAYMENT,
|
||||
fieldAlias: {
|
||||
regions: "region_link.region",
|
||||
},
|
||||
relationship: {
|
||||
serviceName: LINKS.RegionPaymentProvider,
|
||||
primaryKey: "payment_provider_id",
|
||||
foreignKey: "id",
|
||||
alias: "region_link",
|
||||
isList: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -20,6 +20,12 @@ export const LINKS = {
|
||||
Modules.PAYMENT,
|
||||
"payment_collection_id"
|
||||
),
|
||||
RegionPaymentProvider: composeLinkName(
|
||||
Modules.REGION,
|
||||
"region_id",
|
||||
Modules.PAYMENT,
|
||||
"payment_provider_id"
|
||||
),
|
||||
CartPromotion: composeLinkName(
|
||||
Modules.CART,
|
||||
"cart_id",
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { MiddlewaresConfig } from "../loaders/helpers/routing/types"
|
||||
import { adminApiKeyRoutesMiddlewares } from "./admin/api-keys/middlewares"
|
||||
import { adminCampaignRoutesMiddlewares } from "./admin/campaigns/middlewares"
|
||||
import { adminCurrencyRoutesMiddlewares } from "./admin/currencies/middlewares"
|
||||
import { adminCustomerGroupRoutesMiddlewares } from "./admin/customer-groups/middlewares"
|
||||
import { adminCustomerRoutesMiddlewares } from "./admin/customers/middlewares"
|
||||
import { adminInviteRoutesMiddlewares } from "./admin/invites/middlewares"
|
||||
import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares"
|
||||
import { adminRegionRoutesMiddlewares } from "./admin/regions/middlewares"
|
||||
import { adminTaxRegionRoutesMiddlewares } from "./admin/tax-regions/middlewares"
|
||||
import { adminStoreRoutesMiddlewares } from "./admin/stores/middlewares"
|
||||
import { adminTaxRateRoutesMiddlewares } from "./admin/tax-rates/middlewares"
|
||||
import { adminTaxRegionRoutesMiddlewares } from "./admin/tax-regions/middlewares"
|
||||
import { adminUserRoutesMiddlewares } from "./admin/users/middlewares"
|
||||
import { adminWorkflowsExecutionsMiddlewares } from "./admin/workflows-executions/middlewares"
|
||||
import { authRoutesMiddlewares } from "./auth/middlewares"
|
||||
import { hooksRoutesMiddlewares } from "./hooks/middlewares"
|
||||
import { storeCartRoutesMiddlewares } from "./store/carts/middlewares"
|
||||
import { storeCurrencyRoutesMiddlewares } from "./store/currencies/middlewares"
|
||||
import { storeCustomerRoutesMiddlewares } from "./store/customers/middlewares"
|
||||
import { storeRegionRoutesMiddlewares } from "./store/regions/middlewares"
|
||||
import { hooksRoutesMiddlewares } from "./hooks/middlewares"
|
||||
import { adminCurrencyRoutesMiddlewares } from "./admin/currencies/middlewares"
|
||||
import { storeCurrencyRoutesMiddlewares } from "./store/currencies/middlewares"
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
routes: [
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
|
||||
|
||||
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||
const remoteQuery = req.scope.resolve("remoteQuery")
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "regions",
|
||||
fields: ["payment_providers.id", "payment_providers.is_enabled"],
|
||||
variables: {
|
||||
id: req.params.id,
|
||||
},
|
||||
})
|
||||
|
||||
const [region] = await remoteQuery(queryObject)
|
||||
|
||||
res.status(200).json({
|
||||
payment_providers: region.payment_providers.filter((pp) => pp.is_enabled),
|
||||
})
|
||||
}
|
||||
@@ -24,4 +24,9 @@ export const storeRegionRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/store/regions/:id/payment-providers",
|
||||
middlewares: [],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { MapToConfig } from "@medusajs/utils"
|
||||
import { Payment, PaymentCollection } from "@models"
|
||||
import { Payment, PaymentCollection, PaymentProvider } from "@models"
|
||||
|
||||
export const LinkableKeys = {
|
||||
payment_id: Payment.name,
|
||||
payment_collection_id: PaymentCollection.name,
|
||||
payment_provider_id: PaymentProvider.name,
|
||||
}
|
||||
|
||||
const entityLinkableKeysMap: MapToConfig = {}
|
||||
@@ -36,5 +37,12 @@ export const joinerConfig: ModuleJoinerConfig = {
|
||||
entity: PaymentCollection.name,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["payment_provider", "payment_providers"],
|
||||
args: {
|
||||
entity: PaymentProvider.name,
|
||||
methodSuffix: "PaymentProviders",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
import { IPaymentModuleService, LoaderOptions } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
CreatePaymentProviderDTO,
|
||||
LoaderOptions
|
||||
} from "@medusajs/types"
|
||||
|
||||
export default async ({ container }: LoaderOptions): Promise<void> => {
|
||||
const paymentModuleService: IPaymentModuleService = container.resolve(
|
||||
ModuleRegistrationName.PAYMENT
|
||||
)
|
||||
const providersToLoad = container.resolve("payment_providers")
|
||||
const paymentProviderService = container.resolve("paymentProviderService")
|
||||
|
||||
await paymentModuleService.createProvidersOnLoad()
|
||||
const providers = await paymentProviderService.list({
|
||||
id: providersToLoad,
|
||||
})
|
||||
|
||||
const loadedProvidersMap = new Map(providers.map((p) => [p.id, p]))
|
||||
|
||||
const providersToCreate: CreatePaymentProviderDTO[] = []
|
||||
for (const id of providersToLoad) {
|
||||
if (loadedProvidersMap.has(id)) {
|
||||
continue
|
||||
}
|
||||
|
||||
providersToCreate.push({ id })
|
||||
}
|
||||
|
||||
await paymentProviderService.create(providersToCreate)
|
||||
}
|
||||
|
||||
@@ -3,16 +3,18 @@ import {
|
||||
Context,
|
||||
CreateCaptureDTO,
|
||||
CreatePaymentCollectionDTO,
|
||||
CreatePaymentProviderDTO,
|
||||
CreatePaymentSessionDTO,
|
||||
CreateRefundDTO,
|
||||
DAL,
|
||||
FilterablePaymentProviderProps,
|
||||
FindConfig,
|
||||
InternalModuleDeclaration,
|
||||
IPaymentModuleService,
|
||||
ModuleJoinerConfig,
|
||||
ModulesSdkTypes,
|
||||
PaymentCollectionDTO,
|
||||
PaymentDTO,
|
||||
PaymentProviderDTO,
|
||||
PaymentSessionDTO,
|
||||
PaymentSessionStatus,
|
||||
ProviderWebhookPayload,
|
||||
@@ -590,25 +592,23 @@ export default class PaymentModuleService<
|
||||
}
|
||||
}
|
||||
|
||||
async createProvidersOnLoad() {
|
||||
const providersToLoad = this.__container__["payment_providers"]
|
||||
@InjectManager("baseRepository_")
|
||||
async listPaymentProviders(
|
||||
filters: FilterablePaymentProviderProps = {},
|
||||
config: FindConfig<PaymentProviderDTO> = {},
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentProviderDTO[]> {
|
||||
const providers = await this.paymentProviderService_.list(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const providers = await this.paymentProviderService_.list({
|
||||
// @ts-ignore TODO
|
||||
id: providersToLoad,
|
||||
})
|
||||
|
||||
const loadedProvidersMap = new Map(providers.map((p) => [p.id, p]))
|
||||
|
||||
const providersToCreate: CreatePaymentProviderDTO[] = []
|
||||
for (const id of providersToLoad) {
|
||||
if (loadedProvidersMap.has(id)) {
|
||||
continue
|
||||
return await this.baseRepository_.serialize<PaymentProviderDTO[]>(
|
||||
providers,
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
|
||||
providersToCreate.push({ id })
|
||||
}
|
||||
|
||||
await this.paymentProviderService_.create(providersToCreate)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,13 @@ import {
|
||||
CreatePaymentProviderDTO,
|
||||
CreatePaymentProviderSession,
|
||||
DAL,
|
||||
FilterablePaymentProviderProps,
|
||||
FindConfig,
|
||||
InternalModuleDeclaration,
|
||||
IPaymentProvider,
|
||||
PaymentProviderAuthorizeResponse,
|
||||
PaymentProviderDataInput,
|
||||
PaymentProviderDTO,
|
||||
PaymentProviderError,
|
||||
PaymentProviderSessionResponse,
|
||||
PaymentSessionStatus,
|
||||
@@ -19,6 +22,7 @@ import {
|
||||
InjectTransactionManager,
|
||||
isPaymentProviderError,
|
||||
MedusaContext,
|
||||
ModulesSdkUtils,
|
||||
} from "@medusajs/utils"
|
||||
import { PaymentProvider } from "@models"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
@@ -52,9 +56,19 @@ export default class PaymentProviderService {
|
||||
|
||||
@InjectManager("paymentProviderRepository_")
|
||||
async list(
|
||||
filters: FilterablePaymentProviderProps,
|
||||
config: FindConfig<PaymentProviderDTO>,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentProvider[]> {
|
||||
return await this.paymentProviderRepository_.find(undefined, sharedContext)
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<PaymentProvider>(
|
||||
filters,
|
||||
config
|
||||
)
|
||||
|
||||
return await this.paymentProviderRepository_.find(
|
||||
queryOptions,
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
retrieveProvider(providerId: string): IPaymentProvider {
|
||||
|
||||
@@ -434,3 +434,16 @@ export interface PaymentProviderDTO {
|
||||
*/
|
||||
is_enabled: string
|
||||
}
|
||||
|
||||
export interface FilterablePaymentProviderProps
|
||||
extends BaseFilterable<PaymentProviderDTO> {
|
||||
/**
|
||||
* The IDs to filter the payment collection by.
|
||||
*/
|
||||
id?: string | string[]
|
||||
|
||||
/**
|
||||
* Filter by enabled status
|
||||
*/
|
||||
is_enabled?: boolean
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ import { Context } from "../shared-context"
|
||||
import {
|
||||
FilterablePaymentCollectionProps,
|
||||
FilterablePaymentProps,
|
||||
FilterablePaymentProviderProps,
|
||||
FilterablePaymentSessionProps,
|
||||
PaymentCollectionDTO,
|
||||
PaymentDTO,
|
||||
PaymentProviderDTO,
|
||||
PaymentSessionDTO,
|
||||
} from "./common"
|
||||
import {
|
||||
@@ -311,6 +313,12 @@ export interface IPaymentModuleService extends IModuleService {
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentDTO>
|
||||
|
||||
listPaymentSessions(
|
||||
filters?: FilterablePaymentSessionProps,
|
||||
config?: FindConfig<PaymentSessionDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentSessionDTO[]>
|
||||
|
||||
/* ********** PAYMENT ********** */
|
||||
|
||||
/**
|
||||
@@ -388,19 +396,11 @@ export interface IPaymentModuleService extends IModuleService {
|
||||
*/
|
||||
cancelPayment(paymentId: string, sharedContext?: Context): Promise<PaymentDTO>
|
||||
|
||||
listPaymentSessions(
|
||||
filters?: FilterablePaymentSessionProps,
|
||||
config?: FindConfig<PaymentSessionDTO>,
|
||||
listPaymentProviders(
|
||||
filters?: FilterablePaymentProviderProps,
|
||||
config?: FindConfig<PaymentProviderDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentSessionDTO[]>
|
||||
|
||||
/**
|
||||
* This method creates providers on load.
|
||||
*
|
||||
* @example
|
||||
* {example-code}
|
||||
*/
|
||||
createProvidersOnLoad(): Promise<void>
|
||||
): Promise<PaymentProviderDTO[]>
|
||||
|
||||
/* ********** HOOKS ********** */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user