refactor: 🚚 split avatax & taxjar into separate services (#264)
* refactor: ♻️ split into services * docs: 💡 add comment about joining providers * fix: 🐛 change name from avalara to avatax * build: 👷 add changeset * fix: 🐛 move return into catch
This commit is contained in:
parent
5e28fce12e
commit
56a4dbb3a3
36 changed files with 937 additions and 441 deletions
5
.changeset/nervous-toys-lay.md
Normal file
5
.changeset/nervous-toys-lay.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"saleor-app-taxes": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Extracts the tax providers into individual services. Fixes the issue with updating configs with obfuscated values.
|
|
@ -3,3 +3,5 @@ const { randomUUID } = require("crypto"); // Added in: node v14.17.0
|
||||||
export const createId = (): string => randomUUID();
|
export const createId = (): string => randomUUID();
|
||||||
|
|
||||||
export const obfuscateSecret = (value: string) => value.replace(/.(?=.{4})/g, "*");
|
export const obfuscateSecret = (value: string) => value.replace(/.(?=.{4})/g, "*");
|
||||||
|
|
||||||
|
export const isObfuscated = (value: string) => value.includes("****");
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { CreateTransactionModel } from "avatax/lib/models/CreateTransactionModel";
|
import { CreateTransactionModel } from "avatax/lib/models/CreateTransactionModel";
|
||||||
import { LineItemModel } from "avatax/lib/models/LineItemModel";
|
import { LineItemModel } from "avatax/lib/models/LineItemModel";
|
||||||
import { TransactionModel } from "avatax/lib/models/TransactionModel";
|
import { TransactionModel } from "avatax/lib/models/TransactionModel";
|
||||||
import { TaxBaseFragment } from "../../../../../generated/graphql";
|
import { TaxBaseFragment } from "../../../generated/graphql";
|
||||||
|
|
||||||
import { ChannelConfig } from "../../../channels-configuration/channels-config";
|
import { ChannelConfig } from "../channels-configuration/channels-config";
|
||||||
import { taxLineResolver } from "../../tax-line-resolver";
|
import { taxLineResolver } from "../taxes/tax-line-resolver";
|
||||||
import { ResponseTaxPayload } from "../../types";
|
import { ResponseTaxPayload } from "../taxes/types";
|
||||||
import { AvataxConfig } from "./avatax-config";
|
import { AvataxConfig } from "./avatax-config";
|
||||||
|
|
||||||
const SHIPPING_ITEM_CODE = "Shipping";
|
const SHIPPING_ITEM_CODE = "Shipping";
|
|
@ -1,7 +1,7 @@
|
||||||
import Avatax from "avatax";
|
import Avatax from "avatax";
|
||||||
import { CreateTransactionModel } from "avatax/lib/models/CreateTransactionModel";
|
import { CreateTransactionModel } from "avatax/lib/models/CreateTransactionModel";
|
||||||
import packageJson from "../../../../../package.json";
|
import packageJson from "../../../package.json";
|
||||||
import { logger } from "../../../../lib/logger";
|
import { logger } from "../../lib/logger";
|
||||||
import { AvataxConfig } from "./avatax-config";
|
import { AvataxConfig } from "./avatax-config";
|
||||||
|
|
||||||
type AvataxSettings = {
|
type AvataxSettings = {
|
||||||
|
@ -55,6 +55,20 @@ export class AvataxClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
async ping() {
|
async ping() {
|
||||||
return this.client.ping();
|
try {
|
||||||
|
const result = await this.client.ping();
|
||||||
|
|
||||||
|
return {
|
||||||
|
authenticated: result.authenticated,
|
||||||
|
...(!result.authenticated && {
|
||||||
|
error: "Avatax was not able to authenticate with the provided credentials.",
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
authenticated: false,
|
||||||
|
error: "Avatax was not able to authenticate with the provided credentials.",
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
29
apps/taxes/src/modules/avatax/avatax-config.ts
Normal file
29
apps/taxes/src/modules/avatax/avatax-config.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const avataxConfigSchema = z.object({
|
||||||
|
name: z.string().min(1, { message: "Name requires at least one character." }),
|
||||||
|
username: z.string().min(1, { message: "Username requires at least one character." }),
|
||||||
|
password: z.string().min(1, { message: "Password requires at least one character." }),
|
||||||
|
isSandbox: z.boolean(),
|
||||||
|
companyName: z.string().min(1, { message: "Company name requires at least one character." }),
|
||||||
|
isAutocommit: z.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AvataxConfig = z.infer<typeof avataxConfigSchema>;
|
||||||
|
|
||||||
|
export const defaultAvataxConfig: AvataxConfig = {
|
||||||
|
name: "",
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
companyName: "",
|
||||||
|
isSandbox: true,
|
||||||
|
isAutocommit: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const avataxInstanceConfigSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
provider: z.literal("avatax"),
|
||||||
|
config: avataxConfigSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AvataxInstanceConfig = z.infer<typeof avataxInstanceConfigSchema>;
|
116
apps/taxes/src/modules/avatax/avatax-configuration.router.ts
Normal file
116
apps/taxes/src/modules/avatax/avatax-configuration.router.ts
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
import { logger as pinoLogger } from "../../lib/logger";
|
||||||
|
import { protectedClientProcedure } from "../trpc/protected-client-procedure";
|
||||||
|
import { router } from "../trpc/trpc-server";
|
||||||
|
import { avataxConfigSchema } from "./avatax-config";
|
||||||
|
import { AvataxConfigurationService } from "./avatax-configuration.service";
|
||||||
|
|
||||||
|
const getInputSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteInputSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const patchInputSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
value: avataxConfigSchema.partial(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const putInputSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
value: avataxConfigSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
const postInputSchema = z.object({
|
||||||
|
value: avataxConfigSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const avataxConfigurationRouter = router({
|
||||||
|
get: protectedClientProcedure.input(getInputSchema).query(async ({ ctx, input }) => {
|
||||||
|
const logger = pinoLogger.child({
|
||||||
|
saleorApiUrl: ctx.saleorApiUrl,
|
||||||
|
procedure: "avataxConfigurationRouter.get",
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug("avataxConfigurationRouter.get called");
|
||||||
|
|
||||||
|
const { apiClient, saleorApiUrl } = ctx;
|
||||||
|
const avataxConfigurationService = new AvataxConfigurationService(apiClient, saleorApiUrl);
|
||||||
|
|
||||||
|
const result = await avataxConfigurationService.get(input.id);
|
||||||
|
|
||||||
|
logger.debug({ result }, "avataxConfigurationRouter.get finished");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
post: protectedClientProcedure.input(postInputSchema).mutation(async ({ ctx, input }) => {
|
||||||
|
const logger = pinoLogger.child({
|
||||||
|
saleorApiUrl: ctx.saleorApiUrl,
|
||||||
|
procedure: "avataxConfigurationRouter.post",
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug("avataxConfigurationRouter.post called");
|
||||||
|
|
||||||
|
const { apiClient, saleorApiUrl } = ctx;
|
||||||
|
const avataxConfigurationService = new AvataxConfigurationService(apiClient, saleorApiUrl);
|
||||||
|
|
||||||
|
const result = await avataxConfigurationService.post(input.value);
|
||||||
|
|
||||||
|
logger.debug({ result }, "avataxConfigurationRouter.post finished");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
delete: protectedClientProcedure.input(deleteInputSchema).mutation(async ({ ctx, input }) => {
|
||||||
|
const logger = pinoLogger.child({
|
||||||
|
saleorApiUrl: ctx.saleorApiUrl,
|
||||||
|
procedure: "avataxConfigurationRouter.delete",
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug("avataxConfigurationRouter.delete called");
|
||||||
|
|
||||||
|
const { apiClient, saleorApiUrl } = ctx;
|
||||||
|
const avataxConfigurationService = new AvataxConfigurationService(apiClient, saleorApiUrl);
|
||||||
|
|
||||||
|
const result = await avataxConfigurationService.delete(input.id);
|
||||||
|
|
||||||
|
logger.debug({ result }, "avataxConfigurationRouter.delete finished");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
patch: protectedClientProcedure.input(patchInputSchema).mutation(async ({ ctx, input }) => {
|
||||||
|
const logger = pinoLogger.child({
|
||||||
|
saleorApiUrl: ctx.saleorApiUrl,
|
||||||
|
procedure: "avataxConfigurationRouter.patch",
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug("avataxConfigurationRouter.patch called");
|
||||||
|
|
||||||
|
const { apiClient, saleorApiUrl } = ctx;
|
||||||
|
const avataxConfigurationService = new AvataxConfigurationService(apiClient, saleorApiUrl);
|
||||||
|
|
||||||
|
const result = await avataxConfigurationService.patch(input.id, input.value);
|
||||||
|
|
||||||
|
logger.debug({ result }, "avataxConfigurationRouter.patch finished");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
put: protectedClientProcedure.input(putInputSchema).mutation(async ({ ctx, input }) => {
|
||||||
|
const logger = pinoLogger.child({
|
||||||
|
saleorApiUrl: ctx.saleorApiUrl,
|
||||||
|
procedure: "avataxConfigurationRouter.put",
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug("avataxConfigurationRouter.put called");
|
||||||
|
|
||||||
|
const { apiClient, saleorApiUrl } = ctx;
|
||||||
|
const avataxConfigurationService = new AvataxConfigurationService(apiClient, saleorApiUrl);
|
||||||
|
|
||||||
|
const result = await avataxConfigurationService.put(input.id, input.value);
|
||||||
|
|
||||||
|
logger.debug({ result }, "avataxConfigurationRouter.put finished");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
});
|
151
apps/taxes/src/modules/avatax/avatax-configuration.service.ts
Normal file
151
apps/taxes/src/modules/avatax/avatax-configuration.service.ts
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
import pino from "pino";
|
||||||
|
import { Client } from "urql";
|
||||||
|
import { createLogger } from "../../lib/logger";
|
||||||
|
import { isObfuscated, obfuscateSecret } from "../../lib/utils";
|
||||||
|
import { createSettingsManager } from "../app-configuration/metadata-manager";
|
||||||
|
import { CrudSettingsConfigurator } from "../crud-settings/crud-settings.service";
|
||||||
|
import { providersSchema } from "../providers-configuration/providers-config";
|
||||||
|
import { TAX_PROVIDER_KEY } from "../providers-configuration/providers-configuration-service";
|
||||||
|
import { AvataxClient } from "./avatax-client";
|
||||||
|
import {
|
||||||
|
AvataxConfig,
|
||||||
|
avataxConfigSchema,
|
||||||
|
AvataxInstanceConfig,
|
||||||
|
avataxInstanceConfigSchema,
|
||||||
|
} from "./avatax-config";
|
||||||
|
|
||||||
|
const obfuscateConfig = (config: AvataxConfig) => ({
|
||||||
|
...config,
|
||||||
|
username: obfuscateSecret(config.username),
|
||||||
|
password: obfuscateSecret(config.password),
|
||||||
|
});
|
||||||
|
|
||||||
|
const obfuscateProvidersConfig = (instances: AvataxInstanceConfig[]) =>
|
||||||
|
instances.map((instance) => ({
|
||||||
|
...instance,
|
||||||
|
config: obfuscateConfig(instance.config),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const getSchema = avataxInstanceConfigSchema.transform((instance) => ({
|
||||||
|
...instance,
|
||||||
|
config: obfuscateConfig(instance.config),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const patchSchema = avataxConfigSchema.partial().transform((c) => {
|
||||||
|
const { username, password, ...config } = c ?? {};
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
...(username && !isObfuscated(username) && { username }),
|
||||||
|
...(password && !isObfuscated(password) && { password }),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const putSchema = avataxConfigSchema.transform((c) => {
|
||||||
|
const { username, password, ...config } = c;
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
...(!isObfuscated(username) && { username }),
|
||||||
|
...(!isObfuscated(password) && { password }),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export class AvataxConfigurationService {
|
||||||
|
private crudSettingsConfigurator: CrudSettingsConfigurator;
|
||||||
|
private logger: pino.Logger;
|
||||||
|
constructor(client: Client, saleorApiUrl: string) {
|
||||||
|
const settingsManager = createSettingsManager(client);
|
||||||
|
this.crudSettingsConfigurator = new CrudSettingsConfigurator(
|
||||||
|
settingsManager,
|
||||||
|
saleorApiUrl,
|
||||||
|
TAX_PROVIDER_KEY
|
||||||
|
);
|
||||||
|
this.logger = createLogger({
|
||||||
|
service: "AvataxConfigurationService",
|
||||||
|
metadataKey: TAX_PROVIDER_KEY,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll() {
|
||||||
|
this.logger.debug(".getAll called");
|
||||||
|
const { data } = await this.crudSettingsConfigurator.readAll();
|
||||||
|
const validation = providersSchema.safeParse(data);
|
||||||
|
|
||||||
|
if (!validation.success) {
|
||||||
|
this.logger.error({ error: validation.error.format() }, "Validation error while getAll");
|
||||||
|
throw new Error(validation.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const instances = validation.data.filter(
|
||||||
|
(instance) => instance.provider === "avatax"
|
||||||
|
) as AvataxInstanceConfig[];
|
||||||
|
|
||||||
|
return obfuscateProvidersConfig(instances);
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(id: string) {
|
||||||
|
this.logger.debug(`.get called with id: ${id}`);
|
||||||
|
const { data } = await this.crudSettingsConfigurator.read(id);
|
||||||
|
this.logger.debug({ setting: data }, `Fetched setting from crudSettingsConfigurator`);
|
||||||
|
|
||||||
|
const validation = getSchema.safeParse(data);
|
||||||
|
|
||||||
|
if (!validation.success) {
|
||||||
|
this.logger.error({ error: validation.error.format() }, "Validation error while get");
|
||||||
|
throw new Error(validation.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return validation.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async post(config: AvataxConfig) {
|
||||||
|
this.logger.debug(`.post called with value: ${JSON.stringify(config)}`);
|
||||||
|
const avataxClient = new AvataxClient(config);
|
||||||
|
const validation = await avataxClient.ping();
|
||||||
|
|
||||||
|
if (!validation.authenticated) {
|
||||||
|
this.logger.error(validation.error);
|
||||||
|
throw new Error(validation.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async patch(id: string, config: Partial<AvataxConfig>) {
|
||||||
|
this.logger.debug(`.patch called with id: ${id} and value: ${JSON.stringify(config)}`);
|
||||||
|
const result = await this.get(id);
|
||||||
|
// omit the key "id" from the result
|
||||||
|
const { id: _, ...setting } = result;
|
||||||
|
const validation = patchSchema.safeParse(config);
|
||||||
|
|
||||||
|
if (!validation.success) {
|
||||||
|
this.logger.error({ error: validation.error.format() }, "Validation error while patch");
|
||||||
|
throw new Error(validation.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.crudSettingsConfigurator.update(id, {
|
||||||
|
...setting,
|
||||||
|
config: { ...setting.config, ...validation.data },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async put(id: string, config: AvataxConfig) {
|
||||||
|
const result = await this.get(id);
|
||||||
|
// omit the key "id" from the result
|
||||||
|
const { id: _, ...setting } = result;
|
||||||
|
const validation = putSchema.safeParse(config);
|
||||||
|
|
||||||
|
if (!validation.success) {
|
||||||
|
this.logger.error({ error: validation.error.format() }, "Validation error while patch");
|
||||||
|
throw new Error(validation.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug(`.put called with id: ${id} and value: ${JSON.stringify(config)}`);
|
||||||
|
return this.crudSettingsConfigurator.update(id, {
|
||||||
|
...setting,
|
||||||
|
config: { ...validation.data },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(id: string) {
|
||||||
|
this.logger.debug(`.delete called with id: ${id}`);
|
||||||
|
return this.crudSettingsConfigurator.delete(id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import { TaxBaseFragment } from "../../../../../generated/graphql";
|
import { TaxBaseFragment } from "../../../generated/graphql";
|
||||||
import { logger } from "../../../../lib/logger";
|
import { logger } from "../../lib/logger";
|
||||||
import { ChannelConfig } from "../../../channels-configuration/channels-config";
|
import { ChannelConfig } from "../channels-configuration/channels-config";
|
||||||
import { TaxProvider } from "../../tax-provider";
|
import { TaxProvider } from "../taxes/tax-provider";
|
||||||
import { avataxCalculate } from "./avatax-calculate";
|
import { avataxCalculate } from "./avatax-calculate";
|
||||||
import { AvataxClient } from "./avatax-client";
|
import { AvataxClient } from "./avatax-client";
|
||||||
import { AvataxConfig, defaultAvataxConfig } from "./avatax-config";
|
import { AvataxConfig, defaultAvataxConfig } from "./avatax-config";
|
||||||
|
@ -19,24 +19,6 @@ export class AvataxProvider implements TaxProvider {
|
||||||
this.client = avataxClient;
|
this.client = avataxClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate() {
|
|
||||||
logger.info("Avatax validate");
|
|
||||||
const validation = await this.client.ping();
|
|
||||||
logger.info(validation, "Avatax ping result");
|
|
||||||
|
|
||||||
if (validation.authenticated) {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
error:
|
|
||||||
"Avalara was unable to authenticate. Check if the username and password you provided are correct.",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async calculate(payload: TaxBaseFragment, channel: ChannelConfig) {
|
async calculate(payload: TaxBaseFragment, channel: ChannelConfig) {
|
||||||
logger.info("Avatax calculate");
|
logger.info("Avatax calculate");
|
||||||
const model = avataxCalculate.preparePayload(payload, channel, this.config);
|
const model = avataxCalculate.preparePayload(payload, channel, this.config);
|
|
@ -13,10 +13,10 @@ import { Button, makeStyles } from "@saleor/macaw-ui";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { trpcClient } from "../../../../trpc/trpc-client";
|
import { trpcClient } from "../../trpc/trpc-client";
|
||||||
import { AppLink } from "../../../../ui/app-link";
|
import { AppLink } from "../../ui/app-link";
|
||||||
import { useInstanceId } from "../../../tax-context";
|
import { useInstanceId } from "../../taxes/tax-context";
|
||||||
import { avataxInstanceConfigSchema } from "../avatax-config";
|
import { avataxConfigSchema, avataxInstanceConfigSchema } from "../avatax-config";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
reverseRow: {
|
reverseRow: {
|
||||||
|
@ -26,17 +26,15 @@ const useStyles = makeStyles((theme) => ({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const schema = avataxInstanceConfigSchema.omit({ provider: true });
|
const schema = avataxConfigSchema;
|
||||||
type FormValues = z.infer<typeof schema>;
|
type FormValues = z.infer<typeof schema>;
|
||||||
|
|
||||||
const defaultValues: FormValues = {
|
const defaultValues: FormValues = {
|
||||||
config: {
|
companyName: "",
|
||||||
companyName: "",
|
isAutocommit: false,
|
||||||
isAutocommit: false,
|
isSandbox: false,
|
||||||
isSandbox: false,
|
password: "",
|
||||||
password: "",
|
username: "",
|
||||||
username: "",
|
|
||||||
},
|
|
||||||
name: "",
|
name: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,11 +48,31 @@ export const AvataxConfigurationForm = () => {
|
||||||
});
|
});
|
||||||
const { instanceId, setInstanceId } = useInstanceId();
|
const { instanceId, setInstanceId } = useInstanceId();
|
||||||
const { refetch: refetchChannelConfigurationData } =
|
const { refetch: refetchChannelConfigurationData } =
|
||||||
trpcClient.channelsConfiguration.fetch.useQuery();
|
trpcClient.channelsConfiguration.fetch.useQuery(undefined, {
|
||||||
const { data: providersConfigurationData, refetch: refetchProvidersConfigurationData } =
|
onError(error) {
|
||||||
trpcClient.providersConfiguration.getAll.useQuery();
|
appBridge?.dispatch(
|
||||||
|
actions.Notification({
|
||||||
|
title: "Error",
|
||||||
|
text: error.message,
|
||||||
|
status: "error",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { data: providersConfig, refetch: refetchProvidersConfigurationData } =
|
||||||
|
trpcClient.providersConfiguration.getAll.useQuery(undefined, {
|
||||||
|
onError(error) {
|
||||||
|
appBridge?.dispatch(
|
||||||
|
actions.Notification({
|
||||||
|
title: "Error",
|
||||||
|
text: error.message,
|
||||||
|
status: "error",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const instance = providersConfigurationData?.find((instance) => instance.id === instanceId);
|
const instance = providersConfig?.find((instance) => instance.id === instanceId);
|
||||||
|
|
||||||
const resetInstanceId = () => {
|
const resetInstanceId = () => {
|
||||||
setInstanceId(null);
|
setInstanceId(null);
|
||||||
|
@ -62,16 +80,16 @@ export const AvataxConfigurationForm = () => {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (instance) {
|
if (instance) {
|
||||||
const { provider, id, ...values } = instance;
|
const { config } = instance;
|
||||||
reset(values);
|
reset(config);
|
||||||
} else {
|
} else {
|
||||||
reset(defaultValues);
|
reset(defaultValues);
|
||||||
}
|
}
|
||||||
}, [instance, reset]);
|
}, [instance, reset]);
|
||||||
|
|
||||||
const { mutate: createMutation, isLoading: isCreateLoading } =
|
const { mutate: createMutation, isLoading: isCreateLoading } =
|
||||||
trpcClient.providersConfiguration.create.useMutation({
|
trpcClient.avataxConfiguration.post.useMutation({
|
||||||
onSuccess({ id }) {
|
onSuccess({ data: { id } }) {
|
||||||
setInstanceId(id);
|
setInstanceId(id);
|
||||||
refetchProvidersConfigurationData();
|
refetchProvidersConfigurationData();
|
||||||
appBridge?.dispatch(
|
appBridge?.dispatch(
|
||||||
|
@ -94,7 +112,7 @@ export const AvataxConfigurationForm = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate: updateMutation, isLoading: isUpdateLoading } =
|
const { mutate: updateMutation, isLoading: isUpdateLoading } =
|
||||||
trpcClient.providersConfiguration.update.useMutation({
|
trpcClient.avataxConfiguration.patch.useMutation({
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
refetchProvidersConfigurationData();
|
refetchProvidersConfigurationData();
|
||||||
appBridge?.dispatch(
|
appBridge?.dispatch(
|
||||||
|
@ -116,7 +134,7 @@ export const AvataxConfigurationForm = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate: deleteMutation } = trpcClient.providersConfiguration.delete.useMutation({
|
const { mutate: deleteMutation } = trpcClient.avataxConfiguration.delete.useMutation({
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
resetInstanceId();
|
resetInstanceId();
|
||||||
refetchProvidersConfigurationData();
|
refetchProvidersConfigurationData();
|
||||||
|
@ -144,21 +162,15 @@ export const AvataxConfigurationForm = () => {
|
||||||
fullWidth: true,
|
fullWidth: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = (values: FormValues) => {
|
const onSubmit = (value: FormValues) => {
|
||||||
if (instanceId) {
|
if (instanceId) {
|
||||||
updateMutation({
|
updateMutation({
|
||||||
id: instanceId,
|
id: instanceId,
|
||||||
provider: {
|
value,
|
||||||
...values,
|
|
||||||
provider: "avatax",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
createMutation({
|
createMutation({
|
||||||
provider: {
|
value,
|
||||||
...values,
|
|
||||||
provider: "avatax",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -201,9 +213,9 @@ export const AvataxConfigurationForm = () => {
|
||||||
<InputLabel>
|
<InputLabel>
|
||||||
Sandbox
|
Sandbox
|
||||||
<Controller
|
<Controller
|
||||||
name={"config.isSandbox"}
|
name={"isSandbox"}
|
||||||
control={control}
|
control={control}
|
||||||
defaultValue={defaultValues.config.isSandbox}
|
defaultValue={defaultValues.isSandbox}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Switch
|
<Switch
|
||||||
{...field}
|
{...field}
|
||||||
|
@ -229,9 +241,9 @@ export const AvataxConfigurationForm = () => {
|
||||||
<InputLabel>
|
<InputLabel>
|
||||||
Autocommit
|
Autocommit
|
||||||
<Controller
|
<Controller
|
||||||
name={"config.isAutocommit"}
|
name={"isAutocommit"}
|
||||||
control={control}
|
control={control}
|
||||||
defaultValue={defaultValues.config.isAutocommit}
|
defaultValue={defaultValues.isAutocommit}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Switch
|
<Switch
|
||||||
{...field}
|
{...field}
|
||||||
|
@ -254,39 +266,39 @@ export const AvataxConfigurationForm = () => {
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Controller
|
<Controller
|
||||||
name="config.username"
|
name="username"
|
||||||
control={control}
|
control={control}
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<TextField type="text" {...field} label="Username" {...textFieldProps} />
|
<TextField type="text" {...field} label="Username" {...textFieldProps} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{formState.errors.config?.username && (
|
{formState.errors.username && (
|
||||||
<FormHelperText error>{formState.errors.config.username.message}</FormHelperText>
|
<FormHelperText error>{formState.errors.username.message}</FormHelperText>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Controller
|
<Controller
|
||||||
name="config.password"
|
name="password"
|
||||||
control={control}
|
control={control}
|
||||||
defaultValue={defaultValues.config.password}
|
defaultValue={defaultValues.password}
|
||||||
render={({ field }) => <TextField label="Password" {...field} {...textFieldProps} />}
|
render={({ field }) => <TextField label="Password" {...field} {...textFieldProps} />}
|
||||||
/>
|
/>
|
||||||
{formState.errors.config?.password && (
|
{formState.errors.password && (
|
||||||
<FormHelperText error>{formState.errors.config.password.message}</FormHelperText>
|
<FormHelperText error>{formState.errors.password.message}</FormHelperText>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Controller
|
<Controller
|
||||||
name="config.companyName"
|
name="companyName"
|
||||||
control={control}
|
control={control}
|
||||||
defaultValue={defaultValues.config.companyName}
|
defaultValue={defaultValues.companyName}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<TextField type="text" {...field} label="Company name" {...textFieldProps} />
|
<TextField type="text" {...field} label="Company name" {...textFieldProps} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{formState.errors.config?.companyName && (
|
{formState.errors.companyName && (
|
||||||
<FormHelperText error>{formState.errors.config.companyName.message}</FormHelperText>
|
<FormHelperText error>{formState.errors.companyName.message}</FormHelperText>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
|
@ -3,7 +3,7 @@ import { AvataxConfigurationForm } from "./avatax-configuration-form";
|
||||||
export const AvataxConfiguration = () => {
|
export const AvataxConfiguration = () => {
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<h2>Avalara configuration</h2>
|
<h2>Avatax configuration</h2>
|
||||||
<AvataxConfigurationForm />
|
<AvataxConfigurationForm />
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
|
@ -78,9 +78,32 @@ export const ChannelTaxProviderForm = () => {
|
||||||
const { channelSlug } = useChannelSlug();
|
const { channelSlug } = useChannelSlug();
|
||||||
|
|
||||||
const { data: channelConfigurationData, refetch: refetchChannelConfigurationData } =
|
const { data: channelConfigurationData, refetch: refetchChannelConfigurationData } =
|
||||||
trpcClient.channelsConfiguration.fetch.useQuery();
|
trpcClient.channelsConfiguration.fetch.useQuery(undefined, {
|
||||||
|
onError(error) {
|
||||||
|
appBridge?.dispatch(
|
||||||
|
actions.Notification({
|
||||||
|
title: "Error",
|
||||||
|
text: error.message,
|
||||||
|
status: "error",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const { data: providerInstances = [] } = trpcClient.providersConfiguration.getAll.useQuery();
|
const { data: providerInstances = [] } = trpcClient.providersConfiguration.getAll.useQuery(
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
onError(error) {
|
||||||
|
appBridge?.dispatch(
|
||||||
|
actions.Notification({
|
||||||
|
title: "Error",
|
||||||
|
text: error.message,
|
||||||
|
status: "error",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
const channelConfig = channelConfigurationData?.[channelSlug];
|
const channelConfig = channelConfigurationData?.[channelSlug];
|
||||||
|
|
||||||
const { mutate, isLoading } = trpcClient.channelsConfiguration.upsert.useMutation({
|
const { mutate, isLoading } = trpcClient.channelsConfiguration.upsert.useMutation({
|
||||||
|
@ -136,10 +159,10 @@ export const ChannelTaxProviderForm = () => {
|
||||||
defaultValue={""}
|
defaultValue={""}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Select fullWidth {...field}>
|
<Select fullWidth {...field}>
|
||||||
{providerInstances.map(({ name, id, provider }) => (
|
{providerInstances.map(({ config, id, provider }) => (
|
||||||
<MenuItem value={id} key={name}>
|
<MenuItem value={id} key={id}>
|
||||||
<div className={styles.menuItem}>
|
<div className={styles.menuItem}>
|
||||||
<Typography variant="body1">{name}</Typography>
|
<Typography variant="body1">{config.name}</Typography>
|
||||||
<ProviderIcon size={"medium"} provider={provider} />
|
<ProviderIcon size={"medium"} provider={provider} />
|
||||||
</div>
|
</div>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
109
apps/taxes/src/modules/crud-settings/crud-settings.service.ts
Normal file
109
apps/taxes/src/modules/crud-settings/crud-settings.service.ts
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
||||||
|
import pino from "pino";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { createLogger } from "../../lib/logger";
|
||||||
|
import { createId } from "../../lib/utils";
|
||||||
|
|
||||||
|
const settingsSchema = z.array(z.record(z.any()));
|
||||||
|
|
||||||
|
export class CrudSettingsConfigurator {
|
||||||
|
private logger: pino.Logger;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private metadataManager: SettingsManager,
|
||||||
|
private saleorApiUrl: string,
|
||||||
|
private metadataKey: string
|
||||||
|
) {
|
||||||
|
this.metadataKey = metadataKey;
|
||||||
|
this.logger = createLogger({ service: "CrudSettingsConfigurator", metadataKey });
|
||||||
|
}
|
||||||
|
|
||||||
|
async readAll() {
|
||||||
|
this.logger.debug(".readAll called");
|
||||||
|
const result = await this.metadataManager.get(this.metadataKey, this.saleorApiUrl);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
this.logger.debug("No metadata found");
|
||||||
|
return { data: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = JSON.parse(result);
|
||||||
|
const validation = settingsSchema.safeParse(data);
|
||||||
|
|
||||||
|
if (!validation.success) {
|
||||||
|
this.logger.error({ error: validation.error }, "Error while validating metadata");
|
||||||
|
throw new Error("Error while validating metadata");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: validation.data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async read(id: string) {
|
||||||
|
this.logger.debug(".read called");
|
||||||
|
const result = await this.readAll();
|
||||||
|
const { data: settings } = result;
|
||||||
|
|
||||||
|
const item = settings.find((item) => item.id === id);
|
||||||
|
if (!item) {
|
||||||
|
this.logger.error({ id }, "Item not found");
|
||||||
|
throw new Error("Item not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: item,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(data: any) {
|
||||||
|
this.logger.debug(data, ".create called with:");
|
||||||
|
|
||||||
|
const getResponse = await this.readAll();
|
||||||
|
const prevData = getResponse.data;
|
||||||
|
|
||||||
|
const id = createId();
|
||||||
|
const newData = [...prevData, { ...data, id }];
|
||||||
|
await this.metadataManager.set({
|
||||||
|
key: this.metadataKey,
|
||||||
|
value: JSON.stringify(newData),
|
||||||
|
domain: this.saleorApiUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: { id },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(id: string) {
|
||||||
|
this.logger.debug(`.delete called with: ${id}`);
|
||||||
|
|
||||||
|
const getResponse = await this.readAll();
|
||||||
|
const prevData = getResponse.data;
|
||||||
|
const nextData = prevData.filter((item) => item.id !== id);
|
||||||
|
|
||||||
|
await this.metadataManager.set({
|
||||||
|
key: this.metadataKey,
|
||||||
|
value: JSON.stringify(nextData),
|
||||||
|
domain: this.saleorApiUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: string, data: any) {
|
||||||
|
this.logger.debug(data, `.update called with: ${id}`);
|
||||||
|
const getResponse = await this.readAll();
|
||||||
|
const prevData = getResponse.data;
|
||||||
|
const nextData = prevData.map((item) => {
|
||||||
|
if (item.id === id) {
|
||||||
|
return { id, data };
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.metadataManager.set({
|
||||||
|
key: this.metadataKey,
|
||||||
|
value: JSON.stringify(nextData),
|
||||||
|
domain: this.saleorApiUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,43 +0,0 @@
|
||||||
import { Client } from "urql";
|
|
||||||
import { logger as pinoLogger } from "../../lib/logger";
|
|
||||||
import { createSettingsManager } from "../app-configuration/metadata-manager";
|
|
||||||
import { createDefaultConfig, serverProvidersSchema } from "./providers-config";
|
|
||||||
import { TaxProvidersConfigurator } from "./providers-configurator";
|
|
||||||
|
|
||||||
export class GetProvidersConfigurationService {
|
|
||||||
constructor(
|
|
||||||
private settings: {
|
|
||||||
apiClient: Client;
|
|
||||||
saleorApiUrl: string;
|
|
||||||
}
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async getConfiguration() {
|
|
||||||
const logger = pinoLogger.child({
|
|
||||||
service: "GetProvidersConfigurationService",
|
|
||||||
saleorApiUrl: this.settings.saleorApiUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { saleorApiUrl, apiClient } = this.settings;
|
|
||||||
|
|
||||||
const taxConfigurator = new TaxProvidersConfigurator(
|
|
||||||
createSettingsManager(apiClient),
|
|
||||||
saleorApiUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
const savedProvidersConfig = (await taxConfigurator.getConfig()) ?? null;
|
|
||||||
const validation = serverProvidersSchema.safeParse(savedProvidersConfig);
|
|
||||||
|
|
||||||
logger.info({ validation }, "Config validated:");
|
|
||||||
|
|
||||||
if (validation.success) {
|
|
||||||
logger.info("App config is valid. Returning.");
|
|
||||||
return validation.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("App config not found in metadata. Will return default config.");
|
|
||||||
const defaultConfig = createDefaultConfig();
|
|
||||||
|
|
||||||
return defaultConfig;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +1,9 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import { avataxInstanceConfigSchema } from "../avatax/avatax-config";
|
||||||
avataxInstanceConfigSchema,
|
import { taxJarInstanceConfigSchema } from "../taxjar/taxjar-config";
|
||||||
serverAvataxSchema,
|
|
||||||
} from "../taxes/providers/avatax/avatax-config";
|
|
||||||
import {
|
|
||||||
taxJarInstanceConfigSchema,
|
|
||||||
serverTaxJarSchema,
|
|
||||||
} from "../taxes/providers/taxjar/taxjar-config";
|
|
||||||
|
|
||||||
export const providerSchema = taxJarInstanceConfigSchema.or(avataxInstanceConfigSchema);
|
export const providerSchema = taxJarInstanceConfigSchema.or(avataxInstanceConfigSchema);
|
||||||
export const providersSchema = z.array(providerSchema.and(z.object({ id: z.string() })));
|
export const providersSchema = z.array(providerSchema);
|
||||||
|
|
||||||
const serverProviderSchema = serverTaxJarSchema.or(serverAvataxSchema);
|
|
||||||
export const serverProvidersSchema = z.array(
|
|
||||||
serverProviderSchema.and(z.object({ id: z.string() }))
|
|
||||||
);
|
|
||||||
|
|
||||||
export type ProvidersConfig = z.infer<typeof providersSchema>;
|
export type ProvidersConfig = z.infer<typeof providersSchema>;
|
||||||
export type ProviderConfig = z.infer<typeof providerSchema>;
|
export type ProviderConfig = z.infer<typeof providerSchema>;
|
||||||
|
|
||||||
export const defaultTaxProvidersConfig: ProvidersConfig = [];
|
|
||||||
|
|
||||||
export const createDefaultConfig = () => defaultTaxProvidersConfig;
|
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import pino from "pino";
|
||||||
|
import { Client } from "urql";
|
||||||
|
import { createLogger } from "../../lib/logger";
|
||||||
|
import { AvataxConfigurationService } from "../avatax/avatax-configuration.service";
|
||||||
|
import { TaxJarConfigurationService } from "../taxjar/taxjar-configuration.service";
|
||||||
|
|
||||||
|
export const TAX_PROVIDER_KEY = "tax-providers";
|
||||||
|
|
||||||
|
export class TaxProvidersConfigurationService {
|
||||||
|
private avataxConfigurationService: AvataxConfigurationService;
|
||||||
|
private taxJarConfigurationService: TaxJarConfigurationService;
|
||||||
|
private logger: pino.Logger;
|
||||||
|
constructor(client: Client, saleorApiUrl: string) {
|
||||||
|
this.avataxConfigurationService = new AvataxConfigurationService(client, saleorApiUrl);
|
||||||
|
this.taxJarConfigurationService = new TaxJarConfigurationService(client, saleorApiUrl);
|
||||||
|
this.logger = createLogger({
|
||||||
|
service: "TaxProvidersConfigurationService",
|
||||||
|
metadataKey: TAX_PROVIDER_KEY,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll() {
|
||||||
|
this.logger.debug(".getAll called");
|
||||||
|
const taxJar = await this.taxJarConfigurationService.getAll();
|
||||||
|
const avatax = await this.avataxConfigurationService.getAll();
|
||||||
|
// todo: add more clever way of joining the two. Maybe add updated_at date to the config and use it to sort?
|
||||||
|
return [...taxJar, ...avatax];
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,7 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { logger as pinoLogger } from "../../lib/logger";
|
import { logger as pinoLogger } from "../../lib/logger";
|
||||||
import { createId } from "../../lib/utils";
|
|
||||||
import { createSettingsManager } from "../app-configuration/metadata-manager";
|
|
||||||
import { ActiveTaxProvider } from "../taxes/active-tax-provider";
|
|
||||||
import { protectedClientProcedure } from "../trpc/protected-client-procedure";
|
import { protectedClientProcedure } from "../trpc/protected-client-procedure";
|
||||||
import { router } from "../trpc/trpc-server";
|
import { router } from "../trpc/trpc-server";
|
||||||
import { GetProvidersConfigurationService } from "./get-providers-configuration.service";
|
import { TaxProvidersConfigurationService } from "./providers-configuration-service";
|
||||||
import { ProvidersConfig } from "./providers-config";
|
|
||||||
import {
|
|
||||||
createProviderInstanceInputSchema,
|
|
||||||
deleteProviderInstanceInputSchema,
|
|
||||||
updateProviderInstanceInputSchema,
|
|
||||||
} from "./providers-config-input-schema";
|
|
||||||
import { TaxProvidersConfigurator } from "./providers-configurator";
|
|
||||||
|
|
||||||
export const providersConfigurationRouter = router({
|
export const providersConfigurationRouter = router({
|
||||||
getAll: protectedClientProcedure.query(async ({ ctx }) => {
|
getAll: protectedClientProcedure.query(async ({ ctx }) => {
|
||||||
|
@ -20,128 +9,6 @@ export const providersConfigurationRouter = router({
|
||||||
|
|
||||||
logger.debug("providersConfigurationRouter.fetch called");
|
logger.debug("providersConfigurationRouter.fetch called");
|
||||||
|
|
||||||
return new GetProvidersConfigurationService({
|
return new TaxProvidersConfigurationService(ctx.apiClient, ctx.saleorApiUrl).getAll();
|
||||||
apiClient: ctx.apiClient,
|
|
||||||
saleorApiUrl: ctx.saleorApiUrl,
|
|
||||||
}).getConfiguration();
|
|
||||||
}),
|
}),
|
||||||
update: protectedClientProcedure
|
|
||||||
.input(updateProviderInstanceInputSchema)
|
|
||||||
.mutation(async ({ ctx, input }) => {
|
|
||||||
const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl });
|
|
||||||
logger.info(input, "providersConfigurationRouter.update called with input:");
|
|
||||||
|
|
||||||
const currentProviders = await new GetProvidersConfigurationService({
|
|
||||||
apiClient: ctx.apiClient,
|
|
||||||
saleorApiUrl: ctx.saleorApiUrl,
|
|
||||||
}).getConfiguration();
|
|
||||||
|
|
||||||
const provider = currentProviders.find((provider) => provider.id === input.id);
|
|
||||||
|
|
||||||
if (provider) {
|
|
||||||
const taxProvider = new ActiveTaxProvider(provider);
|
|
||||||
const validation = await taxProvider.validate();
|
|
||||||
|
|
||||||
if (validation && !validation.ok) {
|
|
||||||
logger.error(validation.error, "External validation failed.");
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: validation.error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(currentProviders, "Fetched current providers:");
|
|
||||||
|
|
||||||
const taxProvidersConfigurator = new TaxProvidersConfigurator(
|
|
||||||
createSettingsManager(ctx.apiClient),
|
|
||||||
ctx.saleorApiUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
const nextProviders: ProvidersConfig = currentProviders.map((provider) => {
|
|
||||||
if (provider.id === input.id) {
|
|
||||||
return {
|
|
||||||
...input.provider,
|
|
||||||
id: input.id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider;
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info(nextProviders, "Will update providers with the following value:");
|
|
||||||
|
|
||||||
await taxProvidersConfigurator.setConfig(nextProviders);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}),
|
|
||||||
delete: protectedClientProcedure
|
|
||||||
.input(deleteProviderInstanceInputSchema)
|
|
||||||
.mutation(async ({ ctx, input }) => {
|
|
||||||
const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl });
|
|
||||||
logger.info(input, "providersConfigurationRouter.delete called with input:");
|
|
||||||
|
|
||||||
const currentProviders = await new GetProvidersConfigurationService({
|
|
||||||
apiClient: ctx.apiClient,
|
|
||||||
saleorApiUrl: ctx.saleorApiUrl,
|
|
||||||
}).getConfiguration();
|
|
||||||
|
|
||||||
logger.info(currentProviders, "Fetched current providers:");
|
|
||||||
|
|
||||||
const taxProvidersConfigurator = new TaxProvidersConfigurator(
|
|
||||||
createSettingsManager(ctx.apiClient),
|
|
||||||
ctx.saleorApiUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
const nextProviders: ProvidersConfig = currentProviders.filter(
|
|
||||||
(provider) => provider.id !== input.id
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.info(nextProviders, "Will update providers with the following value:");
|
|
||||||
|
|
||||||
await taxProvidersConfigurator.setConfig(nextProviders);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}),
|
|
||||||
create: protectedClientProcedure
|
|
||||||
.input(createProviderInstanceInputSchema)
|
|
||||||
.mutation(async ({ ctx, input }) => {
|
|
||||||
const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl });
|
|
||||||
logger.info(input, "providersConfigurationRouter.create called with input:");
|
|
||||||
|
|
||||||
const currentProviders = await new GetProvidersConfigurationService({
|
|
||||||
apiClient: ctx.apiClient,
|
|
||||||
saleorApiUrl: ctx.saleorApiUrl,
|
|
||||||
}).getConfiguration();
|
|
||||||
|
|
||||||
logger.info(currentProviders, "Fetched current providers:");
|
|
||||||
|
|
||||||
const taxProvidersConfigurator = new TaxProvidersConfigurator(
|
|
||||||
createSettingsManager(ctx.apiClient),
|
|
||||||
ctx.saleorApiUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
const id = createId();
|
|
||||||
const provider = { ...input.provider, id };
|
|
||||||
const nextProviders: ProvidersConfig = [...currentProviders, provider];
|
|
||||||
|
|
||||||
if (provider) {
|
|
||||||
const taxProvider = new ActiveTaxProvider(provider);
|
|
||||||
const validation = await taxProvider.validate();
|
|
||||||
|
|
||||||
if (validation && !validation.ok) {
|
|
||||||
logger.error(validation.error, "External validation failed.");
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: validation.error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(nextProviders, "Will update providers with the following value:");
|
|
||||||
|
|
||||||
await taxProvidersConfigurator.setConfig(nextProviders);
|
|
||||||
|
|
||||||
return { id };
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
|
||||||
import { PrivateMetadataAppConfigurator } from "../app-configuration/app-configurator";
|
|
||||||
import { ProvidersConfig } from "./providers-config";
|
|
||||||
|
|
||||||
export class TaxProvidersConfigurator extends PrivateMetadataAppConfigurator<ProvidersConfig> {
|
|
||||||
constructor(metadataManager: SettingsManager, saleorApiUrl: string) {
|
|
||||||
super(metadataManager, saleorApiUrl, "tax-providers");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { FormControlLabel, Grid, Radio, RadioGroup, Typography } from "@material-ui/core";
|
import { FormControlLabel, Grid, Radio, RadioGroup, Typography } from "@material-ui/core";
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
import { makeStyles } from "@saleor/macaw-ui";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { AvataxConfiguration } from "../../taxes/providers/avatax/ui/avatax-configuration";
|
import { AvataxConfiguration } from "../../avatax/ui/avatax-configuration";
|
||||||
import { providerConfig, TaxProviderName } from "../../taxes/providers/config";
|
import { providerConfig, TaxProviderName } from "../../taxes/providers/config";
|
||||||
import { TaxJarConfiguration } from "../../taxes/providers/taxjar/ui/taxjar-configuration";
|
import { TaxJarConfiguration } from "../../taxjar/ui/taxjar-configuration";
|
||||||
import { useInstanceId } from "../../taxes/tax-context";
|
import { useInstanceId } from "../../taxes/tax-context";
|
||||||
import { trpcClient } from "../../trpc/trpc-client";
|
import { trpcClient } from "../../trpc/trpc-client";
|
||||||
import { AppPaper } from "../../ui/app-paper";
|
import { AppPaper } from "../../ui/app-paper";
|
||||||
|
|
|
@ -58,7 +58,7 @@ export const TaxProvidersInstancesList = () => {
|
||||||
key={instance.id}
|
key={instance.id}
|
||||||
>
|
>
|
||||||
<OffsettedListItemCell className={styles.cell}>
|
<OffsettedListItemCell className={styles.cell}>
|
||||||
{instance.name}
|
{instance.config.name}
|
||||||
<ProviderIcon size="medium" provider={instance.provider} />
|
<ProviderIcon size="medium" provider={instance.provider} />
|
||||||
</OffsettedListItemCell>
|
</OffsettedListItemCell>
|
||||||
</OffsettedListItem>
|
</OffsettedListItem>
|
||||||
|
|
|
@ -2,8 +2,8 @@ import { TaxBaseFragment } from "../../../generated/graphql";
|
||||||
import { createLogger } from "../../lib/logger";
|
import { createLogger } from "../../lib/logger";
|
||||||
import { ChannelConfig } from "../channels-configuration/channels-config";
|
import { ChannelConfig } from "../channels-configuration/channels-config";
|
||||||
import { ProviderConfig } from "../providers-configuration/providers-config";
|
import { ProviderConfig } from "../providers-configuration/providers-config";
|
||||||
import { AvataxProvider } from "./providers/avatax/avatax-provider";
|
import { AvataxProvider } from "../avatax/avatax-provider";
|
||||||
import { TaxJarProvider } from "./providers/taxjar/taxjar-provider";
|
import { TaxJarProvider } from "../taxjar/taxjar-provider";
|
||||||
import { TaxProvider } from "./tax-provider";
|
import { TaxProvider } from "./tax-provider";
|
||||||
import { TaxProviderError } from "./tax-provider-error";
|
import { TaxProviderError } from "./tax-provider-error";
|
||||||
|
|
||||||
|
@ -36,8 +36,4 @@ export class ActiveTaxProvider {
|
||||||
async calculate(payload: TaxBaseFragment, channel: ChannelConfig) {
|
async calculate(payload: TaxBaseFragment, channel: ChannelConfig) {
|
||||||
return this.client.calculate(payload, channel);
|
return this.client.calculate(payload, channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate() {
|
|
||||||
return this.client.validate?.();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
import { z } from "zod";
|
|
||||||
import { obfuscateSecret } from "../../../../lib/utils";
|
|
||||||
import { createProviderInstanceSchema } from "../../tax-common-schema";
|
|
||||||
|
|
||||||
const avataxConfigSchema = z.object({
|
|
||||||
username: z.string().min(1, { message: "Username requires at least one character." }),
|
|
||||||
password: z.string().min(1, { message: "Password requires at least one character." }),
|
|
||||||
isSandbox: z.boolean(),
|
|
||||||
companyName: z.string().min(1, { message: "Company name requires at least one character." }),
|
|
||||||
isAutocommit: z.boolean(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type AvataxConfig = z.infer<typeof avataxConfigSchema>;
|
|
||||||
|
|
||||||
export const defaultAvataxConfig: AvataxConfig = {
|
|
||||||
username: "",
|
|
||||||
password: "",
|
|
||||||
companyName: "",
|
|
||||||
isSandbox: true,
|
|
||||||
isAutocommit: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const avataxInstanceConfigSchema = createProviderInstanceSchema(
|
|
||||||
"avatax",
|
|
||||||
avataxConfigSchema
|
|
||||||
);
|
|
||||||
|
|
||||||
const transformedAvataxConfigSchema = avataxConfigSchema.transform((config) => {
|
|
||||||
return {
|
|
||||||
...config,
|
|
||||||
username: obfuscateSecret(config.username),
|
|
||||||
password: obfuscateSecret(config.username),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const serverAvataxSchema = createProviderInstanceSchema(
|
|
||||||
"avatax",
|
|
||||||
transformedAvataxConfigSchema
|
|
||||||
);
|
|
|
@ -6,7 +6,7 @@ export const providerConfig = {
|
||||||
icon: TaxJarIcon,
|
icon: TaxJarIcon,
|
||||||
},
|
},
|
||||||
avatax: {
|
avatax: {
|
||||||
label: "Avalara",
|
label: "Avatax",
|
||||||
icon: AvataxIcon,
|
icon: AvataxIcon,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { z } from "zod";
|
|
||||||
import { obfuscateSecret } from "../../../../lib/utils";
|
|
||||||
import { createProviderInstanceSchema } from "../../tax-common-schema";
|
|
||||||
|
|
||||||
export const taxJarConfigSchema = z.object({
|
|
||||||
apiKey: z.string().min(1, { message: "API Key requires at least one character." }),
|
|
||||||
isSandbox: z.boolean(),
|
|
||||||
});
|
|
||||||
export type TaxJarConfig = z.infer<typeof taxJarConfigSchema>;
|
|
||||||
|
|
||||||
export const defaultTaxJarConfig: TaxJarConfig = {
|
|
||||||
apiKey: "",
|
|
||||||
isSandbox: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const taxJarInstanceConfigSchema = createProviderInstanceSchema(
|
|
||||||
"taxjar",
|
|
||||||
taxJarConfigSchema
|
|
||||||
);
|
|
||||||
|
|
||||||
const transformedTaxJarConfigSchema = taxJarConfigSchema.transform((config) => {
|
|
||||||
return {
|
|
||||||
...config,
|
|
||||||
apiKey: obfuscateSecret(config.apiKey),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const serverTaxJarSchema = createProviderInstanceSchema(
|
|
||||||
"taxjar",
|
|
||||||
transformedTaxJarConfigSchema
|
|
||||||
);
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { z, ZodTypeAny } from "zod";
|
import { z } from "zod";
|
||||||
import { TaxProviderName } from "./providers/config";
|
|
||||||
|
|
||||||
export const addressSchema = z.object({
|
export const addressSchema = z.object({
|
||||||
country: z.string(),
|
country: z.string(),
|
||||||
|
@ -8,19 +7,3 @@ export const addressSchema = z.object({
|
||||||
city: z.string(),
|
city: z.string(),
|
||||||
street: z.string(),
|
street: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const baseProviderInstanceSchema = z.object({
|
|
||||||
name: z.string().min(1),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const createProviderInstanceSchema = <
|
|
||||||
TConfig extends ZodTypeAny,
|
|
||||||
TProvider extends TaxProviderName
|
|
||||||
>(
|
|
||||||
provider: TProvider,
|
|
||||||
schema: TConfig
|
|
||||||
) =>
|
|
||||||
baseProviderInstanceSchema.extend({
|
|
||||||
provider: z.literal(provider),
|
|
||||||
config: schema,
|
|
||||||
});
|
|
||||||
|
|
|
@ -8,5 +8,4 @@ type ExternalValidationResult = { ok: boolean; error?: string };
|
||||||
export interface TaxProvider {
|
export interface TaxProvider {
|
||||||
name: TaxProviderName;
|
name: TaxProviderName;
|
||||||
calculate: (payload: TaxBaseFragment, channel: ChannelConfig) => Promise<ResponseTaxPayload>;
|
calculate: (payload: TaxBaseFragment, channel: ChannelConfig) => Promise<ResponseTaxPayload>;
|
||||||
validate?: () => Promise<ExternalValidationResult>;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,10 @@ import {
|
||||||
TaxBaseFragment,
|
TaxBaseFragment,
|
||||||
TaxBaseLineFragment,
|
TaxBaseLineFragment,
|
||||||
TaxDiscountFragment,
|
TaxDiscountFragment,
|
||||||
} from "../../../../../generated/graphql";
|
} from "../../../generated/graphql";
|
||||||
import { ChannelConfig } from "../../../channels-configuration/channels-config";
|
import { ChannelConfig } from "../channels-configuration/channels-config";
|
||||||
import { taxLineResolver } from "../../tax-line-resolver";
|
import { taxLineResolver } from "../taxes/tax-line-resolver";
|
||||||
import { ResponseTaxPayload } from "../../types";
|
import { ResponseTaxPayload } from "../taxes/types";
|
||||||
|
|
||||||
const formatCalculatedAmount = (amount: number) => {
|
const formatCalculatedAmount = (amount: number) => {
|
||||||
return Number(amount.toFixed(2));
|
return Number(amount.toFixed(2));
|
|
@ -1,6 +1,6 @@
|
||||||
import TaxJar from "taxjar";
|
import TaxJar from "taxjar";
|
||||||
import { Config, TaxForOrderRes, TaxParams } from "taxjar/dist/util/types";
|
import { Config, TaxForOrderRes, TaxParams } from "taxjar/dist/util/types";
|
||||||
import { logger } from "../../../../lib/logger";
|
import { logger } from "../../lib/logger";
|
||||||
import { TaxJarConfig } from "./taxjar-config";
|
import { TaxJarConfig } from "./taxjar-config";
|
||||||
|
|
||||||
const createTaxJarSettings = (config: TaxJarConfig): Config => {
|
const createTaxJarSettings = (config: TaxJarConfig): Config => {
|
||||||
|
@ -27,4 +27,16 @@ export class TaxJarClient {
|
||||||
const response: TaxForOrderRes = await this.client.taxForOrder(params);
|
const response: TaxForOrderRes = await this.client.taxForOrder(params);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async ping() {
|
||||||
|
try {
|
||||||
|
await this.client.categories();
|
||||||
|
return { authenticated: true };
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
authenticated: false,
|
||||||
|
error: "TaxJar was not able to authenticate with the provided credentials.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
23
apps/taxes/src/modules/taxjar/taxjar-config.ts
Normal file
23
apps/taxes/src/modules/taxjar/taxjar-config.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
import { obfuscateSecret } from "../../lib/utils";
|
||||||
|
|
||||||
|
export const taxJarConfigSchema = z.object({
|
||||||
|
name: z.string().min(1, { message: "Name requires at least one character." }),
|
||||||
|
apiKey: z.string().min(1, { message: "API Key requires at least one character." }),
|
||||||
|
isSandbox: z.boolean(),
|
||||||
|
});
|
||||||
|
export type TaxJarConfig = z.infer<typeof taxJarConfigSchema>;
|
||||||
|
|
||||||
|
export const defaultTaxJarConfig: TaxJarConfig = {
|
||||||
|
name: "",
|
||||||
|
apiKey: "",
|
||||||
|
isSandbox: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const taxJarInstanceConfigSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
provider: z.literal("taxjar"),
|
||||||
|
config: taxJarConfigSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TaxJarInstanceConfig = z.infer<typeof taxJarInstanceConfigSchema>;
|
116
apps/taxes/src/modules/taxjar/taxjar-configuration.router.ts
Normal file
116
apps/taxes/src/modules/taxjar/taxjar-configuration.router.ts
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
import { logger as pinoLogger } from "../../lib/logger";
|
||||||
|
import { protectedClientProcedure } from "../trpc/protected-client-procedure";
|
||||||
|
import { router } from "../trpc/trpc-server";
|
||||||
|
import { taxJarConfigSchema } from "./taxjar-config";
|
||||||
|
import { TaxJarConfigurationService } from "./taxjar-configuration.service";
|
||||||
|
|
||||||
|
const getInputSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteInputSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const patchInputSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
value: taxJarConfigSchema.partial(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const putInputSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
value: taxJarConfigSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
const postInputSchema = z.object({
|
||||||
|
value: taxJarConfigSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const taxjarConfigurationRouter = router({
|
||||||
|
get: protectedClientProcedure.input(getInputSchema).query(async ({ ctx, input }) => {
|
||||||
|
const logger = pinoLogger.child({
|
||||||
|
saleorApiUrl: ctx.saleorApiUrl,
|
||||||
|
procedure: "taxjarConfigurationRouter.get",
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug("taxjarConfigurationRouter.get called");
|
||||||
|
|
||||||
|
const { apiClient, saleorApiUrl } = ctx;
|
||||||
|
const taxjarConfigurationService = new TaxJarConfigurationService(apiClient, saleorApiUrl);
|
||||||
|
|
||||||
|
const result = await taxjarConfigurationService.get(input.id);
|
||||||
|
|
||||||
|
logger.debug({ result }, "taxjarConfigurationRouter.get finished");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
post: protectedClientProcedure.input(postInputSchema).mutation(async ({ ctx, input }) => {
|
||||||
|
const logger = pinoLogger.child({
|
||||||
|
saleorApiUrl: ctx.saleorApiUrl,
|
||||||
|
procedure: "taxjarConfigurationRouter.post",
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug("taxjarConfigurationRouter.post called");
|
||||||
|
|
||||||
|
const { apiClient, saleorApiUrl } = ctx;
|
||||||
|
const taxjarConfigurationService = new TaxJarConfigurationService(apiClient, saleorApiUrl);
|
||||||
|
|
||||||
|
const result = await taxjarConfigurationService.post(input.value);
|
||||||
|
|
||||||
|
logger.debug({ result }, "taxjarConfigurationRouter.post finished");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
delete: protectedClientProcedure.input(deleteInputSchema).mutation(async ({ ctx, input }) => {
|
||||||
|
const logger = pinoLogger.child({
|
||||||
|
saleorApiUrl: ctx.saleorApiUrl,
|
||||||
|
procedure: "taxjarConfigurationRouter.delete",
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug("taxjarConfigurationRouter.delete called");
|
||||||
|
|
||||||
|
const { apiClient, saleorApiUrl } = ctx;
|
||||||
|
const taxjarConfigurationService = new TaxJarConfigurationService(apiClient, saleorApiUrl);
|
||||||
|
|
||||||
|
const result = await taxjarConfigurationService.delete(input.id);
|
||||||
|
|
||||||
|
logger.debug({ result }, "taxjarConfigurationRouter.delete finished");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
patch: protectedClientProcedure.input(patchInputSchema).mutation(async ({ ctx, input }) => {
|
||||||
|
const logger = pinoLogger.child({
|
||||||
|
saleorApiUrl: ctx.saleorApiUrl,
|
||||||
|
procedure: "taxjarConfigurationRouter.patch",
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug("taxjarConfigurationRouter.patch called");
|
||||||
|
|
||||||
|
const { apiClient, saleorApiUrl } = ctx;
|
||||||
|
const taxjarConfigurationService = new TaxJarConfigurationService(apiClient, saleorApiUrl);
|
||||||
|
|
||||||
|
const result = await taxjarConfigurationService.patch(input.id, input.value);
|
||||||
|
|
||||||
|
logger.debug({ result }, "taxjarConfigurationRouter.patch finished");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
put: protectedClientProcedure.input(putInputSchema).mutation(async ({ ctx, input }) => {
|
||||||
|
const logger = pinoLogger.child({
|
||||||
|
saleorApiUrl: ctx.saleorApiUrl,
|
||||||
|
procedure: "taxjarConfigurationRouter.put",
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug("taxjarConfigurationRouter.put called");
|
||||||
|
|
||||||
|
const { apiClient, saleorApiUrl } = ctx;
|
||||||
|
const taxjarConfigurationService = new TaxJarConfigurationService(apiClient, saleorApiUrl);
|
||||||
|
|
||||||
|
const result = await taxjarConfigurationService.put(input.id, input.value);
|
||||||
|
|
||||||
|
logger.debug({ result }, "taxjarConfigurationRouter.put finished");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
});
|
153
apps/taxes/src/modules/taxjar/taxjar-configuration.service.ts
Normal file
153
apps/taxes/src/modules/taxjar/taxjar-configuration.service.ts
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
import pino from "pino";
|
||||||
|
import { Client } from "urql";
|
||||||
|
import { createLogger } from "../../lib/logger";
|
||||||
|
import { isObfuscated, obfuscateSecret } from "../../lib/utils";
|
||||||
|
import { createSettingsManager } from "../app-configuration/metadata-manager";
|
||||||
|
import { CrudSettingsConfigurator } from "../crud-settings/crud-settings.service";
|
||||||
|
import { providersSchema } from "../providers-configuration/providers-config";
|
||||||
|
import { TAX_PROVIDER_KEY } from "../providers-configuration/providers-configuration-service";
|
||||||
|
import { TaxJarClient } from "./taxjar-client";
|
||||||
|
import {
|
||||||
|
TaxJarConfig,
|
||||||
|
taxJarConfigSchema,
|
||||||
|
TaxJarInstanceConfig,
|
||||||
|
taxJarInstanceConfigSchema,
|
||||||
|
} from "./taxjar-config";
|
||||||
|
|
||||||
|
const obfuscateConfig = (config: TaxJarConfig) => ({
|
||||||
|
...config,
|
||||||
|
apiKey: obfuscateSecret(config.apiKey),
|
||||||
|
});
|
||||||
|
|
||||||
|
const obfuscateProvidersConfig = (instances: TaxJarInstanceConfig[]) =>
|
||||||
|
instances.map((instance) => ({
|
||||||
|
...instance,
|
||||||
|
config: obfuscateConfig(instance.config),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const getSchema = taxJarInstanceConfigSchema.transform((instance) => ({
|
||||||
|
...instance,
|
||||||
|
config: obfuscateConfig(instance.config),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const patchSchema = taxJarConfigSchema.partial().transform((c) => {
|
||||||
|
const { apiKey, ...config } = c ?? {};
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
...(apiKey && !isObfuscated(apiKey) && { apiKey }),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const putSchema = taxJarConfigSchema.transform((c) => {
|
||||||
|
const { apiKey, ...config } = c;
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
...(!isObfuscated(apiKey) && { apiKey }),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export class TaxJarConfigurationService {
|
||||||
|
private crudSettingsConfigurator: CrudSettingsConfigurator;
|
||||||
|
private logger: pino.Logger;
|
||||||
|
constructor(client: Client, saleorApiUrl: string) {
|
||||||
|
const settingsManager = createSettingsManager(client);
|
||||||
|
this.crudSettingsConfigurator = new CrudSettingsConfigurator(
|
||||||
|
settingsManager,
|
||||||
|
saleorApiUrl,
|
||||||
|
TAX_PROVIDER_KEY
|
||||||
|
);
|
||||||
|
this.logger = createLogger({
|
||||||
|
service: "TaxJarConfigurationService",
|
||||||
|
metadataKey: TAX_PROVIDER_KEY,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll() {
|
||||||
|
this.logger.debug(".getAll called");
|
||||||
|
const { data } = await this.crudSettingsConfigurator.readAll();
|
||||||
|
this.logger.debug({ settings: data }, `Fetched settings from crudSettingsConfigurator`);
|
||||||
|
const validation = providersSchema.safeParse(data);
|
||||||
|
|
||||||
|
if (!validation.success) {
|
||||||
|
this.logger.error({ error: validation.error.format() }, "Validation error while getAll");
|
||||||
|
throw new Error(validation.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const instances = validation.data.filter(
|
||||||
|
(instance) => instance.provider === "taxjar"
|
||||||
|
) as TaxJarInstanceConfig[];
|
||||||
|
|
||||||
|
return obfuscateProvidersConfig(instances);
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(id: string) {
|
||||||
|
this.logger.debug(`.get called with id: ${id}`);
|
||||||
|
const { data } = await this.crudSettingsConfigurator.read(id);
|
||||||
|
this.logger.debug({ setting: data }, `Fetched setting from crudSettingsConfigurator`);
|
||||||
|
|
||||||
|
const validation = getSchema.safeParse(data);
|
||||||
|
|
||||||
|
if (!validation.success) {
|
||||||
|
this.logger.error({ error: validation.error.format() }, "Validation error while get");
|
||||||
|
throw new Error(validation.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return validation.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async post(config: TaxJarConfig) {
|
||||||
|
this.logger.debug(`.post called with value: ${JSON.stringify(config)}`);
|
||||||
|
const taxJarClient = new TaxJarClient(config);
|
||||||
|
const validation = await taxJarClient.ping();
|
||||||
|
|
||||||
|
if (!validation.authenticated) {
|
||||||
|
this.logger.error({ error: validation.error }, "Validation error while post");
|
||||||
|
throw new Error(validation.error);
|
||||||
|
}
|
||||||
|
return this.crudSettingsConfigurator.create({
|
||||||
|
provider: "taxjar",
|
||||||
|
config: config,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async patch(id: string, config: Partial<TaxJarConfig>) {
|
||||||
|
this.logger.debug(`.patch called with id: ${id} and value: ${JSON.stringify(config)}`);
|
||||||
|
const result = await this.get(id);
|
||||||
|
// omit the key "id" from the result
|
||||||
|
const { id: _, ...setting } = result;
|
||||||
|
const validation = patchSchema.safeParse(config);
|
||||||
|
|
||||||
|
if (!validation.success) {
|
||||||
|
this.logger.error({ error: validation.error.format() }, "Validation error while patch");
|
||||||
|
throw new Error(validation.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.crudSettingsConfigurator.update(id, {
|
||||||
|
...setting,
|
||||||
|
config: { ...setting.config, ...validation.data },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async put(id: string, config: TaxJarConfig) {
|
||||||
|
const result = await this.get(id);
|
||||||
|
// omit the key "id" from the result
|
||||||
|
const { id: _, ...setting } = result;
|
||||||
|
const validation = putSchema.safeParse(config);
|
||||||
|
|
||||||
|
if (!validation.success) {
|
||||||
|
this.logger.error({ error: validation.error.format() }, "Validation error while patch");
|
||||||
|
throw new Error(validation.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug(`.put called with id: ${id} and value: ${JSON.stringify(config)}`);
|
||||||
|
return this.crudSettingsConfigurator.update(id, {
|
||||||
|
...setting,
|
||||||
|
config: { ...validation.data },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(id: string) {
|
||||||
|
this.logger.debug(`.delete called with id: ${id}`);
|
||||||
|
return this.crudSettingsConfigurator.delete(id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,7 @@
|
||||||
import { TaxBaseFragment } from "../../../../../generated/graphql";
|
import { TaxBaseFragment } from "../../../generated/graphql";
|
||||||
import { logger } from "../../../../lib/logger";
|
import { logger } from "../../lib/logger";
|
||||||
import {
|
import { ChannelConfig, defaultChannelConfig } from "../channels-configuration/channels-config";
|
||||||
ChannelConfig,
|
import { TaxProvider } from "../taxes/tax-provider";
|
||||||
defaultChannelConfig,
|
|
||||||
} from "../../../channels-configuration/channels-config";
|
|
||||||
import { TaxProvider } from "../../tax-provider";
|
|
||||||
import { taxJarCalculate } from "./taxjar-calculate";
|
import { taxJarCalculate } from "./taxjar-calculate";
|
||||||
import { TaxJarClient } from "./taxjar-client";
|
import { TaxJarClient } from "./taxjar-client";
|
||||||
import { defaultTaxJarConfig, TaxJarConfig } from "./taxjar-config";
|
import { defaultTaxJarConfig, TaxJarConfig } from "./taxjar-config";
|
|
@ -13,9 +13,9 @@ import { Button, makeStyles } from "@saleor/macaw-ui";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { trpcClient } from "../../../../trpc/trpc-client";
|
import { trpcClient } from "../../trpc/trpc-client";
|
||||||
import { useInstanceId } from "../../../tax-context";
|
import { useInstanceId } from "../../taxes/tax-context";
|
||||||
import { taxJarInstanceConfigSchema } from "../taxjar-config";
|
import { taxJarConfigSchema, taxJarInstanceConfigSchema } from "../taxjar-config";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
reverseRow: {
|
reverseRow: {
|
||||||
|
@ -25,15 +25,13 @@ const useStyles = makeStyles((theme) => ({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const schema = taxJarInstanceConfigSchema.omit({ provider: true });
|
const schema = taxJarConfigSchema;
|
||||||
type FormValues = z.infer<typeof schema>;
|
type FormValues = z.infer<typeof schema>;
|
||||||
|
|
||||||
const defaultValues: FormValues = {
|
const defaultValues: FormValues = {
|
||||||
config: {
|
|
||||||
apiKey: "",
|
|
||||||
isSandbox: false,
|
|
||||||
},
|
|
||||||
name: "",
|
name: "",
|
||||||
|
apiKey: "",
|
||||||
|
isSandbox: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TaxJarConfigurationForm = () => {
|
export const TaxJarConfigurationForm = () => {
|
||||||
|
@ -51,16 +49,36 @@ export const TaxJarConfigurationForm = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const { refetch: refetchChannelConfigurationData } =
|
const { refetch: refetchChannelConfigurationData } =
|
||||||
trpcClient.channelsConfiguration.fetch.useQuery();
|
trpcClient.channelsConfiguration.fetch.useQuery(undefined, {
|
||||||
|
onError(error) {
|
||||||
|
appBridge?.dispatch(
|
||||||
|
actions.Notification({
|
||||||
|
title: "Error",
|
||||||
|
text: error.message,
|
||||||
|
status: "error",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const { data: providersConfigurationData, refetch: refetchProvidersConfigurationData } =
|
const { data: providersConfigurationData, refetch: refetchProvidersConfigurationData } =
|
||||||
trpcClient.providersConfiguration.getAll.useQuery();
|
trpcClient.providersConfiguration.getAll.useQuery(undefined, {
|
||||||
|
onError(error) {
|
||||||
|
appBridge?.dispatch(
|
||||||
|
actions.Notification({
|
||||||
|
title: "Error",
|
||||||
|
text: error.message,
|
||||||
|
status: "error",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const instance = providersConfigurationData?.find((instance) => instance.id === instanceId);
|
const instance = providersConfigurationData?.find((instance) => instance.id === instanceId);
|
||||||
|
|
||||||
const { mutate: createMutation, isLoading: isCreateLoading } =
|
const { mutate: createMutation, isLoading: isCreateLoading } =
|
||||||
trpcClient.providersConfiguration.create.useMutation({
|
trpcClient.taxJarConfiguration.post.useMutation({
|
||||||
onSuccess({ id }) {
|
onSuccess({ data: { id } }) {
|
||||||
setInstanceId(id);
|
setInstanceId(id);
|
||||||
refetchProvidersConfigurationData();
|
refetchProvidersConfigurationData();
|
||||||
refetchChannelConfigurationData();
|
refetchChannelConfigurationData();
|
||||||
|
@ -84,7 +102,7 @@ export const TaxJarConfigurationForm = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate: updateMutation, isLoading: isUpdateLoading } =
|
const { mutate: updateMutation, isLoading: isUpdateLoading } =
|
||||||
trpcClient.providersConfiguration.update.useMutation({
|
trpcClient.taxJarConfiguration.patch.useMutation({
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
refetchProvidersConfigurationData();
|
refetchProvidersConfigurationData();
|
||||||
refetchChannelConfigurationData();
|
refetchChannelConfigurationData();
|
||||||
|
@ -108,7 +126,7 @@ export const TaxJarConfigurationForm = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate: deleteMutation, isLoading: isDeleteLoading } =
|
const { mutate: deleteMutation, isLoading: isDeleteLoading } =
|
||||||
trpcClient.providersConfiguration.delete.useMutation({
|
trpcClient.taxJarConfiguration.delete.useMutation({
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
resetInstanceId();
|
resetInstanceId();
|
||||||
refetchProvidersConfigurationData();
|
refetchProvidersConfigurationData();
|
||||||
|
@ -134,8 +152,8 @@ export const TaxJarConfigurationForm = () => {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (instance) {
|
if (instance) {
|
||||||
const { provider, id, ...values } = instance;
|
const { config } = instance;
|
||||||
reset(values);
|
reset(config);
|
||||||
} else {
|
} else {
|
||||||
reset({ ...defaultValues });
|
reset({ ...defaultValues });
|
||||||
}
|
}
|
||||||
|
@ -145,21 +163,15 @@ export const TaxJarConfigurationForm = () => {
|
||||||
fullWidth: true,
|
fullWidth: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = (values: FormValues) => {
|
const onSubmit = (value: FormValues) => {
|
||||||
if (instanceId) {
|
if (instanceId) {
|
||||||
updateMutation({
|
updateMutation({
|
||||||
id: instanceId,
|
id: instanceId,
|
||||||
provider: {
|
value,
|
||||||
...values,
|
|
||||||
provider: "taxjar",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
createMutation({
|
createMutation({
|
||||||
provider: {
|
value,
|
||||||
...values,
|
|
||||||
provider: "taxjar",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -200,22 +212,22 @@ export const TaxJarConfigurationForm = () => {
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Controller
|
<Controller
|
||||||
name="config.apiKey"
|
name="apiKey"
|
||||||
control={control}
|
control={control}
|
||||||
defaultValue={defaultValues.config.apiKey}
|
defaultValue={defaultValues.apiKey}
|
||||||
render={({ field }) => <TextField label="API Key" {...field} {...textFieldProps} />}
|
render={({ field }) => <TextField label="API Key" {...field} {...textFieldProps} />}
|
||||||
/>
|
/>
|
||||||
{formState.errors.config?.apiKey && (
|
{formState.errors?.apiKey && (
|
||||||
<FormHelperText error>{formState.errors.config?.apiKey.message}</FormHelperText>
|
<FormHelperText error>{formState.errors?.apiKey.message}</FormHelperText>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<InputLabel>
|
<InputLabel>
|
||||||
Sandbox
|
Sandbox
|
||||||
<Controller
|
<Controller
|
||||||
name={"config.isSandbox"}
|
name={"isSandbox"}
|
||||||
control={control}
|
control={control}
|
||||||
defaultValue={defaultValues.config.isSandbox}
|
defaultValue={defaultValues.isSandbox}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Switch
|
<Switch
|
||||||
{...field}
|
{...field}
|
|
@ -2,11 +2,15 @@ import { channelsRouter } from "../channels/channels.router";
|
||||||
import { router } from "./trpc-server";
|
import { router } from "./trpc-server";
|
||||||
import { providersConfigurationRouter } from "../providers-configuration/providers-configuration.router";
|
import { providersConfigurationRouter } from "../providers-configuration/providers-configuration.router";
|
||||||
import { channelsConfigurationRouter } from "../channels-configuration/channels-configuration.router";
|
import { channelsConfigurationRouter } from "../channels-configuration/channels-configuration.router";
|
||||||
|
import { taxjarConfigurationRouter } from "../taxjar/taxjar-configuration.router";
|
||||||
|
import { avataxConfigurationRouter } from "../avatax/avatax-configuration.router";
|
||||||
|
|
||||||
export const appRouter = router({
|
export const appRouter = router({
|
||||||
channels: channelsRouter,
|
channels: channelsRouter,
|
||||||
providersConfiguration: providersConfigurationRouter,
|
providersConfiguration: providersConfigurationRouter,
|
||||||
channelsConfiguration: channelsConfigurationRouter,
|
channelsConfiguration: channelsConfigurationRouter,
|
||||||
|
taxJarConfiguration: taxjarConfigurationRouter,
|
||||||
|
avataxConfiguration: avataxConfigurationRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AppRouter = typeof appRouter;
|
export type AppRouter = typeof appRouter;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { createClient } from "../../../lib/graphql";
|
||||||
import { createLogger } from "../../../lib/logger";
|
import { createLogger } from "../../../lib/logger";
|
||||||
import { calculateTaxesPayloadSchema, ExpectedWebhookPayload } from "../../../lib/saleor/schema";
|
import { calculateTaxesPayloadSchema, ExpectedWebhookPayload } from "../../../lib/saleor/schema";
|
||||||
import { GetChannelsConfigurationService } from "../../../modules/channels-configuration/get-channels-configuration.service";
|
import { GetChannelsConfigurationService } from "../../../modules/channels-configuration/get-channels-configuration.service";
|
||||||
import { GetProvidersConfigurationService } from "../../../modules/providers-configuration/get-providers-configuration.service";
|
import { TaxProvidersConfigurationService } from "../../../modules/providers-configuration/providers-configuration-service";
|
||||||
import { ActiveTaxProvider } from "../../../modules/taxes/active-tax-provider";
|
import { ActiveTaxProvider } from "../../../modules/taxes/active-tax-provider";
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
|
@ -30,7 +30,7 @@ export default checkoutCalculateTaxesSyncWebhook.createHandler(async (req, res,
|
||||||
const validation = calculateTaxesPayloadSchema.safeParse(payload);
|
const validation = calculateTaxesPayloadSchema.safeParse(payload);
|
||||||
|
|
||||||
if (!validation.success) {
|
if (!validation.success) {
|
||||||
logger.error({ error: validation.error.message }, "Payload is invalid");
|
logger.error({ error: validation.error.format() }, "Payload is invalid");
|
||||||
logger.info("Returning no data");
|
logger.info("Returning no data");
|
||||||
return res.send({});
|
return res.send({});
|
||||||
}
|
}
|
||||||
|
@ -43,10 +43,10 @@ export default checkoutCalculateTaxesSyncWebhook.createHandler(async (req, res,
|
||||||
Promise.resolve({ token: authData.token })
|
Promise.resolve({ token: authData.token })
|
||||||
);
|
);
|
||||||
|
|
||||||
const providersConfig = await new GetProvidersConfigurationService({
|
const providersConfig = await new TaxProvidersConfigurationService(
|
||||||
saleorApiUrl: authData.saleorApiUrl,
|
client,
|
||||||
apiClient: client,
|
authData.saleorApiUrl
|
||||||
}).getConfiguration();
|
).getAll();
|
||||||
|
|
||||||
const channelsConfig = await new GetChannelsConfigurationService({
|
const channelsConfig = await new GetChannelsConfigurationService({
|
||||||
saleorApiUrl: authData.saleorApiUrl,
|
saleorApiUrl: authData.saleorApiUrl,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { createClient } from "../../../lib/graphql";
|
||||||
import { createLogger } from "../../../lib/logger";
|
import { createLogger } from "../../../lib/logger";
|
||||||
import { calculateTaxesPayloadSchema, ExpectedWebhookPayload } from "../../../lib/saleor/schema";
|
import { calculateTaxesPayloadSchema, ExpectedWebhookPayload } from "../../../lib/saleor/schema";
|
||||||
import { GetChannelsConfigurationService } from "../../../modules/channels-configuration/get-channels-configuration.service";
|
import { GetChannelsConfigurationService } from "../../../modules/channels-configuration/get-channels-configuration.service";
|
||||||
import { GetProvidersConfigurationService } from "../../../modules/providers-configuration/get-providers-configuration.service";
|
import { TaxProvidersConfigurationService } from "../../../modules/providers-configuration/providers-configuration-service";
|
||||||
import { ActiveTaxProvider } from "../../../modules/taxes/active-tax-provider";
|
import { ActiveTaxProvider } from "../../../modules/taxes/active-tax-provider";
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
|
@ -30,7 +30,7 @@ export default orderCalculateTaxesSyncWebhook.createHandler(async (req, res, ctx
|
||||||
const validation = calculateTaxesPayloadSchema.safeParse(payload);
|
const validation = calculateTaxesPayloadSchema.safeParse(payload);
|
||||||
|
|
||||||
if (!validation.success) {
|
if (!validation.success) {
|
||||||
logger.error({ error: validation.error.message }, "Payload is invalid");
|
logger.error({ error: validation.error.format() }, "Payload is invalid");
|
||||||
logger.info("Returning no data");
|
logger.info("Returning no data");
|
||||||
return res.status(200).json({});
|
return res.status(200).json({});
|
||||||
}
|
}
|
||||||
|
@ -42,11 +42,10 @@ export default orderCalculateTaxesSyncWebhook.createHandler(async (req, res, ctx
|
||||||
const client = createClient(authData.saleorApiUrl, async () =>
|
const client = createClient(authData.saleorApiUrl, async () =>
|
||||||
Promise.resolve({ token: authData.token })
|
Promise.resolve({ token: authData.token })
|
||||||
);
|
);
|
||||||
|
const providersConfig = await new TaxProvidersConfigurationService(
|
||||||
const providersConfig = await new GetProvidersConfigurationService({
|
client,
|
||||||
saleorApiUrl: authData.saleorApiUrl,
|
authData.saleorApiUrl
|
||||||
apiClient: client,
|
).getAll();
|
||||||
}).getConfiguration();
|
|
||||||
|
|
||||||
const channelsConfig = await new GetChannelsConfigurationService({
|
const channelsConfig = await new GetChannelsConfigurationService({
|
||||||
saleorApiUrl: authData.saleorApiUrl,
|
saleorApiUrl: authData.saleorApiUrl,
|
||||||
|
|
Loading…
Reference in a new issue