From 9cedeb182dc19d6127b602fc06e4b8850490e2a9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Frane=20Poli=C4=87?=
<16856471+fPolic@users.noreply.github.com>
Date: Tue, 6 May 2025 19:26:33 +0200
Subject: [PATCH] feat(dashboard, js-sdk, medusa, tax, types): custom tax
providers (#12297)
* wip: setup loaders, add endpoints, module work, types, js sdk
* fix: tax module provider loader
* feat: select provider on region create, fix enpoint middleware registration
* feat: edit form
* fix: rename param
* chore: changeset
* fix: don't default to system provider
* fix: admin fixes, dispalt tax provider
* fix: some tests and types
* fix: remove provider from province regions in test
* fix: more tests, optional provider for sublevel regions, fix few types
* fix: OE test
* feat: edit tax region admin, update tax region core flow changes
* feat: migrate script
* fix: refactor
* chore: use query graph
* feat: provider section
---
.changeset/hungry-cougars-reflect.md | 9 ++
.../__tests__/order-edits/order-edits.spec.ts | 1 +
.../__tests__/product/store/product.spec.ts | 3 +
.../modules/__tests__/fixtures/tax/index.ts | 4 +
.../dashboard-app/routes/get-route.map.tsx | 5 +
.../dashboard/src/hooks/api/tax-regions.tsx | 24 ++++
.../src/i18n/translations/$schema.json | 38 ++++--
.../dashboard/src/i18n/translations/en.json | 8 +-
.../tax-region-card/tax-region-card.tsx | 28 ++++-
.../tax-region-create-form.tsx | 40 +++++++
.../tax-region-detail/tax-region-detail.tsx | 6 +-
.../tax-region-provider-section/index.ts | 1 +
.../tax-region-provider-section.tsx | 28 +++++
.../components/tax-region-edit/index.ts | 1 +
.../tax-region-edit/tax-region-edit-form.tsx | 109 ++++++++++++++++++
.../tax-regions/tax-region-edit/index.ts | 1 +
.../tax-region-edit/tax-region-edit.tsx | 33 ++++++
.../tax-region-detail-section.tsx | 1 +
.../src/tax/steps/update-tax-regions.ts | 3 +-
.../src/tax/workflows/update-tax-regions.ts | 8 +-
packages/core/js-sdk/src/admin/index.ts | 6 +
.../core/js-sdk/src/admin/tax-provider.ts | 51 ++++++++
packages/core/js-sdk/src/admin/tax-rate.ts | 42 +++----
packages/core/js-sdk/src/admin/tax-region.ts | 76 ++++++++----
packages/core/types/src/http/index.ts | 1 +
.../src/http/tax-provider/admin/entities.ts | 10 ++
.../src/http/tax-provider/admin/index.ts | 3 +
.../src/http/tax-provider/admin/queries.ts | 15 +++
.../src/http/tax-provider/admin/responses.ts | 9 ++
.../core/types/src/http/tax-provider/index.ts | 1 +
.../src/http/tax-region/admin/entities.ts | 8 +-
.../src/http/tax-region/admin/payloads.ts | 22 ++++
packages/core/types/src/tax/common.ts | 5 +
packages/core/types/src/tax/mutations.ts | 44 +++++++
packages/core/types/src/tax/service.ts | 20 ++++
.../api/admin/tax-providers/middlewares.ts | 19 +++
.../api/admin/tax-providers/query-config.ts | 11 ++
.../src/api/admin/tax-providers/route.ts | 28 +++++
.../src/api/admin/tax-providers/validators.ts | 22 ++++
.../src/api/admin/tax-regions/[id]/route.ts | 2 +-
.../src/api/admin/tax-regions/validators.ts | 2 +
packages/medusa/src/api/middlewares.ts | 2 +
.../migrate-tax-region-provider.ts | 49 ++++++++
.../utils/setup-tax-structure.ts | 4 +
packages/modules/tax/src/loaders/providers.ts | 63 ++++++++--
packages/modules/tax/src/services/index.ts | 1 +
.../tax/src/services/tax-module-service.ts | 45 +-------
.../modules/tax/src/services/tax-provider.ts | 51 ++++++++
48 files changed, 847 insertions(+), 116 deletions(-)
create mode 100644 .changeset/hungry-cougars-reflect.md
create mode 100644 packages/admin/dashboard/src/routes/tax-regions/tax-region-detail/tax-region-provider-section/index.ts
create mode 100644 packages/admin/dashboard/src/routes/tax-regions/tax-region-detail/tax-region-provider-section/tax-region-provider-section.tsx
create mode 100644 packages/admin/dashboard/src/routes/tax-regions/tax-region-edit/components/tax-region-edit/index.ts
create mode 100644 packages/admin/dashboard/src/routes/tax-regions/tax-region-edit/components/tax-region-edit/tax-region-edit-form.tsx
create mode 100644 packages/admin/dashboard/src/routes/tax-regions/tax-region-edit/index.ts
create mode 100644 packages/admin/dashboard/src/routes/tax-regions/tax-region-edit/tax-region-edit.tsx
create mode 100644 packages/core/js-sdk/src/admin/tax-provider.ts
create mode 100644 packages/core/types/src/http/tax-provider/admin/entities.ts
create mode 100644 packages/core/types/src/http/tax-provider/admin/index.ts
create mode 100644 packages/core/types/src/http/tax-provider/admin/queries.ts
create mode 100644 packages/core/types/src/http/tax-provider/admin/responses.ts
create mode 100644 packages/core/types/src/http/tax-provider/index.ts
create mode 100644 packages/medusa/src/api/admin/tax-providers/middlewares.ts
create mode 100644 packages/medusa/src/api/admin/tax-providers/query-config.ts
create mode 100644 packages/medusa/src/api/admin/tax-providers/route.ts
create mode 100644 packages/medusa/src/api/admin/tax-providers/validators.ts
create mode 100644 packages/medusa/src/migration-scripts/migrate-tax-region-provider.ts
create mode 100644 packages/modules/tax/src/services/tax-provider.ts
diff --git a/.changeset/hungry-cougars-reflect.md b/.changeset/hungry-cougars-reflect.md
new file mode 100644
index 0000000000..d2b783745e
--- /dev/null
+++ b/.changeset/hungry-cougars-reflect.md
@@ -0,0 +1,9 @@
+---
+"@medusajs/dashboard": patch
+"@medusajs/js-sdk": patch
+"@medusajs/tax": patch
+"@medusajs/types": patch
+"@medusajs/medusa": patch
+---
+
+feat(dashboard, js-sdk, medusa, tax, types): custom tax providers
diff --git a/integration-tests/http/__tests__/order-edits/order-edits.spec.ts b/integration-tests/http/__tests__/order-edits/order-edits.spec.ts
index 30e5331ff1..2dc87c5163 100644
--- a/integration-tests/http/__tests__/order-edits/order-edits.spec.ts
+++ b/integration-tests/http/__tests__/order-edits/order-edits.spec.ts
@@ -58,6 +58,7 @@ medusaIntegrationTestRunner({
await api.post(
"/admin/tax-regions",
{
+ provider_id: "tp_system_system",
country_code: "US",
},
adminHeaders
diff --git a/integration-tests/http/__tests__/product/store/product.spec.ts b/integration-tests/http/__tests__/product/store/product.spec.ts
index f0928b6332..8da7af2ef2 100644
--- a/integration-tests/http/__tests__/product/store/product.spec.ts
+++ b/integration-tests/http/__tests__/product/store/product.spec.ts
@@ -2380,6 +2380,7 @@ medusaIntegrationTestRunner({
`/admin/tax-regions`,
{
country_code: "us",
+ provider_id: "tp_system_system",
default_tax_rate: {
code: "default",
rate: 5,
@@ -2393,6 +2394,7 @@ medusaIntegrationTestRunner({
`/admin/tax-regions`,
{
country_code: "it",
+ provider_id: "tp_system_system",
default_tax_rate: {
code: "default",
rate: 10,
@@ -2406,6 +2408,7 @@ medusaIntegrationTestRunner({
`/admin/tax-regions`,
{
country_code: "dk",
+ provider_id: "tp_system_system",
default_tax_rate: {
code: "default",
rate: 20,
diff --git a/integration-tests/modules/__tests__/fixtures/tax/index.ts b/integration-tests/modules/__tests__/fixtures/tax/index.ts
index 802c5fa156..1faaef0bf3 100644
--- a/integration-tests/modules/__tests__/fixtures/tax/index.ts
+++ b/integration-tests/modules/__tests__/fixtures/tax/index.ts
@@ -26,10 +26,12 @@ export const setupTaxStructure = async (service: ITaxModuleService) => {
const [us, dk, de, ca] = await service.createTaxRegions([
{
country_code: "US",
+ provider_id: "tp_system_system",
default_tax_rate: { name: "US Default Rate", rate: 2, code: "US_DEF" },
},
{
country_code: "DK",
+ provider_id: "tp_system_system",
default_tax_rate: {
name: "Denmark Default Rate",
rate: 25,
@@ -38,6 +40,7 @@ export const setupTaxStructure = async (service: ITaxModuleService) => {
},
{
country_code: "DE",
+ provider_id: "tp_system_system",
default_tax_rate: {
code: "DE19",
name: "Germany Default Rate",
@@ -46,6 +49,7 @@ export const setupTaxStructure = async (service: ITaxModuleService) => {
},
{
country_code: "CA",
+ provider_id: "tp_system_system",
default_tax_rate: {
name: "Canada Default Rate",
rate: 5,
diff --git a/packages/admin/dashboard/src/dashboard-app/routes/get-route.map.tsx b/packages/admin/dashboard/src/dashboard-app/routes/get-route.map.tsx
index 32e3daed08..edd9c09b86 100644
--- a/packages/admin/dashboard/src/dashboard-app/routes/get-route.map.tsx
+++ b/packages/admin/dashboard/src/dashboard-app/routes/get-route.map.tsx
@@ -1626,6 +1626,11 @@ export function getRouteMap({
},
],
},
+ {
+ path: "edit",
+ lazy: () =>
+ import("../../routes/tax-regions/tax-region-edit"),
+ },
{
path: "provinces/:province_id",
lazy: async () => {
diff --git a/packages/admin/dashboard/src/hooks/api/tax-regions.tsx b/packages/admin/dashboard/src/hooks/api/tax-regions.tsx
index 74eb7a73df..8a7ceff93c 100644
--- a/packages/admin/dashboard/src/hooks/api/tax-regions.tsx
+++ b/packages/admin/dashboard/src/hooks/api/tax-regions.tsx
@@ -74,6 +74,30 @@ export const useCreateTaxRegion = (
})
}
+export const useUpdateTaxRegion = (
+ id: string,
+ query?: HttpTypes.AdminTaxRegionParams,
+ options?: UseMutationOptions<
+ HttpTypes.AdminTaxRegionResponse,
+ FetchError,
+ HttpTypes.AdminUpdateTaxRegion,
+ QueryKey
+ >
+) => {
+ return useMutation({
+ mutationFn: (payload) => sdk.admin.taxRegion.update(id, payload, query),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: taxRegionsQueryKeys.detail(id),
+ })
+ queryClient.invalidateQueries({ queryKey: taxRegionsQueryKeys.lists() })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
export const useDeleteTaxRegion = (
id: string,
options?: UseMutationOptions<
diff --git a/packages/admin/dashboard/src/i18n/translations/$schema.json b/packages/admin/dashboard/src/i18n/translations/$schema.json
index b2aef80e2b..d6bf8c2075 100644
--- a/packages/admin/dashboard/src/i18n/translations/$schema.json
+++ b/packages/admin/dashboard/src/i18n/translations/$schema.json
@@ -2038,7 +2038,7 @@
"type": "string"
}
},
- "required": ["label", "hint"],
+ "required": ["label", "hint", "placeholder"],
"additionalProperties": false
},
"subtitle": {
@@ -2051,7 +2051,7 @@
"type": "string"
}
},
- "required": ["label"],
+ "required": ["label", "placeholder"],
"additionalProperties": false
},
"handle": {
@@ -2067,7 +2067,7 @@
"type": "string"
}
},
- "required": ["label", "tooltip"],
+ "required": ["label", "tooltip", "placeholder"],
"additionalProperties": false
},
"description": {
@@ -2083,7 +2083,7 @@
"type": "string"
}
},
- "required": ["label", "hint"],
+ "required": ["label", "hint", "placeholder"],
"additionalProperties": false
},
"discountable": {
@@ -6479,6 +6479,16 @@
"required": ["header", "hint", "errors", "successToast"],
"additionalProperties": false
},
+ "edit": {
+ "type": "object",
+ "properties": {
+ "successToast": {
+ "type": "string"
+ }
+ },
+ "required": ["successToast"],
+ "additionalProperties": false
+ },
"province": {
"type": "object",
"properties": {
@@ -6502,6 +6512,16 @@
"required": ["header", "create"],
"additionalProperties": false
},
+ "provider": {
+ "type": "object",
+ "properties": {
+ "header": {
+ "type": "string"
+ }
+ },
+ "required": ["header"],
+ "additionalProperties": false
+ },
"state": {
"type": "object",
"properties": {
@@ -6910,6 +6930,9 @@
"taxCode": {
"type": "string"
},
+ "taxProvider": {
+ "type": "string"
+ },
"targets": {
"type": "object",
"properties": {
@@ -7240,6 +7263,7 @@
"defaultTaxRate",
"taxRate",
"taxCode",
+ "taxProvider",
"targets",
"sublevels",
"noDefaultRate"
@@ -7252,7 +7276,9 @@
"list",
"delete",
"create",
+ "edit",
"province",
+ "provider",
"state",
"stateOrTerritory",
"county",
@@ -9192,9 +9218,6 @@
"deleteRateDescription": {
"type": "string"
},
- "editTaxRate": {
- "type": "string"
- },
"editRateAction": {
"type": "string"
},
@@ -9252,7 +9275,6 @@
"createTaxRate",
"createTaxRateHint",
"deleteRateDescription",
- "editTaxRate",
"editRateAction",
"editOverridesAction",
"editOverridesTitle",
diff --git a/packages/admin/dashboard/src/i18n/translations/en.json b/packages/admin/dashboard/src/i18n/translations/en.json
index 077a66045b..d5d6d06c94 100644
--- a/packages/admin/dashboard/src/i18n/translations/en.json
+++ b/packages/admin/dashboard/src/i18n/translations/en.json
@@ -1723,6 +1723,9 @@
},
"successToast": "The tax region was successfully created."
},
+ "edit": {
+ "successToast": "The tax region was successfully updated."
+ },
"province": {
"header": "Provinces",
"create": {
@@ -1730,6 +1733,9 @@
"hint": "Create a new tax region to define tax rates for a specific province."
}
},
+ "provider": {
+ "header": "Tax Provider"
+ },
"state": {
"header": "States",
"create": {
@@ -1855,6 +1861,7 @@
},
"taxRate": "Tax rate",
"taxCode": "Tax code",
+ "taxProvider": "Tax provider",
"targets": {
"label": "Targets",
"hint": "Select the targets that this tax rate will apply to.",
@@ -2471,7 +2478,6 @@
"createTaxRate": "Create Tax Rate",
"createTaxRateHint": "Create a new tax rate for the region.",
"deleteRateDescription": "You are about to delete the tax rate {{name}}. This action cannot be undone.",
- "editTaxRate": "Edit Tax Rate",
"editRateAction": "Edit rate",
"editOverridesAction": "Edit overrides",
"editOverridesTitle": "Edit Tax Rate Overrides",
diff --git a/packages/admin/dashboard/src/routes/tax-regions/common/components/tax-region-card/tax-region-card.tsx b/packages/admin/dashboard/src/routes/tax-regions/common/components/tax-region-card/tax-region-card.tsx
index b4041e7edf..5546518061 100644
--- a/packages/admin/dashboard/src/routes/tax-regions/common/components/tax-region-card/tax-region-card.tsx
+++ b/packages/admin/dashboard/src/routes/tax-regions/common/components/tax-region-card/tax-region-card.tsx
@@ -2,11 +2,20 @@ import { HttpTypes } from "@medusajs/types"
import { Heading, Text, Tooltip, clx } from "@medusajs/ui"
import ReactCountryFlag from "react-country-flag"
-import { ExclamationCircle, MapPin, Plus, Trash } from "@medusajs/icons"
+import {
+ ExclamationCircle,
+ MapPin,
+ Plus,
+ Trash,
+ PencilSquare,
+} from "@medusajs/icons"
import { ComponentPropsWithoutRef, ReactNode } from "react"
import { useTranslation } from "react-i18next"
import { Link } from "react-router-dom"
-import { ActionMenu } from "../../../../../components/common/action-menu"
+import {
+ Action,
+ ActionMenu,
+} from "../../../../../components/common/action-menu"
import { IconAvatar } from "../../../../../components/common/icon-avatar"
import { getCountryByIso2 } from "../../../../../lib/data/countries"
import {
@@ -107,7 +116,9 @@ export const TaxRegionCard = ({
{name}
) : (
- {name}
+
+ {name}
+
)}
@@ -164,7 +175,9 @@ const TaxRegionCardActions = ({
}) => {
const { t } = useTranslation()
- const to = taxRegion.parent_id
+ const hasParent = !!taxRegion.parent_id
+
+ const to = hasParent
? `/settings/tax-regions/${taxRegion.parent_id}`
: undefined
const handleDelete = useDeleteTaxRegionAction({ taxRegion, to })
@@ -187,12 +200,17 @@ const TaxRegionCardActions = ({
: []),
{
actions: [
+ !hasParent && {
+ icon: ,
+ label: t("actions.edit"),
+ to: `/settings/tax-regions/${taxRegion.id}/edit`,
+ },
{
icon: ,
label: t("actions.delete"),
onClick: handleDelete,
},
- ],
+ ].filter(Boolean) as unknown as Action[],
},
]}
/>
diff --git a/packages/admin/dashboard/src/routes/tax-regions/tax-region-create/components/tax-region-create-form/tax-region-create-form.tsx b/packages/admin/dashboard/src/routes/tax-regions/tax-region-create/components/tax-region-create-form/tax-region-create-form.tsx
index c3f41b5001..22ef01a64e 100644
--- a/packages/admin/dashboard/src/routes/tax-regions/tax-region-create/components/tax-region-create-form/tax-region-create-form.tsx
+++ b/packages/admin/dashboard/src/routes/tax-regions/tax-region-create/components/tax-region-create-form/tax-region-create-form.tsx
@@ -14,6 +14,10 @@ import {
} from "../../../../../components/modals"
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
import { useCreateTaxRegion } from "../../../../../hooks/api"
+import { useComboboxData } from "../../../../../hooks/use-combobox-data"
+import { Combobox } from "../../../../../components/inputs/combobox"
+import { formatProvider } from "../../../../../lib/format-provider"
+import { sdk } from "../../../../../lib/client"
type TaxRegionCreateFormProps = {
parentId?: string
@@ -27,12 +31,23 @@ const TaxRegionCreateSchema = z.object({
value: z.string().optional(),
}),
country_code: z.string().min(1),
+ provider_id: z.string(),
})
export const TaxRegionCreateForm = ({ parentId }: TaxRegionCreateFormProps) => {
const { t } = useTranslation()
const { handleSuccess } = useRouteModal()
+ const taxProviders = useComboboxData({
+ queryKey: ["tax_providers"],
+ queryFn: (params) => sdk.admin.taxProvider.list(params),
+ getOptions: (data) =>
+ data.tax_providers.map((provider) => ({
+ label: formatProvider(provider.id),
+ value: provider.id,
+ })),
+ })
+
const form = useForm>({
defaultValues: {
name: "",
@@ -41,6 +56,7 @@ export const TaxRegionCreateForm = ({ parentId }: TaxRegionCreateFormProps) => {
},
code: "",
country_code: "",
+ provider_id: "",
},
resolver: zodResolver(TaxRegionCreateSchema),
})
@@ -64,6 +80,7 @@ export const TaxRegionCreateForm = ({ parentId }: TaxRegionCreateFormProps) => {
country_code: values.country_code,
parent_id: parentId,
default_tax_rate: defaultRate,
+ provider_id: values.provider_id,
},
{
onSuccess: ({ tax_region }) => {
@@ -192,6 +209,29 @@ export const TaxRegionCreateForm = ({ parentId }: TaxRegionCreateFormProps) => {
)
}}
/>
+ (
+
+
+ {t("taxRegions.fields.taxProvider")}
+
+
+
+
+
+
+ )}
+ />
diff --git a/packages/admin/dashboard/src/routes/tax-regions/tax-region-detail/tax-region-detail.tsx b/packages/admin/dashboard/src/routes/tax-regions/tax-region-detail/tax-region-detail.tsx
index 9809b3cee0..8dc8fde965 100644
--- a/packages/admin/dashboard/src/routes/tax-regions/tax-region-detail/tax-region-detail.tsx
+++ b/packages/admin/dashboard/src/routes/tax-regions/tax-region-detail/tax-region-detail.tsx
@@ -1,15 +1,16 @@
import { useLoaderData, useParams } from "react-router-dom"
+import { useState } from "react"
import { SingleColumnPage } from "../../../components/layout/pages"
import { useTaxRegion } from "../../../hooks/api/tax-regions"
import { TaxRegionDetailSection } from "./components/tax-region-detail-section"
import { TaxRegionProvinceSection } from "./components/tax-region-province-section"
-import { useState } from "react"
import { SingleColumnPageSkeleton } from "../../../components/common/skeleton"
import { useExtension } from "../../../providers/extension-provider"
import { TaxRegionOverrideSection } from "./components/tax-region-override-section"
import { TaxRegionSublevelAlert } from "./components/tax-region-sublevel-alert"
+import { TaxRegionProviderSection } from "./tax-region-provider-section"
import { taxRegionLoader } from "./loader"
export const TaxRegionDetail = () => {
@@ -41,7 +42,7 @@ export const TaxRegionDetail = () => {
enable when tax region update is added to the API
+ // showMetadata // TOOD -> enable tax region metadata
widgets={{
after: getWidgets("tax.details.after"),
before: getWidgets("tax.details.before"),
@@ -58,6 +59,7 @@ export const TaxRegionDetail = () => {
showSublevelRegions={showSublevelRegions}
/>
+
)
}
diff --git a/packages/admin/dashboard/src/routes/tax-regions/tax-region-detail/tax-region-provider-section/index.ts b/packages/admin/dashboard/src/routes/tax-regions/tax-region-detail/tax-region-provider-section/index.ts
new file mode 100644
index 0000000000..4e6b80b3ef
--- /dev/null
+++ b/packages/admin/dashboard/src/routes/tax-regions/tax-region-detail/tax-region-provider-section/index.ts
@@ -0,0 +1 @@
+export * from "./tax-region-provider-section"
diff --git a/packages/admin/dashboard/src/routes/tax-regions/tax-region-detail/tax-region-provider-section/tax-region-provider-section.tsx b/packages/admin/dashboard/src/routes/tax-regions/tax-region-detail/tax-region-provider-section/tax-region-provider-section.tsx
new file mode 100644
index 0000000000..44340e1f5b
--- /dev/null
+++ b/packages/admin/dashboard/src/routes/tax-regions/tax-region-detail/tax-region-provider-section/tax-region-provider-section.tsx
@@ -0,0 +1,28 @@
+import { useTranslation } from "react-i18next"
+import { HttpTypes } from "@medusajs/types"
+import { Container, Heading } from "@medusajs/ui"
+
+import { formatProvider } from "../../../../lib/format-provider"
+
+export function TaxRegionProviderSection({
+ taxRegion,
+}: {
+ taxRegion: HttpTypes.AdminTaxRegion
+}) {
+ const { t } = useTranslation()
+
+ return (
+
+
+ {t("taxRegions.provider.header")}
+
+
+ {taxRegion.provider_id && (
+
+ {formatProvider(taxRegion.provider_id!)}
+
+ )}
+
+
+ )
+}
diff --git a/packages/admin/dashboard/src/routes/tax-regions/tax-region-edit/components/tax-region-edit/index.ts b/packages/admin/dashboard/src/routes/tax-regions/tax-region-edit/components/tax-region-edit/index.ts
new file mode 100644
index 0000000000..6882e60118
--- /dev/null
+++ b/packages/admin/dashboard/src/routes/tax-regions/tax-region-edit/components/tax-region-edit/index.ts
@@ -0,0 +1 @@
+export * from "./tax-region-edit-form"
diff --git a/packages/admin/dashboard/src/routes/tax-regions/tax-region-edit/components/tax-region-edit/tax-region-edit-form.tsx b/packages/admin/dashboard/src/routes/tax-regions/tax-region-edit/components/tax-region-edit/tax-region-edit-form.tsx
new file mode 100644
index 0000000000..f0929322d8
--- /dev/null
+++ b/packages/admin/dashboard/src/routes/tax-regions/tax-region-edit/components/tax-region-edit/tax-region-edit-form.tsx
@@ -0,0 +1,109 @@
+import { zodResolver } from "@hookform/resolvers/zod"
+import { HttpTypes } from "@medusajs/types"
+import { Button, toast } from "@medusajs/ui"
+import { useForm } from "react-hook-form"
+import { useTranslation } from "react-i18next"
+import { z } from "zod"
+
+import { Form } from "../../../../../components/common/form"
+import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
+import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
+import { useComboboxData } from "../../../../../hooks/use-combobox-data"
+import { sdk } from "../../../../../lib/client"
+import { Combobox } from "../../../../../components/inputs/combobox"
+import { formatProvider } from "../../../../../lib/format-provider"
+import { useUpdateTaxRegion } from "../../../../../hooks/api"
+
+type TaxRegionEditFormProps = {
+ taxRegion: HttpTypes.AdminTaxRegion
+}
+
+const TaxRegionEditSchema = z.object({
+ provider_id: z.string().min(1),
+})
+
+export const TaxRegionEditForm = ({ taxRegion }: TaxRegionEditFormProps) => {
+ const { t } = useTranslation()
+ const { handleSuccess } = useRouteModal()
+
+ const taxProviders = useComboboxData({
+ queryKey: ["tax_providers"],
+ queryFn: (params) => sdk.admin.taxProvider.list(params),
+ getOptions: (data) =>
+ data.tax_providers.map((provider) => ({
+ label: formatProvider(provider.id),
+ value: provider.id,
+ })),
+ })
+
+ const form = useForm>({
+ defaultValues: {
+ provider_id: taxRegion.provider_id,
+ },
+ resolver: zodResolver(TaxRegionEditSchema),
+ })
+
+ const { mutateAsync, isPending } = useUpdateTaxRegion(taxRegion.id)
+
+ const handleSubmit = form.handleSubmit(async (values) => {
+ await mutateAsync(
+ {
+ provider_id: values.provider_id,
+ },
+ {
+ onSuccess: () => {
+ toast.success(t("taxRegions.edit.successToast"))
+ handleSuccess()
+ },
+ onError: (error) => {
+ toast.error(error.message)
+ },
+ }
+ )
+ })
+
+ return (
+
+
+
+
+
(
+
+ {t("taxRegions.fields.taxProvider")}
+
+
+
+
+
+ )}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/packages/admin/dashboard/src/routes/tax-regions/tax-region-edit/index.ts b/packages/admin/dashboard/src/routes/tax-regions/tax-region-edit/index.ts
new file mode 100644
index 0000000000..01a9ee8fdb
--- /dev/null
+++ b/packages/admin/dashboard/src/routes/tax-regions/tax-region-edit/index.ts
@@ -0,0 +1 @@
+export { TaxRegionEdit as Component } from "./tax-region-edit"
diff --git a/packages/admin/dashboard/src/routes/tax-regions/tax-region-edit/tax-region-edit.tsx b/packages/admin/dashboard/src/routes/tax-regions/tax-region-edit/tax-region-edit.tsx
new file mode 100644
index 0000000000..03cbf32721
--- /dev/null
+++ b/packages/admin/dashboard/src/routes/tax-regions/tax-region-edit/tax-region-edit.tsx
@@ -0,0 +1,33 @@
+import { Heading } from "@medusajs/ui"
+import { useTranslation } from "react-i18next"
+import { useParams } from "react-router-dom"
+import { RouteDrawer } from "../../../components/modals"
+import { TaxRegionEditForm } from "./components/tax-region-edit"
+import { useTaxRegion } from "../../../hooks/api"
+
+export const TaxRegionEdit = () => {
+ const { t } = useTranslation()
+ const { id } = useParams()
+
+ const { tax_region, isPending, isError, error } = useTaxRegion(id!)
+
+ const ready = !isPending && !!tax_region
+
+ if (isError) {
+ throw error
+ }
+
+ return (
+
+
+
+ {t("taxRegions.taxRates.edit.header")}
+
+
+ {t("taxRegions.taxRates.edit.hint")}
+
+
+ {ready && }
+
+ )
+}
diff --git a/packages/admin/dashboard/src/routes/tax-regions/tax-region-province-detail/components/tax-region-province-detail-section/tax-region-detail-section.tsx b/packages/admin/dashboard/src/routes/tax-regions/tax-region-province-detail/components/tax-region-province-detail-section/tax-region-detail-section.tsx
index 84a2eb640c..3687132b50 100644
--- a/packages/admin/dashboard/src/routes/tax-regions/tax-region-province-detail/components/tax-region-province-detail-section/tax-region-detail-section.tsx
+++ b/packages/admin/dashboard/src/routes/tax-regions/tax-region-province-detail/components/tax-region-province-detail-section/tax-region-detail-section.tsx
@@ -33,6 +33,7 @@ export const TaxRegionProvinceDetailSection = ({
)
}
/>
+
{defaultRates.map((rate) => {
return
})}
diff --git a/packages/core/core-flows/src/tax/steps/update-tax-regions.ts b/packages/core/core-flows/src/tax/steps/update-tax-regions.ts
index ba56580ca6..aa8c20cf4a 100644
--- a/packages/core/core-flows/src/tax/steps/update-tax-regions.ts
+++ b/packages/core/core-flows/src/tax/steps/update-tax-regions.ts
@@ -17,7 +17,7 @@ export type UpdateTaxRegionsStepInput = UpdateTaxRegionDTO[]
export const updateTaxRegionsStepId = "update-tax-regions"
/**
* This step updates tax regions.
- *
+ *
* @example
* const data = updateTaxRegionsStep([
* {
@@ -45,6 +45,7 @@ export const updateTaxRegionsStep = createStep(
id: d.id,
province_code: d.province_code,
metadata: d.metadata,
+ provider_id: d.provider_id,
}))
)
diff --git a/packages/core/core-flows/src/tax/workflows/update-tax-regions.ts b/packages/core/core-flows/src/tax/workflows/update-tax-regions.ts
index 26a0c26239..a234e35184 100644
--- a/packages/core/core-flows/src/tax/workflows/update-tax-regions.ts
+++ b/packages/core/core-flows/src/tax/workflows/update-tax-regions.ts
@@ -20,10 +20,10 @@ export const updateTaxRegionsWorkflowId = "update-tax-regions"
/**
* This workflow updates one or more tax regions. It's used by the
* [Update Tax Regions Admin API Route](https://docs.medusajs.com/api/admin#tax-regions_posttaxregionsid).
- *
+ *
* You can use this workflow within your own customizations or custom workflows, allowing you
* to update tax regions in your custom flows.
- *
+ *
* @example
* const { result } = await updateTaxRegionsWorkflow(container)
* .run({
@@ -34,9 +34,9 @@ export const updateTaxRegionsWorkflowId = "update-tax-regions"
* }
* ]
* })
- *
+ *
* @summary
- *
+ *
* Update one or more tax regions.
*/
export const updateTaxRegionsWorkflow = createWorkflow(
diff --git a/packages/core/js-sdk/src/admin/index.ts b/packages/core/js-sdk/src/admin/index.ts
index d734bbe241..2c573ecfb8 100644
--- a/packages/core/js-sdk/src/admin/index.ts
+++ b/packages/core/js-sdk/src/admin/index.ts
@@ -37,6 +37,7 @@ import { ShippingOption } from "./shipping-option"
import { ShippingProfile } from "./shipping-profile"
import { StockLocation } from "./stock-location"
import { Store } from "./store"
+import { TaxProvider } from "./tax-provider"
import { TaxRate } from "./tax-rate"
import { TaxRegion } from "./tax-region"
import { Upload } from "./upload"
@@ -208,6 +209,10 @@ export class Admin {
* @tags promotion
*/
public promotion: Promotion
+ /**
+ * @tags tax
+ */
+ public taxProvider: TaxProvider
/**
* @tags promotion
*/
@@ -261,5 +266,6 @@ export class Admin {
this.promotion = new Promotion(client)
this.campaign = new Campaign(client)
this.plugin = new Plugin(client)
+ this.taxProvider = new TaxProvider(client)
}
}
diff --git a/packages/core/js-sdk/src/admin/tax-provider.ts b/packages/core/js-sdk/src/admin/tax-provider.ts
new file mode 100644
index 0000000000..5a989f78cf
--- /dev/null
+++ b/packages/core/js-sdk/src/admin/tax-provider.ts
@@ -0,0 +1,51 @@
+import { HttpTypes } from "@medusajs/types"
+import { Client } from "../client"
+import { ClientHeaders } from "../types"
+
+const taxProviderUrl = "/admin/tax-providers"
+
+export class TaxProvider {
+ /**
+ * @ignore
+ */
+ private client: Client
+ /**
+ * @ignore
+ */
+ constructor(client: Client) {
+ this.client = client
+ }
+
+ /**
+ * This method retrieves a list of tax providers. It sends a request to the
+ * [List Tax Providers](https://docs.medusajs.com/api/admin#tax-providers_gettaxproviders)
+ * API route.
+ *
+ * @param query - Filters and pagination configurations.
+ * @param headers - Headers to pass in the request.
+ * @returns The list of tax providers.
+ *
+ * @example
+ * To retrieve the list of tax providers:
+ *
+ * ```ts
+ * sdk.admin.taxProvider.list()
+ * .then(({ tax_providers, count, limit, offset }) => {
+ * console.log(tax_providers)
+ * })
+ * ```
+ */
+ async list(
+ query?: HttpTypes.AdminGetTaxProvidersParams,
+ headers?: ClientHeaders
+ ) {
+ return await this.client.fetch(
+ taxProviderUrl,
+ {
+ method: "GET",
+ headers,
+ query,
+ }
+ )
+ }
+}
diff --git a/packages/core/js-sdk/src/admin/tax-rate.ts b/packages/core/js-sdk/src/admin/tax-rate.ts
index 0ce2f41f9c..65538f0507 100644
--- a/packages/core/js-sdk/src/admin/tax-rate.ts
+++ b/packages/core/js-sdk/src/admin/tax-rate.ts
@@ -20,12 +20,12 @@ export class TaxRate {
* This method creates a tax rate. It sends a request to the
* [Create Tax Rate](https://docs.medusajs.com/api/admin#tax-rates_posttaxrates)
* API route.
- *
+ *
* @param body - The details of the tax rate to create.
* @param query - Configure the fields and relations to retrieve in the tax rate.
* @param headers - Headers to pass in the request.
* @returns The tax rate's details.
- *
+ *
* @example
* sdk.admin.taxRate.create({
* name: "VAT",
@@ -54,13 +54,13 @@ export class TaxRate {
* This method updates a tax rate. It sends a request to the
* [Update Tax Rate](https://docs.medusajs.com/api/admin#tax-rates_posttaxratesid)
* API route.
- *
+ *
* @param id - The ID of the tax rate to update.
* @param body - The details of the tax rate to update.
* @param query - Configure the fields and relations to retrieve in the tax rate.
* @param headers - Headers to pass in the request.
* @returns The tax rate's details.
- *
+ *
* @example
* sdk.admin.taxRate.update("txrat_123", {
* name: "VAT",
@@ -91,11 +91,11 @@ export class TaxRate {
* This method deletes a tax rate. It sends a request to the
* [Delete Tax Rate](https://docs.medusajs.com/api/admin#tax-rates_deletetaxratesid)
* API route.
- *
+ *
* @param id - The ID of the tax rate to delete.
* @param headers - Headers to pass in the request.
* @returns The deletion's details.
- *
+ *
* @example
* sdk.admin.taxRate.delete("txrat_123")
* .then(({ deleted }) => {
@@ -116,24 +116,24 @@ export class TaxRate {
* This method retrieves a tax rate. It sends a request to the
* [Get Tax Rate](https://docs.medusajs.com/api/admin#tax-rates_gettaxratesid)
* API route.
- *
+ *
* @param id - The ID of the tax rate to retrieve.
* @param query - Configure the fields and relations to retrieve in the tax rate.
* @param headers - Headers to pass in the request.
* @returns The tax rate's details.
- *
+ *
* @example
* To retrieve a tax rate by its ID:
- *
+ *
* ```ts
* sdk.admin.taxRate.retrieve("txrat_123")
* .then(({ tax_rate }) => {
* console.log(tax_rate)
* })
* ```
- *
+ *
* To specify the fields and relations to retrieve:
- *
+ *
* ```ts
* sdk.admin.taxRate.retrieve("txrat_123", {
* fields: "id,*tax_region"
@@ -142,7 +142,7 @@ export class TaxRate {
* console.log(tax_rate)
* })
* ```
- *
+ *
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/admin#select-fields-and-relations).
*/
async retrieve(
@@ -164,25 +164,25 @@ export class TaxRate {
* This method retrieves a list of tax rates. It sends a request to the
* [List Tax Rates](https://docs.medusajs.com/api/admin#tax-rates_gettaxrates)
* API route.
- *
+ *
* @param query - Filters and pagination configurations.
* @param headers - Headers to pass in the request.
* @returns The list of tax rates.
- *
+ *
* @example
* To retrieve the list of tax rates:
- *
+ *
* ```ts
* sdk.admin.taxRate.list()
* .then(({ tax_rates, count, limit, offset }) => {
* console.log(tax_rates)
* })
* ```
- *
+ *
* To configure the pagination, pass the `limit` and `offset` query parameters.
- *
+ *
* For example, to retrieve only 10 items and skip 10 items:
- *
+ *
* ```ts
* sdk.admin.taxRate.list({
* limit: 10,
@@ -192,10 +192,10 @@ export class TaxRate {
* console.log(tax_rates)
* })
* ```
- *
+ *
* Using the `fields` query parameter, you can specify the fields and relations to retrieve
* in each tax rate:
- *
+ *
* ```ts
* sdk.admin.taxRate.list({
* fields: "id,*tax_region"
@@ -204,7 +204,7 @@ export class TaxRate {
* console.log(tax_rates)
* })
* ```
- *
+ *
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/admin#select-fields-and-relations).
*/
async list(
diff --git a/packages/core/js-sdk/src/admin/tax-region.ts b/packages/core/js-sdk/src/admin/tax-region.ts
index f232af3b3b..6b2f65f983 100644
--- a/packages/core/js-sdk/src/admin/tax-region.ts
+++ b/packages/core/js-sdk/src/admin/tax-region.ts
@@ -6,7 +6,7 @@ const taxRegionUrl = "/admin/tax-regions"
/**
* @privateRemarks
- *
+ *
* TODO: Add support for updating a tax region
*/
export class TaxRegion {
@@ -25,12 +25,12 @@ export class TaxRegion {
* This method creates a tax region. It sends a request to the
* [Create Tax Region](https://docs.medusajs.com/api/admin#tax-regions_posttaxregions)
* API route.
- *
+ *
* @param body - The details of the tax region to create.
* @param query - Configure the fields and relations to retrieve in the tax region.
* @param headers - Headers to pass in the request.
* @returns The tax region's details.
- *
+ *
* @example
* sdk.admin.taxRegion.create({
* country_code: "us",
@@ -62,15 +62,51 @@ export class TaxRegion {
)
}
+ /**
+ * This method updates a tax region. It sends a request to the
+ * [Update Tax Region](https://docs.medusajs.com/api/admin#tax-regions_posttaxregionsid)
+ * API route.
+ *
+ * @param id - The ID of the tax region to update.
+ * @param body - The details of the tax region to update.
+ * @param query - Configure the fields and relations to retrieve in the tax region.
+ * @param headers - Headers to pass in the request.
+ * @returns The tax region's details.
+ *
+ * @example
+ * sdk.admin.taxRegion.update("txreg_123", {
+ * province_code: "ca",
+ * })
+ * .then(({ tax_region }) => {
+ * console.log(tax_region)
+ * })
+ */
+ async update(
+ id: string,
+ body: HttpTypes.AdminUpdateTaxRegion,
+ query?: HttpTypes.SelectParams,
+ headers?: ClientHeaders
+ ) {
+ return await this.client.fetch(
+ `${taxRegionUrl}/${id}`,
+ {
+ method: "POST",
+ headers,
+ body,
+ query,
+ }
+ )
+ }
+
/**
* This method deletes a tax region. It sends a request to the
* [Delete Tax Region](https://docs.medusajs.com/api/admin#tax-regions_deletetaxregionsid)
* API route.
- *
+ *
* @param id - The ID of the tax region to delete.
* @param headers - Headers to pass in the request.
* @returns The deletion's details.
- *
+ *
* @example
* sdk.admin.taxRegion.delete("txreg_123")
* .then(({ deleted }) => {
@@ -91,24 +127,24 @@ export class TaxRegion {
* This method retrieves a tax region. It sends a request to the
* [Get Tax Region](https://docs.medusajs.com/api/admin#tax-regions_gettaxregionsid)
* API route.
- *
+ *
* @param id - The ID of the tax region to retrieve.
* @param query - Configure the fields and relations to retrieve in the tax region.
* @param headers - Headers to pass in the request.
* @returns The tax region's details.
- *
+ *
* @example
* To retrieve a tax region by its ID:
- *
+ *
* ```ts
* sdk.admin.taxRegion.retrieve("txreg_123")
* .then(({ tax_region }) => {
* console.log(tax_region)
* })
* ```
- *
+ *
* To specify the fields and relations to retrieve:
- *
+ *
* ```ts
* sdk.admin.taxRegion.retrieve("txreg_123", {
* fields: "id,*tax_rates"
@@ -117,7 +153,7 @@ export class TaxRegion {
* console.log(tax_region)
* })
* ```
- *
+ *
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/admin#select-fields-and-relations).
*/
async retrieve(
@@ -139,25 +175,25 @@ export class TaxRegion {
* This method retrieves a list of tax regions. It sends a request to the
* [List Tax Regions](https://docs.medusajs.com/api/admin#tax-regions_gettaxregions)
* API route.
- *
+ *
* @param query - Filters and pagination configurations.
* @param headers - Headers to pass in the request.
* @returns The list of tax regions.
- *
+ *
* @example
* To retrieve the list of tax regions:
- *
+ *
* ```ts
* sdk.admin.taxRegion.list()
* .then(({ tax_regions, count, limit, offset }) => {
* console.log(tax_regions)
* })
* ```
- *
+ *
* To configure the pagination, pass the `limit` and `offset` query parameters.
- *
+ *
* For example, to retrieve only 10 items and skip 10 items:
- *
+ *
* ```ts
* sdk.admin.taxRegion.list({
* limit: 10,
@@ -167,10 +203,10 @@ export class TaxRegion {
* console.log(tax_regions)
* })
* ```
- *
+ *
* Using the `fields` query parameter, you can specify the fields and relations to retrieve
* in each tax region:
- *
+ *
* ```ts
* sdk.admin.taxRegion.list({
* fields: "id,*tax_rates"
@@ -179,7 +215,7 @@ export class TaxRegion {
* console.log(tax_regions)
* })
* ```
- *
+ *
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/admin#select-fields-and-relations).
*/
async list(
diff --git a/packages/core/types/src/http/index.ts b/packages/core/types/src/http/index.ts
index 7a1af0bb65..72de197bd7 100644
--- a/packages/core/types/src/http/index.ts
+++ b/packages/core/types/src/http/index.ts
@@ -39,6 +39,7 @@ export * from "./shipping-option"
export * from "./shipping-profile"
export * from "./stock-locations"
export * from "./store"
+export * from "./tax-provider"
export * from "./tax-rate"
export * from "./tax-region"
export * from "./user"
diff --git a/packages/core/types/src/http/tax-provider/admin/entities.ts b/packages/core/types/src/http/tax-provider/admin/entities.ts
new file mode 100644
index 0000000000..e303b75b18
--- /dev/null
+++ b/packages/core/types/src/http/tax-provider/admin/entities.ts
@@ -0,0 +1,10 @@
+export interface AdminTaxProvider {
+ /**
+ * The ID of the tax provider.
+ */
+ id: string
+ /**
+ * Whether the tax provider is enabled.
+ */
+ is_enabled: boolean
+}
diff --git a/packages/core/types/src/http/tax-provider/admin/index.ts b/packages/core/types/src/http/tax-provider/admin/index.ts
new file mode 100644
index 0000000000..020c34f02c
--- /dev/null
+++ b/packages/core/types/src/http/tax-provider/admin/index.ts
@@ -0,0 +1,3 @@
+export * from "./entities"
+export * from "./queries"
+export * from "./responses"
diff --git a/packages/core/types/src/http/tax-provider/admin/queries.ts b/packages/core/types/src/http/tax-provider/admin/queries.ts
new file mode 100644
index 0000000000..2e22a7f6ea
--- /dev/null
+++ b/packages/core/types/src/http/tax-provider/admin/queries.ts
@@ -0,0 +1,15 @@
+import { BaseFilterable } from "../../../dal"
+import { FindParams } from "../../common"
+
+export interface AdminGetTaxProvidersParams
+ extends FindParams,
+ BaseFilterable {
+ /**
+ * Filter by tax provider ID(s).
+ */
+ id?: string | string[]
+ /**
+ * Whether the tax provider is enabled.
+ */
+ is_enabled?: boolean
+}
diff --git a/packages/core/types/src/http/tax-provider/admin/responses.ts b/packages/core/types/src/http/tax-provider/admin/responses.ts
new file mode 100644
index 0000000000..ceb35855c0
--- /dev/null
+++ b/packages/core/types/src/http/tax-provider/admin/responses.ts
@@ -0,0 +1,9 @@
+import { PaginatedResponse } from "../../common"
+import { AdminTaxProvider } from "./entities"
+
+export type AdminTaxProviderListResponse = PaginatedResponse<{
+ /**
+ * The list of tax providers.
+ */
+ tax_providers: AdminTaxProvider[]
+}>
diff --git a/packages/core/types/src/http/tax-provider/index.ts b/packages/core/types/src/http/tax-provider/index.ts
new file mode 100644
index 0000000000..26b8eb9dad
--- /dev/null
+++ b/packages/core/types/src/http/tax-provider/index.ts
@@ -0,0 +1 @@
+export * from "./admin"
diff --git a/packages/core/types/src/http/tax-region/admin/entities.ts b/packages/core/types/src/http/tax-region/admin/entities.ts
index 75f37a2c4d..53a4fc5e1a 100644
--- a/packages/core/types/src/http/tax-region/admin/entities.ts
+++ b/packages/core/types/src/http/tax-region/admin/entities.ts
@@ -7,14 +7,14 @@ export interface AdminTaxRegion {
id: string
/**
* The tax region's country code.
- *
+ *
* @example
* "us"
*/
country_code: string | null
/**
* The tax region's lower-case [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) province or state code.
- *
+ *
* @example
* "us-ca"
*/
@@ -27,6 +27,10 @@ export interface AdminTaxRegion {
* The ID of the parent tax region.
*/
parent_id: string | null
+ /**
+ * The ID of the tax provider for the region.
+ */
+ provider_id: string | null
/**
* The date the tax region was created.
*/
diff --git a/packages/core/types/src/http/tax-region/admin/payloads.ts b/packages/core/types/src/http/tax-region/admin/payloads.ts
index 2295ad3a37..6ee466ba78 100644
--- a/packages/core/types/src/http/tax-region/admin/payloads.ts
+++ b/packages/core/types/src/http/tax-region/admin/payloads.ts
@@ -3,6 +3,10 @@ export interface AdminCreateTaxRegion {
* The country code of the tax region.
*/
country_code: string
+ /**
+ * The ID of the tax provider.
+ */
+ provider_id?: string
/**
* The lower-case [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) province or state code of the tax region.
*/
@@ -41,3 +45,21 @@ export interface AdminCreateTaxRegion {
*/
metadata?: Record
}
+
+export interface AdminUpdateTaxRegion {
+ /**
+ * The lower-case [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) province or state code of the tax region.
+ */
+ province_code?: string
+ /**
+ * The ID of the tax provider.
+ */
+ provider_id?: string
+ /**
+ * Custom key-value pairs that can be added to the tax region.
+ */
+ /**
+ * Custom key-value pairs that can be added to the tax region.
+ */
+ metadata?: Record
+}
diff --git a/packages/core/types/src/tax/common.ts b/packages/core/types/src/tax/common.ts
index b7616280a7..609109fd54 100644
--- a/packages/core/types/src/tax/common.ts
+++ b/packages/core/types/src/tax/common.ts
@@ -163,6 +163,11 @@ export interface TaxRegionDTO {
*/
parent_id: string | null
+ /**
+ * The ID of the tax provider for the region.
+ */
+ provider_id: string | null
+
/**
* Holds custom data in key-value pairs.
*/
diff --git a/packages/core/types/src/tax/mutations.ts b/packages/core/types/src/tax/mutations.ts
index 7b5230ff99..ada920b249 100644
--- a/packages/core/types/src/tax/mutations.ts
+++ b/packages/core/types/src/tax/mutations.ts
@@ -1,4 +1,7 @@
import { MetadataType } from "../common"
+import { BaseFilterable } from "../dal"
+import { OperatorMap } from "../dal"
+import { TaxProviderDTO } from "./common"
/**
* The tax rate to be created.
@@ -49,6 +52,37 @@ export interface CreateTaxRateDTO {
metadata?: MetadataType
}
+/**
+ * The tax provider to be created.
+ */
+export interface CreateTaxProviderDTO {
+ /**
+ * The provider's ID.
+ */
+ id: string
+
+ /**
+ * Whether the provider is enabled.
+ */
+ is_enabled?: boolean
+}
+
+/**
+ * The filters to apply on the retrieved tax providers.
+ */
+export interface FilterableTaxProviderProps
+ extends BaseFilterable {
+ /**
+ * The IDs to filter the tax provider by.
+ */
+ id?: string | string[] | OperatorMap
+
+ /**
+ * Filter by whether the tax provider is enabled.
+ */
+ is_enabled?: boolean
+}
+
/**
* The attributes in the tax rate to be created or updated.
*/
@@ -166,6 +200,11 @@ export interface CreateTaxRegionDTO {
*/
parent_id?: string | null
+ /**
+ * The ID of the tax provider for the region.
+ */
+ provider_id?: string | null
+
/**
* Holds custom data in key-value pairs.
*/
@@ -220,6 +259,11 @@ export interface UpdateTaxRegionDTO {
*/
province_code?: string | null
+ /**
+ * The ID of the tax provider for the region.
+ */
+ provider_id?: string | null
+
/**
* Holds custom data in key-value pairs.
*/
diff --git a/packages/core/types/src/tax/service.ts b/packages/core/types/src/tax/service.ts
index adecadaf4c..b8669fb152 100644
--- a/packages/core/types/src/tax/service.ts
+++ b/packages/core/types/src/tax/service.ts
@@ -11,6 +11,7 @@ import {
TaxableItemDTO,
TaxableShippingDTO,
TaxCalculationContext,
+ TaxProviderDTO,
TaxRateDTO,
TaxRateRuleDTO,
TaxRegionDTO,
@@ -19,6 +20,7 @@ import {
CreateTaxRateDTO,
CreateTaxRateRuleDTO,
CreateTaxRegionDTO,
+ FilterableTaxProviderProps,
UpdateTaxRateDTO,
UpdateTaxRegionDTO,
UpsertTaxRateDTO,
@@ -580,6 +582,24 @@ export interface ITaxModuleService extends IModuleService {
sharedContext?: Context
): Promise
+ /**
+ * This method retrieves a paginated list of tax providers based on optional filters and configuration.
+ *
+ * @param {FilterableTaxProviderProps} filters - The filters to apply on the retrieved tax providers.
+ * @param {FindConfig} config - The configurations determining how the tax provider is retrieved.
+ * @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
+ * @returns {Promise} The list of tax providers.
+ *
+ * @example
+ * const taxProviders = await taxModuleService.listTaxProviders()
+ */
+
+ listTaxProviders(
+ filters?: FilterableTaxProviderProps,
+ config?: FindConfig,
+ sharedContext?: Context
+ ): Promise
+
/**
* This method creates tax rate rules.
*
diff --git a/packages/medusa/src/api/admin/tax-providers/middlewares.ts b/packages/medusa/src/api/admin/tax-providers/middlewares.ts
new file mode 100644
index 0000000000..5adb2ec37f
--- /dev/null
+++ b/packages/medusa/src/api/admin/tax-providers/middlewares.ts
@@ -0,0 +1,19 @@
+import * as QueryConfig from "./query-config"
+
+import { validateAndTransformQuery } from "@medusajs/framework"
+import { MiddlewareRoute } from "@medusajs/framework/http"
+
+import { AdminGetTaxProvidersParams } from "./validators"
+
+export const adminTaxProviderRoutesMiddlewares: MiddlewareRoute[] = [
+ {
+ method: "GET",
+ matcher: "/admin/tax-providers",
+ middlewares: [
+ validateAndTransformQuery(
+ AdminGetTaxProvidersParams,
+ QueryConfig.listTransformQueryConfig
+ ),
+ ],
+ },
+]
diff --git a/packages/medusa/src/api/admin/tax-providers/query-config.ts b/packages/medusa/src/api/admin/tax-providers/query-config.ts
new file mode 100644
index 0000000000..7c1e78292e
--- /dev/null
+++ b/packages/medusa/src/api/admin/tax-providers/query-config.ts
@@ -0,0 +1,11 @@
+export const defaults = ["id", "is_enabled"]
+
+export const retrieveTransformQueryConfig = {
+ defaults,
+ isList: false,
+}
+
+export const listTransformQueryConfig = {
+ ...retrieveTransformQueryConfig,
+ isList: true,
+}
diff --git a/packages/medusa/src/api/admin/tax-providers/route.ts b/packages/medusa/src/api/admin/tax-providers/route.ts
new file mode 100644
index 0000000000..8be4510423
--- /dev/null
+++ b/packages/medusa/src/api/admin/tax-providers/route.ts
@@ -0,0 +1,28 @@
+import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
+import {
+ AuthenticatedMedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+
+import { HttpTypes } from "@medusajs/framework/types"
+
+export const GET = async (
+ req: AuthenticatedMedusaRequest,
+ res: MedusaResponse
+) => {
+ const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
+
+ const result = await query.graph({
+ entity: "tax_providers",
+ filters: req.filterableFields,
+ pagination: req.queryConfig.pagination,
+ fields: req.queryConfig.fields,
+ })
+
+ res.status(200).json({
+ tax_providers: result.data,
+ count: result.metadata?.count ?? 0,
+ offset: result.metadata?.skip ?? 0,
+ limit: result.metadata?.take ?? 0,
+ })
+}
diff --git a/packages/medusa/src/api/admin/tax-providers/validators.ts b/packages/medusa/src/api/admin/tax-providers/validators.ts
new file mode 100644
index 0000000000..73c4df816d
--- /dev/null
+++ b/packages/medusa/src/api/admin/tax-providers/validators.ts
@@ -0,0 +1,22 @@
+import { z } from "zod"
+import { createFindParams } from "../../utils/validators"
+import { applyAndAndOrOperators } from "../../utils/common-validators"
+
+export const AdminGetTaxProvidersParamsFields = z.object({
+ id: z.union([z.string(), z.array(z.string())]).optional(),
+ is_enabled: z.boolean().optional(),
+})
+
+export type AdminGetTaxProvidersParamsFieldsType = z.infer<
+ typeof AdminGetTaxProvidersParamsFields
+>
+
+export type AdminGetTaxProvidersParamsType = z.infer<
+ typeof AdminGetTaxProvidersParams
+>
+export const AdminGetTaxProvidersParams = createFindParams({
+ limit: 20,
+ offset: 0,
+})
+ .merge(AdminGetTaxProvidersParamsFields)
+ .merge(applyAndAndOrOperators(AdminGetTaxProvidersParamsFields))
diff --git a/packages/medusa/src/api/admin/tax-regions/[id]/route.ts b/packages/medusa/src/api/admin/tax-regions/[id]/route.ts
index afbad3259e..db66025f50 100644
--- a/packages/medusa/src/api/admin/tax-regions/[id]/route.ts
+++ b/packages/medusa/src/api/admin/tax-regions/[id]/route.ts
@@ -14,7 +14,7 @@ import {
import { AdminUpdateTaxRegionType } from "../validators"
export const GET = async (
- req: AuthenticatedMedusaRequest,
+ req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
diff --git a/packages/medusa/src/api/admin/tax-regions/validators.ts b/packages/medusa/src/api/admin/tax-regions/validators.ts
index 49b9314cc9..e704a37ae5 100644
--- a/packages/medusa/src/api/admin/tax-regions/validators.ts
+++ b/packages/medusa/src/api/admin/tax-regions/validators.ts
@@ -42,6 +42,7 @@ export const AdminGetTaxRegionsParams = createFindParams({
export type AdminCreateTaxRegionType = z.infer
export const AdminCreateTaxRegion = z.object({
country_code: z.string(),
+ provider_id: z.string().nullish(),
province_code: z.string().nullish(),
parent_id: z.string().nullish(),
default_tax_rate: z
@@ -59,5 +60,6 @@ export const AdminCreateTaxRegion = z.object({
export type AdminUpdateTaxRegionType = z.infer
export const AdminUpdateTaxRegion = z.object({
province_code: z.string().nullish(),
+ provider_id: z.string().optional(),
metadata: z.record(z.unknown()).nullish(),
})
diff --git a/packages/medusa/src/api/middlewares.ts b/packages/medusa/src/api/middlewares.ts
index 5f53fc39d8..0d9a0b1884 100644
--- a/packages/medusa/src/api/middlewares.ts
+++ b/packages/medusa/src/api/middlewares.ts
@@ -38,6 +38,7 @@ import { adminStockLocationRoutesMiddlewares } from "./admin/stock-locations/mid
import { adminStoreRoutesMiddlewares } from "./admin/stores/middlewares"
import { adminTaxRateRoutesMiddlewares } from "./admin/tax-rates/middlewares"
import { adminTaxRegionRoutesMiddlewares } from "./admin/tax-regions/middlewares"
+import { adminTaxProviderRoutesMiddlewares } from "./admin/tax-providers/middlewares"
import { adminUploadRoutesMiddlewares } from "./admin/uploads/middlewares"
import { adminUserRoutesMiddlewares } from "./admin/users/middlewares"
import { adminWorkflowsExecutionsMiddlewares } from "./admin/workflows-executions/middlewares"
@@ -120,6 +121,7 @@ export default defineMiddlewares([
...adminRefundReasonsRoutesMiddlewares,
...adminExchangeRoutesMiddlewares,
...adminProductVariantRoutesMiddlewares,
+ ...adminTaxProviderRoutesMiddlewares,
...adminOrderEditRoutesMiddlewares,
...adminPaymentCollectionsMiddlewares,
])
diff --git a/packages/medusa/src/migration-scripts/migrate-tax-region-provider.ts b/packages/medusa/src/migration-scripts/migrate-tax-region-provider.ts
new file mode 100644
index 0000000000..5cab4ef9d7
--- /dev/null
+++ b/packages/medusa/src/migration-scripts/migrate-tax-region-provider.ts
@@ -0,0 +1,49 @@
+import { updateTaxRegionsStep, useQueryGraphStep } from "@medusajs/core-flows"
+import { ExecArgs } from "@medusajs/framework/types"
+import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
+import { transform, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
+import { createWorkflow } from "@medusajs/framework/workflows-sdk"
+
+const assignSystemProviderToTaxRegionsWorkflow = createWorkflow(
+ "assign-system-provider-to-tax-regions",
+ () => {
+ const { data: taxRegions } = useQueryGraphStep({
+ entity: "tax_region",
+ fields: ["id", "provider_id", "province_code"],
+ })
+
+ const updateData = transform({ taxRegions }, ({ taxRegions }) => {
+ /**
+ * Update only parent regions that don't have a provider set.
+ */
+ return taxRegions
+ .filter(
+ (taxRegion) => !taxRegion.province_code && !taxRegion.provider_id
+ )
+ .map((taxRegion) => ({
+ id: taxRegion.id,
+ provider_id: "tp_system_system",
+ }))
+ })
+
+ updateTaxRegionsStep(updateData)
+
+ return new WorkflowResponse(void 0)
+ }
+)
+
+export default async function assignTaxSystemProviderToTaxRegions({
+ container,
+}: ExecArgs) {
+ const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
+
+ logger.info("Assigning tax system provider to tax regions")
+
+ try {
+ await assignSystemProviderToTaxRegionsWorkflow(container).run()
+ logger.info("System provider assigned to tax regions")
+ } catch (e) {
+ logger.error(e)
+ throw e
+ }
+}
diff --git a/packages/modules/tax/integration-tests/utils/setup-tax-structure.ts b/packages/modules/tax/integration-tests/utils/setup-tax-structure.ts
index e2ce1ce7a1..8e3ac3d356 100644
--- a/packages/modules/tax/integration-tests/utils/setup-tax-structure.ts
+++ b/packages/modules/tax/integration-tests/utils/setup-tax-structure.ts
@@ -27,14 +27,17 @@ export const setupTaxStructure = async (service: ITaxModuleService) => {
const [us, dk, de, ca] = await service.createTaxRegions([
{
country_code: "US",
+ provider_id: "tp_system_system",
default_tax_rate: { name: "US Default Rate", rate: 2, code: "US" },
},
{
country_code: "DK",
+ provider_id: "tp_system_system",
default_tax_rate: { name: "Denmark Default Rate", rate: 25, code: "DK" },
},
{
country_code: "DE",
+ provider_id: "tp_system_system",
default_tax_rate: {
code: "DE19",
name: "Germany Default Rate",
@@ -43,6 +46,7 @@ export const setupTaxStructure = async (service: ITaxModuleService) => {
},
{
country_code: "CA",
+ provider_id: "tp_system_system",
default_tax_rate: { name: "Canada Default Rate", rate: 5, code: "CA" },
},
])
diff --git a/packages/modules/tax/src/loaders/providers.ts b/packages/modules/tax/src/loaders/providers.ts
index 9c6268e6d8..4deca59941 100644
--- a/packages/modules/tax/src/loaders/providers.ts
+++ b/packages/modules/tax/src/loaders/providers.ts
@@ -4,25 +4,35 @@ import {
LoaderOptions,
ModuleProvider,
ModulesSdkTypes,
+ CreateTaxProviderDTO,
} from "@medusajs/framework/types"
-import { asFunction, Lifetime } from "awilix"
+import { asFunction, asValue, Lifetime } from "awilix"
import * as providers from "../providers"
+import TaxProviderService from "../services/tax-provider"
+import { MedusaError } from "@medusajs/framework/utils"
+
+const PROVIDER_REGISTRATION_KEY = "tax_providers" as const
const registrationFn = async (klass, container, pluginOptions) => {
+ if (!klass?.identifier) {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_ARGUMENT,
+ `Trying to register a tax provider without a provider identifier.`
+ )
+ }
+
+ const key = `tp_${klass.identifier}${
+ pluginOptions.id ? `_${pluginOptions.id}` : ""
+ }`
+
container.register({
- [`tp_${klass.identifier}`]: asFunction(
- (cradle) => new klass(cradle, pluginOptions),
- { lifetime: klass.LIFE_TIME || Lifetime.SINGLETON }
- ),
+ [key]: asFunction((cradle) => new klass(cradle, pluginOptions.options), {
+ lifetime: klass.LIFE_TIME || Lifetime.SINGLETON,
+ }),
})
- container.registerAdd(
- "tax_providers",
- asFunction((cradle) => new klass(cradle, pluginOptions), {
- lifetime: klass.LIFE_TIME || Lifetime.SINGLETON,
- })
- )
+ container.registerAdd(PROVIDER_REGISTRATION_KEY, asValue(key))
}
export default async ({
@@ -36,7 +46,7 @@ export default async ({
>): Promise => {
// Local providers
for (const provider of Object.values(providers)) {
- await registrationFn(provider, container, {})
+ await registrationFn(provider, container, { id: "system" })
}
await moduleProviderLoader({
@@ -44,4 +54,33 @@ export default async ({
providers: options?.providers || [],
registerServiceFn: registrationFn,
})
+
+ await registerProvidersInDb({ container })
+}
+
+const registerProvidersInDb = async ({
+ container,
+}: LoaderOptions): Promise => {
+ const providersToLoad = container.resolve(PROVIDER_REGISTRATION_KEY)
+ const taxProviderService =
+ container.resolve("taxProviderService")
+
+ const existingProviders = await taxProviderService.list(
+ { id: providersToLoad },
+ {}
+ )
+
+ const upsertData: CreateTaxProviderDTO[] = []
+
+ for (const { id } of existingProviders) {
+ if (!providersToLoad.includes(id)) {
+ upsertData.push({ id, is_enabled: false })
+ }
+ }
+
+ for (const id of providersToLoad) {
+ upsertData.push({ id, is_enabled: true })
+ }
+
+ await taxProviderService.upsert(upsertData)
}
diff --git a/packages/modules/tax/src/services/index.ts b/packages/modules/tax/src/services/index.ts
index 5f121c8a3b..c58b3548d8 100644
--- a/packages/modules/tax/src/services/index.ts
+++ b/packages/modules/tax/src/services/index.ts
@@ -1 +1,2 @@
export { default as TaxModuleService } from "./tax-module-service"
+export { default as TaxProviderService } from "./tax-provider"
diff --git a/packages/modules/tax/src/services/tax-module-service.ts b/packages/modules/tax/src/services/tax-module-service.ts
index 48a3c018b7..3c75b356bf 100644
--- a/packages/modules/tax/src/services/tax-module-service.ts
+++ b/packages/modules/tax/src/services/tax-module-service.ts
@@ -20,13 +20,14 @@ import {
promiseAll,
} from "@medusajs/framework/utils"
import { TaxProvider, TaxRate, TaxRateRule, TaxRegion } from "@models"
+import { TaxProviderService } from "@services"
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
taxRateService: ModulesSdkTypes.IMedusaInternalService
taxRegionService: ModulesSdkTypes.IMedusaInternalService
taxRateRuleService: ModulesSdkTypes.IMedusaInternalService
- taxProviderService: ModulesSdkTypes.IMedusaInternalService
+ taxProviderService: TaxProviderService
[key: `tp_${string}`]: ITaxProvider
}
@@ -57,9 +58,7 @@ export default class TaxModuleService
protected taxRateRuleService_: ModulesSdkTypes.IMedusaInternalService<
InferEntityType
>
- protected taxProviderService_: ModulesSdkTypes.IMedusaInternalService<
- InferEntityType
- >
+ protected taxProviderService_: TaxProviderService
constructor(
{
@@ -445,7 +444,7 @@ export default class TaxModuleService
)
const taxLines = await this.getTaxLinesFromProvider(
- parentRegion.provider_id,
+ parentRegion.provider_id as string,
toReturn,
calculationContext
)
@@ -454,20 +453,11 @@ export default class TaxModuleService
}
private async getTaxLinesFromProvider(
- rawProviderId: string | null,
+ providerId: string,
items: ItemWithRates[],
calculationContext: TaxTypes.TaxCalculationContext
) {
- const providerId = rawProviderId || "system"
- let provider: ITaxProvider
- try {
- provider = this.container_[`tp_${providerId}`] as ITaxProvider
- } catch (err) {
- throw new MedusaError(
- MedusaError.Types.NOT_FOUND,
- `Failed to resolve Tax Provider with id: ${providerId}. Make sure it's installed and configured in the Tax Module's options.`
- )
- }
+ const provider = this.taxProviderService_.retrieveProvider(providerId)
const [itemLines, shippingLines] = items.reduce(
(acc, line) => {
@@ -730,27 +720,4 @@ export default class TaxModuleService
private normalizeRegionCodes(code: string) {
return code.toLowerCase()
}
-
- // @InjectTransactionManager()
- // async createProvidersOnLoad(@MedusaContext() sharedContext: Context = {}) {
- // const providersToLoad = this.container_["tax_providers"] as ITaxProvider[]
-
- // const ids = providersToLoad.map((p) => p.getIdentifier())
-
- // const existing = await this.taxProviderService_.update(
- // { selector: { id: { $in: ids } }, data: { is_enabled: true } },
- // sharedContext
- // )
-
- // const existingIds = existing.map((p) => p.id)
- // const diff = arrayDifference(ids, existingIds)
- // await this.taxProviderService_.create(
- // diff.map((id) => ({ id, is_enabled: true }))
- // )
-
- // await this.taxProviderService_.update({
- // selector: { id: { $nin: ids } },
- // data: { is_enabled: false },
- // })
- // }
}
diff --git a/packages/modules/tax/src/services/tax-provider.ts b/packages/modules/tax/src/services/tax-provider.ts
new file mode 100644
index 0000000000..51deb0624a
--- /dev/null
+++ b/packages/modules/tax/src/services/tax-provider.ts
@@ -0,0 +1,51 @@
+import { DAL, ITaxProvider, Logger, TaxTypes } from "@medusajs/framework/types"
+import { ModulesSdkUtils } from "@medusajs/framework/utils"
+
+import TaxProvider from "../models/tax-provider"
+
+type InjectedDependencies = {
+ logger?: Logger
+ taxProviderRepository: DAL.RepositoryService
+ [key: `tp_${string}`]: ITaxProvider
+}
+
+export default class TaxProviderService extends ModulesSdkUtils.MedusaInternalService(
+ TaxProvider
+) {
+ #logger: Logger
+
+ constructor(container: InjectedDependencies) {
+ super(container)
+ this.#logger = container["logger"]
+ ? container.logger
+ : (console as unknown as Logger)
+ }
+
+ retrieveProvider(providerId: string): ITaxProvider {
+ try {
+ return this.__container__[providerId] as ITaxProvider
+ } catch (err) {
+ if (err.name === "AwilixResolutionError") {
+ const errMessage = `
+ Unable to retrieve the tax provider with id: ${providerId}
+ Please make sure that the provider is registered in the container and it is configured correctly in your project configuration file.`
+ throw new Error(errMessage)
+ }
+
+ const errMessage = `Unable to retrieve the tax provider with id: ${providerId}, the following error occurred: ${err.message}`
+ this.#logger.error(errMessage)
+
+ throw new Error(errMessage)
+ }
+ }
+
+ async getTaxLines(
+ providerId: string,
+ itemLines: TaxTypes.ItemTaxCalculationLine[],
+ shippingLines: TaxTypes.ShippingTaxCalculationLine[],
+ context: TaxTypes.TaxCalculationContext
+ ): Promise<(TaxTypes.ItemTaxLineDTO | TaxTypes.ShippingTaxLineDTO)[]> {
+ const provider = this.retrieveProvider(providerId)
+ return provider.getTaxLines(itemLines, shippingLines, context)
+ }
+}