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 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 { LineItemModel } from "avatax/lib/models/LineItemModel";
|
||||
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 { taxLineResolver } from "../../tax-line-resolver";
|
||||
import { ResponseTaxPayload } from "../../types";
|
||||
import { ChannelConfig } from "../channels-configuration/channels-config";
|
||||
import { taxLineResolver } from "../taxes/tax-line-resolver";
|
||||
import { ResponseTaxPayload } from "../taxes/types";
|
||||
import { AvataxConfig } from "./avatax-config";
|
||||
|
||||
const SHIPPING_ITEM_CODE = "Shipping";
|
|
@ -1,7 +1,7 @@
|
|||
import Avatax from "avatax";
|
||||
import { CreateTransactionModel } from "avatax/lib/models/CreateTransactionModel";
|
||||
import packageJson from "../../../../../package.json";
|
||||
import { logger } from "../../../../lib/logger";
|
||||
import packageJson from "../../../package.json";
|
||||
import { logger } from "../../lib/logger";
|
||||
import { AvataxConfig } from "./avatax-config";
|
||||
|
||||
type AvataxSettings = {
|
||||
|
@ -55,6 +55,20 @@ export class AvataxClient {
|
|||
}
|
||||
|
||||
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 { logger } from "../../../../lib/logger";
|
||||
import { ChannelConfig } from "../../../channels-configuration/channels-config";
|
||||
import { TaxProvider } from "../../tax-provider";
|
||||
import { TaxBaseFragment } from "../../../generated/graphql";
|
||||
import { logger } from "../../lib/logger";
|
||||
import { ChannelConfig } from "../channels-configuration/channels-config";
|
||||
import { TaxProvider } from "../taxes/tax-provider";
|
||||
import { avataxCalculate } from "./avatax-calculate";
|
||||
import { AvataxClient } from "./avatax-client";
|
||||
import { AvataxConfig, defaultAvataxConfig } from "./avatax-config";
|
||||
|
@ -19,24 +19,6 @@ export class AvataxProvider implements TaxProvider {
|
|||
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) {
|
||||
logger.info("Avatax calculate");
|
||||
const model = avataxCalculate.preparePayload(payload, channel, this.config);
|
|
@ -13,10 +13,10 @@ import { Button, makeStyles } from "@saleor/macaw-ui";
|
|||
import React from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { trpcClient } from "../../../../trpc/trpc-client";
|
||||
import { AppLink } from "../../../../ui/app-link";
|
||||
import { useInstanceId } from "../../../tax-context";
|
||||
import { avataxInstanceConfigSchema } from "../avatax-config";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { AppLink } from "../../ui/app-link";
|
||||
import { useInstanceId } from "../../taxes/tax-context";
|
||||
import { avataxConfigSchema, avataxInstanceConfigSchema } from "../avatax-config";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
reverseRow: {
|
||||
|
@ -26,17 +26,15 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
const schema = avataxInstanceConfigSchema.omit({ provider: true });
|
||||
const schema = avataxConfigSchema;
|
||||
type FormValues = z.infer<typeof schema>;
|
||||
|
||||
const defaultValues: FormValues = {
|
||||
config: {
|
||||
companyName: "",
|
||||
isAutocommit: false,
|
||||
isSandbox: false,
|
||||
password: "",
|
||||
username: "",
|
||||
},
|
||||
companyName: "",
|
||||
isAutocommit: false,
|
||||
isSandbox: false,
|
||||
password: "",
|
||||
username: "",
|
||||
name: "",
|
||||
};
|
||||
|
||||
|
@ -50,11 +48,31 @@ export const AvataxConfigurationForm = () => {
|
|||
});
|
||||
const { instanceId, setInstanceId } = useInstanceId();
|
||||
const { refetch: refetchChannelConfigurationData } =
|
||||
trpcClient.channelsConfiguration.fetch.useQuery();
|
||||
const { data: providersConfigurationData, refetch: refetchProvidersConfigurationData } =
|
||||
trpcClient.providersConfiguration.getAll.useQuery();
|
||||
trpcClient.channelsConfiguration.fetch.useQuery(undefined, {
|
||||
onError(error) {
|
||||
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 = () => {
|
||||
setInstanceId(null);
|
||||
|
@ -62,16 +80,16 @@ export const AvataxConfigurationForm = () => {
|
|||
|
||||
React.useEffect(() => {
|
||||
if (instance) {
|
||||
const { provider, id, ...values } = instance;
|
||||
reset(values);
|
||||
const { config } = instance;
|
||||
reset(config);
|
||||
} else {
|
||||
reset(defaultValues);
|
||||
}
|
||||
}, [instance, reset]);
|
||||
|
||||
const { mutate: createMutation, isLoading: isCreateLoading } =
|
||||
trpcClient.providersConfiguration.create.useMutation({
|
||||
onSuccess({ id }) {
|
||||
trpcClient.avataxConfiguration.post.useMutation({
|
||||
onSuccess({ data: { id } }) {
|
||||
setInstanceId(id);
|
||||
refetchProvidersConfigurationData();
|
||||
appBridge?.dispatch(
|
||||
|
@ -94,7 +112,7 @@ export const AvataxConfigurationForm = () => {
|
|||
});
|
||||
|
||||
const { mutate: updateMutation, isLoading: isUpdateLoading } =
|
||||
trpcClient.providersConfiguration.update.useMutation({
|
||||
trpcClient.avataxConfiguration.patch.useMutation({
|
||||
onSuccess() {
|
||||
refetchProvidersConfigurationData();
|
||||
appBridge?.dispatch(
|
||||
|
@ -116,7 +134,7 @@ export const AvataxConfigurationForm = () => {
|
|||
},
|
||||
});
|
||||
|
||||
const { mutate: deleteMutation } = trpcClient.providersConfiguration.delete.useMutation({
|
||||
const { mutate: deleteMutation } = trpcClient.avataxConfiguration.delete.useMutation({
|
||||
onSuccess() {
|
||||
resetInstanceId();
|
||||
refetchProvidersConfigurationData();
|
||||
|
@ -144,21 +162,15 @@ export const AvataxConfigurationForm = () => {
|
|||
fullWidth: true,
|
||||
};
|
||||
|
||||
const onSubmit = (values: FormValues) => {
|
||||
const onSubmit = (value: FormValues) => {
|
||||
if (instanceId) {
|
||||
updateMutation({
|
||||
id: instanceId,
|
||||
provider: {
|
||||
...values,
|
||||
provider: "avatax",
|
||||
},
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
createMutation({
|
||||
provider: {
|
||||
...values,
|
||||
provider: "avatax",
|
||||
},
|
||||
value,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -201,9 +213,9 @@ export const AvataxConfigurationForm = () => {
|
|||
<InputLabel>
|
||||
Sandbox
|
||||
<Controller
|
||||
name={"config.isSandbox"}
|
||||
name={"isSandbox"}
|
||||
control={control}
|
||||
defaultValue={defaultValues.config.isSandbox}
|
||||
defaultValue={defaultValues.isSandbox}
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
{...field}
|
||||
|
@ -229,9 +241,9 @@ export const AvataxConfigurationForm = () => {
|
|||
<InputLabel>
|
||||
Autocommit
|
||||
<Controller
|
||||
name={"config.isAutocommit"}
|
||||
name={"isAutocommit"}
|
||||
control={control}
|
||||
defaultValue={defaultValues.config.isAutocommit}
|
||||
defaultValue={defaultValues.isAutocommit}
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
{...field}
|
||||
|
@ -254,39 +266,39 @@ export const AvataxConfigurationForm = () => {
|
|||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Controller
|
||||
name="config.username"
|
||||
name="username"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ field }) => (
|
||||
<TextField type="text" {...field} label="Username" {...textFieldProps} />
|
||||
)}
|
||||
/>
|
||||
{formState.errors.config?.username && (
|
||||
<FormHelperText error>{formState.errors.config.username.message}</FormHelperText>
|
||||
{formState.errors.username && (
|
||||
<FormHelperText error>{formState.errors.username.message}</FormHelperText>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Controller
|
||||
name="config.password"
|
||||
name="password"
|
||||
control={control}
|
||||
defaultValue={defaultValues.config.password}
|
||||
defaultValue={defaultValues.password}
|
||||
render={({ field }) => <TextField label="Password" {...field} {...textFieldProps} />}
|
||||
/>
|
||||
{formState.errors.config?.password && (
|
||||
<FormHelperText error>{formState.errors.config.password.message}</FormHelperText>
|
||||
{formState.errors.password && (
|
||||
<FormHelperText error>{formState.errors.password.message}</FormHelperText>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Controller
|
||||
name="config.companyName"
|
||||
name="companyName"
|
||||
control={control}
|
||||
defaultValue={defaultValues.config.companyName}
|
||||
defaultValue={defaultValues.companyName}
|
||||
render={({ field }) => (
|
||||
<TextField type="text" {...field} label="Company name" {...textFieldProps} />
|
||||
)}
|
||||
/>
|
||||
{formState.errors.config?.companyName && (
|
||||
<FormHelperText error>{formState.errors.config.companyName.message}</FormHelperText>
|
||||
{formState.errors.companyName && (
|
||||
<FormHelperText error>{formState.errors.companyName.message}</FormHelperText>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
|
@ -3,7 +3,7 @@ import { AvataxConfigurationForm } from "./avatax-configuration-form";
|
|||
export const AvataxConfiguration = () => {
|
||||
return (
|
||||
<section>
|
||||
<h2>Avalara configuration</h2>
|
||||
<h2>Avatax configuration</h2>
|
||||
<AvataxConfigurationForm />
|
||||
</section>
|
||||
);
|
|
@ -78,9 +78,32 @@ export const ChannelTaxProviderForm = () => {
|
|||
const { channelSlug } = useChannelSlug();
|
||||
|
||||
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 { mutate, isLoading } = trpcClient.channelsConfiguration.upsert.useMutation({
|
||||
|
@ -136,10 +159,10 @@ export const ChannelTaxProviderForm = () => {
|
|||
defaultValue={""}
|
||||
render={({ field }) => (
|
||||
<Select fullWidth {...field}>
|
||||
{providerInstances.map(({ name, id, provider }) => (
|
||||
<MenuItem value={id} key={name}>
|
||||
{providerInstances.map(({ config, id, provider }) => (
|
||||
<MenuItem value={id} key={id}>
|
||||
<div className={styles.menuItem}>
|
||||
<Typography variant="body1">{name}</Typography>
|
||||
<Typography variant="body1">{config.name}</Typography>
|
||||
<ProviderIcon size={"medium"} provider={provider} />
|
||||
</div>
|
||||
</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 {
|
||||
avataxInstanceConfigSchema,
|
||||
serverAvataxSchema,
|
||||
} from "../taxes/providers/avatax/avatax-config";
|
||||
import {
|
||||
taxJarInstanceConfigSchema,
|
||||
serverTaxJarSchema,
|
||||
} from "../taxes/providers/taxjar/taxjar-config";
|
||||
import { avataxInstanceConfigSchema } from "../avatax/avatax-config";
|
||||
import { taxJarInstanceConfigSchema } from "../taxjar/taxjar-config";
|
||||
|
||||
export const providerSchema = taxJarInstanceConfigSchema.or(avataxInstanceConfigSchema);
|
||||
export const providersSchema = z.array(providerSchema.and(z.object({ id: z.string() })));
|
||||
|
||||
const serverProviderSchema = serverTaxJarSchema.or(serverAvataxSchema);
|
||||
export const serverProvidersSchema = z.array(
|
||||
serverProviderSchema.and(z.object({ id: z.string() }))
|
||||
);
|
||||
export const providersSchema = z.array(providerSchema);
|
||||
|
||||
export type ProvidersConfig = z.infer<typeof providersSchema>;
|
||||
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 { 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 { router } from "../trpc/trpc-server";
|
||||
import { GetProvidersConfigurationService } from "./get-providers-configuration.service";
|
||||
import { ProvidersConfig } from "./providers-config";
|
||||
import {
|
||||
createProviderInstanceInputSchema,
|
||||
deleteProviderInstanceInputSchema,
|
||||
updateProviderInstanceInputSchema,
|
||||
} from "./providers-config-input-schema";
|
||||
import { TaxProvidersConfigurator } from "./providers-configurator";
|
||||
import { TaxProvidersConfigurationService } from "./providers-configuration-service";
|
||||
|
||||
export const providersConfigurationRouter = router({
|
||||
getAll: protectedClientProcedure.query(async ({ ctx }) => {
|
||||
|
@ -20,128 +9,6 @@ export const providersConfigurationRouter = router({
|
|||
|
||||
logger.debug("providersConfigurationRouter.fetch called");
|
||||
|
||||
return new GetProvidersConfigurationService({
|
||||
apiClient: ctx.apiClient,
|
||||
saleorApiUrl: ctx.saleorApiUrl,
|
||||
}).getConfiguration();
|
||||
return new TaxProvidersConfigurationService(ctx.apiClient, ctx.saleorApiUrl).getAll();
|
||||
}),
|
||||
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 { makeStyles } from "@saleor/macaw-ui";
|
||||
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 { TaxJarConfiguration } from "../../taxes/providers/taxjar/ui/taxjar-configuration";
|
||||
import { TaxJarConfiguration } from "../../taxjar/ui/taxjar-configuration";
|
||||
import { useInstanceId } from "../../taxes/tax-context";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { AppPaper } from "../../ui/app-paper";
|
||||
|
|
|
@ -58,7 +58,7 @@ export const TaxProvidersInstancesList = () => {
|
|||
key={instance.id}
|
||||
>
|
||||
<OffsettedListItemCell className={styles.cell}>
|
||||
{instance.name}
|
||||
{instance.config.name}
|
||||
<ProviderIcon size="medium" provider={instance.provider} />
|
||||
</OffsettedListItemCell>
|
||||
</OffsettedListItem>
|
||||
|
|
|
@ -2,8 +2,8 @@ import { TaxBaseFragment } from "../../../generated/graphql";
|
|||
import { createLogger } from "../../lib/logger";
|
||||
import { ChannelConfig } from "../channels-configuration/channels-config";
|
||||
import { ProviderConfig } from "../providers-configuration/providers-config";
|
||||
import { AvataxProvider } from "./providers/avatax/avatax-provider";
|
||||
import { TaxJarProvider } from "./providers/taxjar/taxjar-provider";
|
||||
import { AvataxProvider } from "../avatax/avatax-provider";
|
||||
import { TaxJarProvider } from "../taxjar/taxjar-provider";
|
||||
import { TaxProvider } from "./tax-provider";
|
||||
import { TaxProviderError } from "./tax-provider-error";
|
||||
|
||||
|
@ -36,8 +36,4 @@ export class ActiveTaxProvider {
|
|||
async calculate(payload: TaxBaseFragment, channel: ChannelConfig) {
|
||||
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,
|
||||
},
|
||||
avatax: {
|
||||
label: "Avalara",
|
||||
label: "Avatax",
|
||||
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 { TaxProviderName } from "./providers/config";
|
||||
import { z } from "zod";
|
||||
|
||||
export const addressSchema = z.object({
|
||||
country: z.string(),
|
||||
|
@ -8,19 +7,3 @@ export const addressSchema = z.object({
|
|||
city: 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 {
|
||||
name: TaxProviderName;
|
||||
calculate: (payload: TaxBaseFragment, channel: ChannelConfig) => Promise<ResponseTaxPayload>;
|
||||
validate?: () => Promise<ExternalValidationResult>;
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@ import {
|
|||
TaxBaseFragment,
|
||||
TaxBaseLineFragment,
|
||||
TaxDiscountFragment,
|
||||
} from "../../../../../generated/graphql";
|
||||
import { ChannelConfig } from "../../../channels-configuration/channels-config";
|
||||
import { taxLineResolver } from "../../tax-line-resolver";
|
||||
import { ResponseTaxPayload } from "../../types";
|
||||
} from "../../../generated/graphql";
|
||||
import { ChannelConfig } from "../channels-configuration/channels-config";
|
||||
import { taxLineResolver } from "../taxes/tax-line-resolver";
|
||||
import { ResponseTaxPayload } from "../taxes/types";
|
||||
|
||||
const formatCalculatedAmount = (amount: number) => {
|
||||
return Number(amount.toFixed(2));
|
|
@ -1,6 +1,6 @@
|
|||
import TaxJar from "taxjar";
|
||||
import { Config, TaxForOrderRes, TaxParams } from "taxjar/dist/util/types";
|
||||
import { logger } from "../../../../lib/logger";
|
||||
import { logger } from "../../lib/logger";
|
||||
import { TaxJarConfig } from "./taxjar-config";
|
||||
|
||||
const createTaxJarSettings = (config: TaxJarConfig): Config => {
|
||||
|
@ -27,4 +27,16 @@ export class TaxJarClient {
|
|||
const response: TaxForOrderRes = await this.client.taxForOrder(params);
|
||||
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 { logger } from "../../../../lib/logger";
|
||||
import {
|
||||
ChannelConfig,
|
||||
defaultChannelConfig,
|
||||
} from "../../../channels-configuration/channels-config";
|
||||
import { TaxProvider } from "../../tax-provider";
|
||||
import { TaxBaseFragment } from "../../../generated/graphql";
|
||||
import { logger } from "../../lib/logger";
|
||||
import { ChannelConfig, defaultChannelConfig } from "../channels-configuration/channels-config";
|
||||
import { TaxProvider } from "../taxes/tax-provider";
|
||||
import { taxJarCalculate } from "./taxjar-calculate";
|
||||
import { TaxJarClient } from "./taxjar-client";
|
||||
import { defaultTaxJarConfig, TaxJarConfig } from "./taxjar-config";
|
|
@ -13,9 +13,9 @@ import { Button, makeStyles } from "@saleor/macaw-ui";
|
|||
import React from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { trpcClient } from "../../../../trpc/trpc-client";
|
||||
import { useInstanceId } from "../../../tax-context";
|
||||
import { taxJarInstanceConfigSchema } from "../taxjar-config";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { useInstanceId } from "../../taxes/tax-context";
|
||||
import { taxJarConfigSchema, taxJarInstanceConfigSchema } from "../taxjar-config";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
reverseRow: {
|
||||
|
@ -25,15 +25,13 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
const schema = taxJarInstanceConfigSchema.omit({ provider: true });
|
||||
const schema = taxJarConfigSchema;
|
||||
type FormValues = z.infer<typeof schema>;
|
||||
|
||||
const defaultValues: FormValues = {
|
||||
config: {
|
||||
apiKey: "",
|
||||
isSandbox: false,
|
||||
},
|
||||
name: "",
|
||||
apiKey: "",
|
||||
isSandbox: false,
|
||||
};
|
||||
|
||||
export const TaxJarConfigurationForm = () => {
|
||||
|
@ -51,16 +49,36 @@ export const TaxJarConfigurationForm = () => {
|
|||
};
|
||||
|
||||
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 } =
|
||||
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 { mutate: createMutation, isLoading: isCreateLoading } =
|
||||
trpcClient.providersConfiguration.create.useMutation({
|
||||
onSuccess({ id }) {
|
||||
trpcClient.taxJarConfiguration.post.useMutation({
|
||||
onSuccess({ data: { id } }) {
|
||||
setInstanceId(id);
|
||||
refetchProvidersConfigurationData();
|
||||
refetchChannelConfigurationData();
|
||||
|
@ -84,7 +102,7 @@ export const TaxJarConfigurationForm = () => {
|
|||
});
|
||||
|
||||
const { mutate: updateMutation, isLoading: isUpdateLoading } =
|
||||
trpcClient.providersConfiguration.update.useMutation({
|
||||
trpcClient.taxJarConfiguration.patch.useMutation({
|
||||
onSuccess() {
|
||||
refetchProvidersConfigurationData();
|
||||
refetchChannelConfigurationData();
|
||||
|
@ -108,7 +126,7 @@ export const TaxJarConfigurationForm = () => {
|
|||
});
|
||||
|
||||
const { mutate: deleteMutation, isLoading: isDeleteLoading } =
|
||||
trpcClient.providersConfiguration.delete.useMutation({
|
||||
trpcClient.taxJarConfiguration.delete.useMutation({
|
||||
onSuccess() {
|
||||
resetInstanceId();
|
||||
refetchProvidersConfigurationData();
|
||||
|
@ -134,8 +152,8 @@ export const TaxJarConfigurationForm = () => {
|
|||
|
||||
React.useEffect(() => {
|
||||
if (instance) {
|
||||
const { provider, id, ...values } = instance;
|
||||
reset(values);
|
||||
const { config } = instance;
|
||||
reset(config);
|
||||
} else {
|
||||
reset({ ...defaultValues });
|
||||
}
|
||||
|
@ -145,21 +163,15 @@ export const TaxJarConfigurationForm = () => {
|
|||
fullWidth: true,
|
||||
};
|
||||
|
||||
const onSubmit = (values: FormValues) => {
|
||||
const onSubmit = (value: FormValues) => {
|
||||
if (instanceId) {
|
||||
updateMutation({
|
||||
id: instanceId,
|
||||
provider: {
|
||||
...values,
|
||||
provider: "taxjar",
|
||||
},
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
createMutation({
|
||||
provider: {
|
||||
...values,
|
||||
provider: "taxjar",
|
||||
},
|
||||
value,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -200,22 +212,22 @@ export const TaxJarConfigurationForm = () => {
|
|||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Controller
|
||||
name="config.apiKey"
|
||||
name="apiKey"
|
||||
control={control}
|
||||
defaultValue={defaultValues.config.apiKey}
|
||||
defaultValue={defaultValues.apiKey}
|
||||
render={({ field }) => <TextField label="API Key" {...field} {...textFieldProps} />}
|
||||
/>
|
||||
{formState.errors.config?.apiKey && (
|
||||
<FormHelperText error>{formState.errors.config?.apiKey.message}</FormHelperText>
|
||||
{formState.errors?.apiKey && (
|
||||
<FormHelperText error>{formState.errors?.apiKey.message}</FormHelperText>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputLabel>
|
||||
Sandbox
|
||||
<Controller
|
||||
name={"config.isSandbox"}
|
||||
name={"isSandbox"}
|
||||
control={control}
|
||||
defaultValue={defaultValues.config.isSandbox}
|
||||
defaultValue={defaultValues.isSandbox}
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
{...field}
|
|
@ -2,11 +2,15 @@ import { channelsRouter } from "../channels/channels.router";
|
|||
import { router } from "./trpc-server";
|
||||
import { providersConfigurationRouter } from "../providers-configuration/providers-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({
|
||||
channels: channelsRouter,
|
||||
providersConfiguration: providersConfigurationRouter,
|
||||
channelsConfiguration: channelsConfigurationRouter,
|
||||
taxJarConfiguration: taxjarConfigurationRouter,
|
||||
avataxConfiguration: avataxConfigurationRouter,
|
||||
});
|
||||
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
|
|
@ -5,7 +5,7 @@ import { createClient } from "../../../lib/graphql";
|
|||
import { createLogger } from "../../../lib/logger";
|
||||
import { calculateTaxesPayloadSchema, ExpectedWebhookPayload } from "../../../lib/saleor/schema";
|
||||
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";
|
||||
|
||||
export const config = {
|
||||
|
@ -30,7 +30,7 @@ export default checkoutCalculateTaxesSyncWebhook.createHandler(async (req, res,
|
|||
const validation = calculateTaxesPayloadSchema.safeParse(payload);
|
||||
|
||||
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");
|
||||
return res.send({});
|
||||
}
|
||||
|
@ -43,10 +43,10 @@ export default checkoutCalculateTaxesSyncWebhook.createHandler(async (req, res,
|
|||
Promise.resolve({ token: authData.token })
|
||||
);
|
||||
|
||||
const providersConfig = await new GetProvidersConfigurationService({
|
||||
saleorApiUrl: authData.saleorApiUrl,
|
||||
apiClient: client,
|
||||
}).getConfiguration();
|
||||
const providersConfig = await new TaxProvidersConfigurationService(
|
||||
client,
|
||||
authData.saleorApiUrl
|
||||
).getAll();
|
||||
|
||||
const channelsConfig = await new GetChannelsConfigurationService({
|
||||
saleorApiUrl: authData.saleorApiUrl,
|
||||
|
|
|
@ -5,7 +5,7 @@ import { createClient } from "../../../lib/graphql";
|
|||
import { createLogger } from "../../../lib/logger";
|
||||
import { calculateTaxesPayloadSchema, ExpectedWebhookPayload } from "../../../lib/saleor/schema";
|
||||
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";
|
||||
|
||||
export const config = {
|
||||
|
@ -30,7 +30,7 @@ export default orderCalculateTaxesSyncWebhook.createHandler(async (req, res, ctx
|
|||
const validation = calculateTaxesPayloadSchema.safeParse(payload);
|
||||
|
||||
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");
|
||||
return res.status(200).json({});
|
||||
}
|
||||
|
@ -42,11 +42,10 @@ export default orderCalculateTaxesSyncWebhook.createHandler(async (req, res, ctx
|
|||
const client = createClient(authData.saleorApiUrl, async () =>
|
||||
Promise.resolve({ token: authData.token })
|
||||
);
|
||||
|
||||
const providersConfig = await new GetProvidersConfigurationService({
|
||||
saleorApiUrl: authData.saleorApiUrl,
|
||||
apiClient: client,
|
||||
}).getConfiguration();
|
||||
const providersConfig = await new TaxProvidersConfigurationService(
|
||||
client,
|
||||
authData.saleorApiUrl
|
||||
).getAll();
|
||||
|
||||
const channelsConfig = await new GetChannelsConfigurationService({
|
||||
saleorApiUrl: authData.saleorApiUrl,
|
||||
|
|
Loading…
Reference in a new issue