feat: region payment providers management workflows/api (#6864)

This commit is contained in:
Adrien de Peretti
2024-04-04 20:41:34 +02:00
committed by GitHub
parent 12fcb655cd
commit e944a627f0
48 changed files with 1033 additions and 89 deletions
@@ -0,0 +1,216 @@
import { ModuleRegistrationName, RemoteLink } from "@medusajs/modules-sdk"
import { IPaymentModuleService, RemoteQueryFunction } from "@medusajs/types"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
import {
arrayDifference,
ContainerRegistrationKeys,
LINKS,
MedusaError,
Modules,
promiseAll,
remoteQueryObjectFromString,
} from "@medusajs/utils"
export interface SetRegionsPaymentProvidersStepInput {
input: {
id: string
payment_providers?: string[]
}[]
}
interface FilteredSetRegionsPaymentProvidersStepInput {
id: string
payment_providers: string[]
}
type LinkItems = {
[Modules.REGION]: { region_id: string }
[Modules.PAYMENT]: { payment_provider_id: string }
}[]
async function validatePaymentProvidersExists(
paymentService: IPaymentModuleService,
paymentProviderIds: string[]
) {
const paymentProviders = await paymentService.listPaymentProviders({
id: { $in: paymentProviderIds },
is_enabled: true,
})
const retrievedPaymentProviderIds = paymentProviders.map((p) => p.id)
const missingProviders = arrayDifference(
paymentProviderIds,
retrievedPaymentProviderIds
)
if (missingProviders.length) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Payment providers with ids ${missingProviders.join(
", "
)} not found or not enabled`
)
}
}
async function getCurrentRegionPaymentProvidersLinks(
regionIds: string[],
{ remoteQuery }: { remoteQuery: RemoteQueryFunction }
): Promise<
{
[Modules.REGION]: { region_id: string }
[Modules.PAYMENT]: { payment_provider_id: string }
}[]
> {
const query = remoteQueryObjectFromString({
service: LINKS.RegionPaymentProvider,
variables: {
filters: { region_id: regionIds },
take: null,
},
fields: ["region_id", "payment_provider_id"],
})
const regionProviderLinks = (await remoteQuery(query)) as {
region_id: string
payment_provider_id: string
}[]
return regionProviderLinks.map((region) => {
return {
[Modules.REGION]: {
region_id: region.region_id,
},
[Modules.PAYMENT]: {
payment_provider_id: region.payment_provider_id,
},
}
})
}
export const setRegionsPaymentProvidersStepId =
"add-region-payment-providers-step"
export const setRegionsPaymentProvidersStep = createStep(
setRegionsPaymentProvidersStepId,
async (data: SetRegionsPaymentProvidersStepInput, { container }) => {
const dataInputToProcess = data.input.filter((inputData) => {
return inputData.payment_providers?.length
}) as FilteredSetRegionsPaymentProvidersStepInput[]
if (!dataInputToProcess.length) {
return new StepResponse(void 0)
}
const paymentService = container.resolve<IPaymentModuleService>(
ModuleRegistrationName.PAYMENT
)
const remoteLink = container.resolve<RemoteLink>(
ContainerRegistrationKeys.REMOTE_LINK
)
const remoteQuery = container.resolve<RemoteQueryFunction>(
ContainerRegistrationKeys.REMOTE_QUERY
)
const allPaymentProviderIds = dataInputToProcess
.map((inputData) => {
return inputData.payment_providers!
})
.flat()
const uniquePaymentProviderIds = Array.from(
new Set<string>(allPaymentProviderIds)
)
await validatePaymentProvidersExists(
paymentService,
uniquePaymentProviderIds
)
const regionIds = dataInputToProcess.map((inputData) => inputData.id)
const currentExistingLinks = await getCurrentRegionPaymentProvidersLinks(
regionIds,
{ remoteQuery }
)
const linksToRemove: LinkItems = currentExistingLinks
.filter((existingLink) => {
return !dataInputToProcess.some((input) => {
return (
input.id === existingLink[Modules.REGION].region_id &&
input.payment_providers.includes(
existingLink[Modules.PAYMENT].payment_provider_id
)
)
})
})
.map((link) => {
return {
[Modules.REGION]: { region_id: link[Modules.REGION].region_id },
[Modules.PAYMENT]: {
payment_provider_id: link[Modules.PAYMENT].payment_provider_id,
},
}
})
const linksToCreate = dataInputToProcess
.map((inputData) => {
return inputData.payment_providers.map((provider) => {
const alreadyExists = currentExistingLinks.some((link) => {
return (
link[Modules.REGION].region_id === inputData.id &&
link[Modules.PAYMENT].payment_provider_id === provider
)
})
if (alreadyExists) {
return
}
return {
[Modules.REGION]: { region_id: inputData.id },
[Modules.PAYMENT]: { payment_provider_id: provider },
}
})
})
.flat()
.filter((d): d is LinkItems[0] => !!d)
const promises: Promise<unknown[]>[] = []
if (linksToRemove.length) {
promises.push(remoteLink.dismiss(linksToRemove))
}
if (linksToCreate.length) {
promises.push(remoteLink.create(linksToCreate))
}
await promiseAll(promises)
return new StepResponse(void 0, {
linksToCreate: linksToRemove,
linksToRemove: linksToCreate,
})
},
async (rollbackData, { container }) => {
if (!rollbackData) {
return
}
const remoteLink = container.resolve<RemoteLink>(
ContainerRegistrationKeys.REMOTE_LINK
)
const promises: Promise<unknown[]>[] = []
if (rollbackData.linksToRemove.length) {
promises.push(remoteLink.dismiss(rollbackData.linksToRemove))
}
if (rollbackData.linksToCreate.length) {
promises.push(remoteLink.create(rollbackData.linksToCreate))
}
await promiseAll(promises)
}
)
@@ -29,6 +29,10 @@ export const updateRegionsStep = createStep(
relations,
})
if (Object.keys(data.update).length === 0) {
return new StepResponse(prevData, [])
}
const regions = await service.update(data.selector, data.update)
return new StepResponse(regions, prevData)
@@ -48,7 +52,7 @@ export const updateRegionsStep = createStep(
name: r.name,
currency_code: r.currency_code,
metadata: r.metadata,
countries: r.countries.map((c) => c.iso_2),
countries: r.countries?.map((c) => c.iso_2),
}))
)
}
@@ -1,13 +1,57 @@
import { CreateRegionDTO, RegionDTO } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { WorkflowTypes } from "@medusajs/types"
import {
createWorkflow,
transform,
WorkflowData,
} from "@medusajs/workflows-sdk"
import { createRegionsStep } from "../steps"
type WorkflowInput = { regionsData: CreateRegionDTO[] }
import { setRegionsPaymentProvidersStep } from "../steps/set-regions-payment-providers"
export const createRegionsWorkflowId = "create-regions"
export const createRegionsWorkflow = createWorkflow(
createRegionsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<RegionDTO[]> => {
return createRegionsStep(input.regionsData)
(
input: WorkflowData<WorkflowTypes.RegionWorkflow.CreateRegionsWorkflowInput>
): WorkflowData<WorkflowTypes.RegionWorkflow.CreateRegionsWorkflowOutput> => {
const data = transform(input, (data) => {
const regionIndexToPaymentProviders = data.regions.map(
(region, index) => {
return {
region_index: index,
payment_providers: region.payment_providers,
}
}
)
return {
regions: data.regions,
regionIndexToPaymentProviders,
}
})
const regions = createRegionsStep(data.regions)
const normalizedRegionProviderData = transform(
{
regionIndexToPaymentProviders: data.regionIndexToPaymentProviders,
regions,
},
(data) => {
return data.regionIndexToPaymentProviders.map(
({ region_index, payment_providers }) => {
return {
id: data.regions[region_index].id,
payment_providers,
}
}
)
}
)
setRegionsPaymentProvidersStep({
input: normalizedRegionProviderData,
})
return regions
}
)
@@ -1,22 +1,46 @@
import { WorkflowTypes } from "@medusajs/types"
import {
FilterableRegionProps,
RegionDTO,
UpdateRegionDTO,
} from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
createWorkflow,
transform,
WorkflowData,
} from "@medusajs/workflows-sdk"
import { updateRegionsStep } from "../steps"
type UpdateRegionsStepInput = {
selector: FilterableRegionProps
update: UpdateRegionDTO
}
type WorkflowInput = UpdateRegionsStepInput
import { setRegionsPaymentProvidersStep } from "../steps/set-regions-payment-providers"
export const updateRegionsWorkflowId = "update-regions"
export const updateRegionsWorkflow = createWorkflow(
updateRegionsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<RegionDTO[]> => {
return updateRegionsStep(input)
(
input: WorkflowData<WorkflowTypes.RegionWorkflow.UpdateRegionsWorkflowInput>
): WorkflowData<WorkflowTypes.RegionWorkflow.UpdateRegionsWorkflowOutput> => {
const data = transform(input, (data) => {
const { selector, update } = data
const { payment_providers = [], ...rest } = update
return {
selector,
update: rest,
payment_providers,
}
})
const regions = updateRegionsStep(data)
const upsertProvidersNormalizedInput = transform(
{ data, regions },
(data) => {
return data.regions.map((region) => {
return {
id: region.id,
payment_providers: data.data.payment_providers,
}
})
}
)
setRegionsPaymentProvidersStep({
input: upsertProvidersNormalizedInput,
})
return regions
}
)