chore: split credentials and settings (#886)

* feat:  grey out disabled links

* chore: 🚚 move fields to new avatax settings fragment

* build: 👷 add changeset

* refactor: 🚚 move companyCode to credentials

* refactor: ♻️ make helper texts more accurate

* refactor: 🚚 Avatax -> AvaTax
This commit is contained in:
Adrian Pilarczyk 2023-08-21 10:28:43 +02:00 committed by GitHub
parent c50797e836
commit be761b251e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 219 additions and 203 deletions

View file

@ -0,0 +1,5 @@
---
"saleor-app-taxes": minor
---
Changed the layout in the AvaTax configuration form to consist of three sections: "Credentials", "Settings" (new) and "Address". Now, the "Credentials" section contains only fields that affect authentication.

View file

@ -13,7 +13,7 @@ const avataxCredentialsSchema = z.object({
password: z.string().min(1, { message: "Password requires at least one character." }), password: z.string().min(1, { message: "Password requires at least one character." }),
}); });
// All that is needed to create Avatax configuration. // All that is needed to create AvaTax configuration.
export const baseAvataxConfigSchema = z.object({ export const baseAvataxConfigSchema = z.object({
isSandbox: z.boolean(), isSandbox: z.boolean(),
credentials: avataxCredentialsSchema, credentials: avataxCredentialsSchema,

View file

@ -49,7 +49,7 @@ export const avataxConnectionRouter = router({
await ctx.connectionService.verifyConnections(); await ctx.connectionService.verifyConnections();
logger.info("Avatax connections were successfully verified"); logger.info("AvaTax connections were successfully verified");
return { ok: true }; return { ok: true };
}), }),
@ -62,7 +62,7 @@ export const avataxConnectionRouter = router({
const result = await ctx.connectionService.getById(input.id); const result = await ctx.connectionService.getById(input.id);
logger.info(`Avatax configuration with an id: ${result.id} was successfully retrieved`); logger.info(`AvaTax configuration with an id: ${result.id} was successfully retrieved`);
return result; return result;
}), }),
@ -76,7 +76,7 @@ export const avataxConnectionRouter = router({
const result = await ctx.connectionService.create(input.value); const result = await ctx.connectionService.create(input.value);
logger.info("Avatax configuration was successfully created"); logger.info("AvaTax configuration was successfully created");
return result; return result;
}), }),
@ -92,7 +92,7 @@ export const avataxConnectionRouter = router({
const result = await ctx.connectionService.delete(input.id); const result = await ctx.connectionService.delete(input.id);
logger.info(`Avatax configuration with an id: ${input.id} was deleted`); logger.info(`AvaTax configuration with an id: ${input.id} was deleted`);
return result; return result;
}), }),
@ -108,7 +108,7 @@ export const avataxConnectionRouter = router({
const result = await ctx.connectionService.update(input.id, input.value); const result = await ctx.connectionService.update(input.id, input.value);
logger.info(`Avatax configuration with an id: ${input.id} was successfully updated`); logger.info(`AvaTax configuration with an id: ${input.id} was successfully updated`);
return result; return result;
}), }),
@ -137,7 +137,7 @@ export const avataxConnectionRouter = router({
const result = await addressValidationService.validate(input.id, input.value); const result = await addressValidationService.validate(input.id, input.value);
logger.info(`Avatax address was successfully validated`); logger.info(`AvaTax address was successfully validated`);
return result; return result;
}), }),
@ -157,7 +157,7 @@ export const avataxConnectionRouter = router({
const result = await addressValidation.validate(input.value.address); const result = await addressValidation.validate(input.value.address);
logger.info(`Avatax address was successfully validated`); logger.info(`AvaTax address was successfully validated`);
return result; return result;
}), }),
@ -184,7 +184,7 @@ export const avataxConnectionRouter = router({
await authValidation.validate(input.id, input.value); await authValidation.validate(input.id, input.value);
logger.info(`Avatax client was successfully validated`); logger.info(`AvaTax client was successfully validated`);
}), }),
createValidateCredentials: protectedClientProcedure createValidateCredentials: protectedClientProcedure
.input(z.object({ value: baseAvataxConfigSchema })) .input(z.object({ value: baseAvataxConfigSchema }))
@ -200,7 +200,7 @@ export const avataxConnectionRouter = router({
const result = await authValidation.validate(); const result = await authValidation.validate();
logger.info(`Avatax client was successfully validated`); logger.info(`AvaTax client was successfully validated`);
return result; return result;
}), }),

View file

@ -15,7 +15,7 @@ export class AvataxDocumentCodeResolver {
const code = avataxDocumentCode ?? orderId; const code = avataxDocumentCode ?? orderId;
/* /*
* The requirement from Avatax API is that document code is a string that must be between 1 and 20 characters long. * The requirement from AvaTax API is that document code is a string that must be between 1 and 20 characters long.
* // todo: document that its sliced * // todo: document that its sliced
*/ */
return code.slice(0, 20); return code.slice(0, 20);

View file

@ -27,21 +27,21 @@ export class AvataxCalculateTaxesAdapter
// todo: refactor because its getting too big // todo: refactor because its getting too big
async send(payload: AvataxCalculateTaxesPayload): Promise<AvataxCalculateTaxesResponse> { async send(payload: AvataxCalculateTaxesPayload): Promise<AvataxCalculateTaxesResponse> {
this.logger.debug("Transforming the Saleor payload for calculating taxes with Avatax..."); this.logger.debug("Transforming the Saleor payload for calculating taxes with AvaTax...");
const payloadService = new AvataxCalculateTaxesPayloadService(this.authData); const payloadService = new AvataxCalculateTaxesPayloadService(this.authData);
const target = await payloadService.getPayload(payload.taxBase, this.config); const target = await payloadService.getPayload(payload.taxBase, this.config);
this.logger.debug("Calling Avatax createTransaction with transformed payload..."); this.logger.debug("Calling AvaTax createTransaction with transformed payload...");
const client = new AvataxClient(this.config); const client = new AvataxClient(this.config);
const response = await client.createTransaction(target); const response = await client.createTransaction(target);
this.logger.debug("Avatax createTransaction successfully responded"); this.logger.debug("AvaTax createTransaction successfully responded");
const responseTransformer = new AvataxCalculateTaxesResponseTransformer(); const responseTransformer = new AvataxCalculateTaxesResponseTransformer();
const transformedResponse = responseTransformer.transform(response); const transformedResponse = responseTransformer.transform(response);
this.logger.debug("Transformed Avatax createTransaction response"); this.logger.debug("Transformed AvaTax createTransaction response");
return transformedResponse; return transformedResponse;
} }

View file

@ -26,7 +26,7 @@ export class AvataxCalculateTaxesPayloadLinesTransformer {
}); });
if (taxBase.shippingPrice.amount !== 0) { if (taxBase.shippingPrice.amount !== 0) {
// * In Avatax, shipping is a regular line // * In AvaTax, shipping is a regular line
const shippingLine: LineItemModel = { const shippingLine: LineItemModel = {
amount: taxBase.shippingPrice.amount, amount: taxBase.shippingPrice.amount,
itemCode: SHIPPING_ITEM_CODE, itemCode: SHIPPING_ITEM_CODE,

View file

@ -16,7 +16,7 @@ export class AvataxAuthValidationService {
const result = await this.avataxClient.ping(); const result = await this.avataxClient.ping();
if (!result.authenticated) { if (!result.authenticated) {
throw new Error("Invalid Avatax credentials."); throw new Error("Invalid AvaTax credentials.");
} }
} catch (error) { } catch (error) {
const errorResolver = new AvataxValidationErrorResolver(); const errorResolver = new AvataxValidationErrorResolver();

View file

@ -27,9 +27,9 @@ describe("AvataxValidationErrorResolver", () => {
}); });
expect(result).toBeInstanceOf(Error); expect(result).toBeInstanceOf(Error);
expect(result.message).toBe("Invalid Avatax credentials."); expect(result.message).toBe("Invalid AvaTax credentials.");
}); });
it("when other Avatax error, should return error with first message", () => { it("when other AvaTax error, should return error with first message", () => {
const result = errorResolver.resolve({ const result = errorResolver.resolve({
code: "error", code: "error",
details: [ details: [
@ -57,6 +57,6 @@ describe("AvataxValidationErrorResolver", () => {
const result = errorResolver.resolve("error"); const result = errorResolver.resolve("error");
expect(result).toBeInstanceOf(Error); expect(result).toBeInstanceOf(Error);
expect(result.message).toBe("Unknown error while validating Avatax configuration."); expect(result.message).toBe("Unknown error while validating AvaTax configuration.");
}); });
}); });

View file

@ -26,12 +26,12 @@ export class AvataxValidationErrorResolver {
const parseResult = avataxErrorSchema.safeParse(error); const parseResult = avataxErrorSchema.safeParse(error);
const isErrorParsed = parseResult.success; const isErrorParsed = parseResult.success;
// Avatax doesn't return a type for their error format, so we need to parse the error // AvaTax doesn't return a type for their error format, so we need to parse the error
if (isErrorParsed) { if (isErrorParsed) {
const { code, details } = parseResult.data; const { code, details } = parseResult.data;
if (code === "AuthenticationException") { if (code === "AuthenticationException") {
return new Error("Invalid Avatax credentials."); return new Error("Invalid AvaTax credentials.");
} }
return new Error(details[0].message); return new Error(details[0].message);
@ -41,7 +41,7 @@ export class AvataxValidationErrorResolver {
return error; return error;
} }
this.logger.error("Unknown error while validating Avatax configuration."); this.logger.error("Unknown error while validating AvaTax configuration.");
return new Error("Unknown error while validating Avatax configuration."); return new Error("Unknown error while validating AvaTax configuration.");
} }
} }

View file

@ -48,7 +48,7 @@ export class PublicAvataxConnectionService {
const connections = await this.connectionService.getAll(); const connections = await this.connectionService.getAll();
if (connections.length === 0) { if (connections.length === 0) {
throw new Error("No Avatax connections found"); throw new Error("No AvaTax connections found");
} }
} }
} }

View file

@ -15,12 +15,12 @@ export class AvataxOrderCancelledAdapter implements WebhookAdapter<OrderCancelle
} }
async send(payload: OrderCancelledPayload) { async send(payload: OrderCancelledPayload) {
this.logger.debug("Transforming the Saleor payload for cancelling transaction with Avatax..."); this.logger.debug("Transforming the Saleor payload for cancelling transaction with AvaTax...");
const payloadTransformer = new AvataxOrderCancelledPayloadTransformer(this.config); const payloadTransformer = new AvataxOrderCancelledPayloadTransformer(this.config);
const target = payloadTransformer.transform({ ...payload }); const target = payloadTransformer.transform({ ...payload });
this.logger.debug("Calling Avatax voidTransaction with transformed payload..."); this.logger.debug("Calling AvaTax voidTransaction with transformed payload...");
const client = new AvataxClient(this.config); const client = new AvataxClient(this.config);

View file

@ -23,22 +23,22 @@ export class AvataxOrderConfirmedAdapter
} }
async send(payload: AvataxOrderConfirmedPayload): Promise<AvataxOrderConfirmedResponse> { async send(payload: AvataxOrderConfirmedPayload): Promise<AvataxOrderConfirmedResponse> {
this.logger.debug("Transforming the Saleor payload for creating order with Avatax..."); this.logger.debug("Transforming the Saleor payload for creating order with AvaTax...");
const payloadService = new AvataxOrderConfirmedPayloadService(this.authData); const payloadService = new AvataxOrderConfirmedPayloadService(this.authData);
const target = await payloadService.getPayload(payload.order, this.config); const target = await payloadService.getPayload(payload.order, this.config);
this.logger.debug("Calling Avatax createTransaction with transformed payload..."); this.logger.debug("Calling AvaTax createTransaction with transformed payload...");
const client = new AvataxClient(this.config); const client = new AvataxClient(this.config);
const response = await client.createTransaction(target); const response = await client.createTransaction(target);
this.logger.debug("Avatax createTransaction successfully responded"); this.logger.debug("AvaTax createTransaction successfully responded");
const responseTransformer = new AvataxOrderConfirmedResponseTransformer(); const responseTransformer = new AvataxOrderConfirmedResponseTransformer();
const transformedResponse = responseTransformer.transform(response); const transformedResponse = responseTransformer.transform(response);
this.logger.debug("Transformed Avatax createTransaction response"); this.logger.debug("Transformed AvaTax createTransaction response");
return transformedResponse; return transformedResponse;
} }

View file

@ -31,7 +31,7 @@ export class AvataxOrderConfirmedPayloadLinesTransformer {
}); });
if (order.shippingPrice.net.amount !== 0) { if (order.shippingPrice.net.amount !== 0) {
// * In Avatax, shipping is a regular line // * In AvaTax, shipping is a regular line
const shippingLine: LineItemModel = { const shippingLine: LineItemModel = {
amount: order.shippingPrice.gross.amount, amount: order.shippingPrice.gross.amount,
taxIncluded: true, taxIncluded: true,

View file

@ -48,7 +48,7 @@ export class AvataxOrderConfirmedPayloadTransformer {
entityUseCode, entityUseCode,
customerCode: customerCode:
order.user?.id ?? order.user?.id ??
"" /* In Saleor Avatax plugin, the customer code is 0. In Taxes App, we set it to the user id. */, "" /* In Saleor AvaTax plugin, the customer code is 0. In Taxes App, we set it to the user id. */,
companyCode: avataxConfig.companyCode ?? defaultAvataxConfig.companyCode, companyCode: avataxConfig.companyCode ?? defaultAvataxConfig.companyCode,
// * commit: If true, the transaction will be committed immediately after it is created. See: https://developer.avalara.com/communications/dev-guide_rest_v2/commit-uncommit // * commit: If true, the transaction will be committed immediately after it is created. See: https://developer.avalara.com/communications/dev-guide_rest_v2/commit-uncommit
commit: avataxConfig.isAutocommit, commit: avataxConfig.isAutocommit,

View file

@ -8,7 +8,7 @@ export class AvataxOrderConfirmedResponseTransformer {
id: taxProviderUtils.resolveOptionalOrThrow( id: taxProviderUtils.resolveOptionalOrThrow(
response.code, response.code,
new Error( new Error(
"Could not update the order metadata with Avatax transaction code because it was not returned from the createTransaction mutation." "Could not update the order metadata with AvaTax transaction code because it was not returned from the createTransaction mutation."
) )
), ),
}; };

View file

@ -24,22 +24,22 @@ export class AvataxOrderCreatedAdapter
} }
async send(payload: AvataxOrderCreatedPayload): Promise<AvataxOrderCreatedResponse> { async send(payload: AvataxOrderCreatedPayload): Promise<AvataxOrderCreatedResponse> {
this.logger.debug("Transforming the Saleor payload for creating order with Avatax..."); this.logger.debug("Transforming the Saleor payload for creating order with AvaTax...");
const payloadService = new AvataxOrderCreatedPayloadService(this.authData); const payloadService = new AvataxOrderCreatedPayloadService(this.authData);
const target = await payloadService.getPayload(payload.order, this.config); const target = await payloadService.getPayload(payload.order, this.config);
this.logger.debug("Calling Avatax createTransaction with transformed payload..."); this.logger.debug("Calling AvaTax createTransaction with transformed payload...");
const client = new AvataxClient(this.config); const client = new AvataxClient(this.config);
const response = await client.createTransaction(target); const response = await client.createTransaction(target);
this.logger.debug("Avatax createTransaction successfully responded"); this.logger.debug("AvaTax createTransaction successfully responded");
const responseTransformer = new AvataxOrderCreatedResponseTransformer(); const responseTransformer = new AvataxOrderCreatedResponseTransformer();
const transformedResponse = responseTransformer.transform(response); const transformedResponse = responseTransformer.transform(response);
this.logger.debug("Transformed Avatax createTransaction response"); this.logger.debug("Transformed AvaTax createTransaction response");
return transformedResponse; return transformedResponse;
} }

View file

@ -31,7 +31,7 @@ export class AvataxOrderCreatedPayloadLinesTransformer {
}); });
if (order.shippingPrice.net.amount !== 0) { if (order.shippingPrice.net.amount !== 0) {
// * In Avatax, shipping is a regular line // * In AvaTax, shipping is a regular line
const shippingLine: LineItemModel = { const shippingLine: LineItemModel = {
amount: order.shippingPrice.gross.amount, amount: order.shippingPrice.gross.amount,
taxIncluded: true, taxIncluded: true,

View file

@ -48,7 +48,7 @@ export class AvataxOrderCreatedPayloadTransformer {
code, code,
customerCode: customerCode:
order.user?.id ?? order.user?.id ??
"" /* In Saleor Avatax plugin, the customer code is 0. In Taxes App, we set it to the user id. */, "" /* In Saleor AvaTax plugin, the customer code is 0. In Taxes App, we set it to the user id. */,
companyCode: avataxConfig.companyCode, companyCode: avataxConfig.companyCode,
// * commit: If true, the transaction will be committed immediately after it is created. See: https://developer.avalara.com/communications/dev-guide_rest_v2/commit-uncommit // * commit: If true, the transaction will be committed immediately after it is created. See: https://developer.avalara.com/communications/dev-guide_rest_v2/commit-uncommit
commit: avataxConfig.isAutocommit, commit: avataxConfig.isAutocommit,

View file

@ -8,7 +8,7 @@ export class AvataxOrderCreatedResponseTransformer {
id: taxProviderUtils.resolveOptionalOrThrow( id: taxProviderUtils.resolveOptionalOrThrow(
response.code, response.code,
new Error( new Error(
"Could not update the order metadata with Avatax transaction code because it was not returned from the createTransaction mutation." "Could not update the order metadata with AvaTax transaction code because it was not returned from the createTransaction mutation."
) )
), ),
}; };

View file

@ -22,22 +22,22 @@ export class AvataxOrderFulfilledAdapter
} }
async send(payload: AvataxOrderFulfilledPayload): Promise<AvataxOrderFulfilledResponse> { async send(payload: AvataxOrderFulfilledPayload): Promise<AvataxOrderFulfilledResponse> {
this.logger.debug("Transforming the Saleor payload for commiting transaction with Avatax..."); this.logger.debug("Transforming the Saleor payload for commiting transaction with AvaTax...");
const payloadTransformer = new AvataxOrderFulfilledPayloadTransformer(this.config); const payloadTransformer = new AvataxOrderFulfilledPayloadTransformer(this.config);
const target = payloadTransformer.transform({ ...payload }); const target = payloadTransformer.transform({ ...payload });
this.logger.debug("Calling Avatax commitTransaction with transformed payload..."); this.logger.debug("Calling AvaTax commitTransaction with transformed payload...");
const client = new AvataxClient(this.config); const client = new AvataxClient(this.config);
const response = await client.commitTransaction(target); const response = await client.commitTransaction(target);
this.logger.debug("Avatax commitTransaction succesfully responded"); this.logger.debug("AvaTax commitTransaction succesfully responded");
const responseTransformer = new AvataxOrderFulfilledResponseTransformer(); const responseTransformer = new AvataxOrderFulfilledResponseTransformer();
const transformedResponse = responseTransformer.transform(response); const transformedResponse = responseTransformer.transform(response);
this.logger.debug("Transformed Avatax commitTransaction response"); this.logger.debug("Transformed AvaTax commitTransaction response");
return transformedResponse; return transformedResponse;
} }

View file

@ -11,7 +11,7 @@ const MOCK_AVATAX_CONFIG: AvataxConfig = {
isDocumentRecordingEnabled: true, isDocumentRecordingEnabled: true,
isAutocommit: false, isAutocommit: false,
isSandbox: true, isSandbox: true,
name: "Avatax-1", name: "AvaTax-1",
shippingTaxCode: "FR000000", shippingTaxCode: "FR000000",
address: { address: {
country: "US", country: "US",

View file

@ -55,107 +55,68 @@ export const AvataxConfigurationCredentialsFragment = (
return ( return (
<> <>
<FormSection title="Credentials"> <FormSection title="Credentials">
<Box paddingY={4} display={"flex"} flexDirection={"column"} gap={10}> <div>
<div> <Input
<Input control={control}
control={control} name="credentials.username"
name="credentials.username" required
required label="Username *"
label="Username *" helperText={formState.errors.credentials?.username?.message}
helperText={formState.errors.credentials?.username?.message} />
/> <HelperText>
You can obtain it in the <i>API Keys</i> section of <i>Settings</i> <i>License</i> in
your Avalara Dashboard.
</HelperText>
</div>
<div>
<Input
control={control}
name="credentials.password"
type="password"
required
label="Password *"
helperText={formState.errors.credentials?.password?.message}
/>
<HelperText>
You can obtain it in the <i>API Keys</i> section of <i>Settings</i> <i>License</i> in
your Avalara Dashboard.
</HelperText>
</div>
<div>
<Input
control={control}
name="companyCode"
label="Company code"
helperText={formState.errors.companyCode?.message}
/>
<HelperText>
When not provided, the default company code will be used.{" "}
<TextLink
newTab
href="https://developer.avalara.com/erp-integration-guide/sales-tax-badge/transactions/simple-transactions/company-codes/"
>
Read more
</TextLink>{" "}
about company codes.
</HelperText>
</div>
<AppToggle
control={control}
label="Use sandbox mode"
helperText={
<HelperText> <HelperText>
You can obtain it in the <i>API Keys</i> section of <i>Settings</i> <i>License</i>{" "} Choose between
in your Avalara Dashboard.
</HelperText>
</div>
<div>
<Input
control={control}
name="credentials.password"
type="password"
required
label="Password *"
helperText={formState.errors.credentials?.password?.message}
/>
<HelperText>
You can obtain it in the <i>API Keys</i> section of <i>Settings</i> <i>License</i>{" "}
in your Avalara Dashboard.
</HelperText>
</div>
<div>
<Input
control={control}
name="companyCode"
label="Company name"
helperText={formState.errors.companyCode?.message}
/>
<HelperText>
When not provided, the default company will be used.{" "}
<TextLink <TextLink
href="https://developer.avalara.com/erp-integration-guide/sales-tax-badge/authentication-in-avatax/sandbox-vs-production/"
newTab newTab
href="https://developer.avalara.com/erp-integration-guide/sales-tax-badge/transactions/simple-transactions/company-codes/"
> >
Read more <q>Production</q> and <q>Sandbox</q>
</TextLink>{" "} </TextLink>{" "}
about company codes. environment according to your credentials.
</HelperText> </HelperText>
</div> }
</Box> name="isSandbox"
<Box paddingY={4} display={"flex"} flexDirection={"column"} gap={10}> />
<AppToggle
control={control}
label="Use sandbox mode"
helperText={
<HelperText>
Toggling between{" "}
<TextLink
href="https://developer.avalara.com/erp-integration-guide/sales-tax-badge/authentication-in-avatax/sandbox-vs-production/"
newTab
>
<q>Production</q> and <q>Sandbox</q>
</TextLink>{" "}
environment.
</HelperText>
}
name="isSandbox"
/>
<AppToggle
control={control}
label="Document recording"
helperText={
<HelperText>
When turned off, the document type will always be set to <i>SalesOrder</i>. This
means the transactions will not be recorded in Avatax. Read more{" "}
<TextLink
href="https://developer.avalara.com/ecommerce-integration-guide/sales-tax-badge/designing/disable-document-recording/"
newTab
>
here
</TextLink>
.
</HelperText>
}
name="isDocumentRecordingEnabled"
/>
<AppToggle
control={control}
label="Autocommit"
helperText={
<HelperText>
If enabled, the order will be automatically{" "}
<TextLink
href="https://developer.avalara.com/communications/dev-guide_rest_v2/commit-uncommit/"
newTab
>
commited to Avalara.
</TextLink>{" "}
</HelperText>
}
name="isAutocommit"
/>
</Box>
</FormSection> </FormSection>
<Box display="flex" justifyContent={"flex-end"}> <Box display="flex" justifyContent={"flex-end"}>
<Button variant="secondary" onClick={verifyCredentials}> <Button variant="secondary" onClick={verifyCredentials}>

View file

@ -14,9 +14,9 @@ import {
} from "../avatax-connection-schema"; } from "../avatax-connection-schema";
import { AvataxConfigurationAddressFragment } from "./avatax-configuration-address-fragment"; import { AvataxConfigurationAddressFragment } from "./avatax-configuration-address-fragment";
import { AvataxConfigurationCredentialsFragment } from "./avatax-configuration-credentials-fragment"; import { AvataxConfigurationCredentialsFragment } from "./avatax-configuration-credentials-fragment";
import { AvataxConfigurationTaxesFragment } from "./avatax-configuration-taxes-fragment"; import { AvataxConfigurationSettingsFragment } from "./avatax-configuration-settings-fragment";
import { HelperText } from "./form-helper-text";
import { useAvataxConfigurationStatus } from "./configuration-status"; import { useAvataxConfigurationStatus } from "./configuration-status";
import { HelperText } from "./form-helper-text";
type AvataxConfigurationFormProps = { type AvataxConfigurationFormProps = {
submit: { submit: {
@ -76,13 +76,13 @@ export const AvataxConfigurationForm = (props: AvataxConfigurationFormProps) =>
isLoading={props.validateCredentials.isLoading} isLoading={props.validateCredentials.isLoading}
/> />
<Divider marginY={8} /> <Divider marginY={8} />
<AvataxConfigurationSettingsFragment />
<Divider marginY={8} />
<AvataxConfigurationAddressFragment <AvataxConfigurationAddressFragment
onValidateAddress={props.validateAddress.handleFn} onValidateAddress={props.validateAddress.handleFn}
isLoading={props.validateAddress.isLoading} isLoading={props.validateAddress.isLoading}
/> />
<Divider marginY={8} /> <Divider marginY={8} />
<AvataxConfigurationTaxesFragment />
<Divider marginY={8} />
<Box display={"flex"} justifyContent={"space-between"} alignItems={"center"}> <Box display={"flex"} justifyContent={"space-between"} alignItems={"center"}>
{props.leftButton} {props.leftButton}

View file

@ -0,0 +1,71 @@
import { TextLink } from "@saleor/apps-ui";
import { Input } from "@saleor/react-hook-form-macaw";
import { useFormContext } from "react-hook-form";
import { AppToggle } from "../../ui/app-toggle";
import { AvataxConfig } from "../avatax-connection-schema";
import { useAvataxConfigurationStatus } from "./configuration-status";
import { HelperText } from "./form-helper-text";
import { FormSection } from "./form-section";
export const AvataxConfigurationSettingsFragment = () => {
const { control, formState } = useFormContext<AvataxConfig>();
const { status } = useAvataxConfigurationStatus();
const disabled = status === "not_authenticated";
return (
<FormSection title="Settings" disabled={disabled}>
<AppToggle
control={control}
label="Document recording"
disabled={disabled}
helperText={
<HelperText>
When turned off, the document type will always be set to <i>SalesOrder</i>. This means
the transactions will not be recorded in AvaTax. Read more{" "}
<TextLink
href="https://developer.avalara.com/ecommerce-integration-guide/sales-tax-badge/designing/disable-document-recording/"
newTab
>
here
</TextLink>
.
</HelperText>
}
name="isDocumentRecordingEnabled"
/>
<AppToggle
control={control}
label="Autocommit"
disabled={disabled}
helperText={
<HelperText>
If enabled, the order will be automatically{" "}
<TextLink
href="https://developer.avalara.com/communications/dev-guide_rest_v2/commit-uncommit/"
newTab
>
commited to Avalara.
</TextLink>{" "}
</HelperText>
}
name="isAutocommit"
/>
<div>
<Input
disabled={disabled}
control={control}
name="shippingTaxCode"
label="Shipping tax code"
helperText={formState.errors.shippingTaxCode?.message}
/>
<HelperText disabled={disabled}>
Tax code that for the shipping line sent to AvaTax.{" "}
<TextLink newTab href="https://taxcode.avatax.avalara.com">
Must match AvaTax tax codes format.
</TextLink>
</HelperText>
</div>
</FormSection>
);
};

View file

@ -1,34 +0,0 @@
import { TextLink } from "@saleor/apps-ui";
import { Input } from "@saleor/react-hook-form-macaw";
import React from "react";
import { useFormContext } from "react-hook-form";
import { AvataxConfig } from "../avatax-connection-schema";
import { HelperText } from "./form-helper-text";
import { FormSection } from "./form-section";
import { useAvataxConfigurationStatus } from "./configuration-status";
export const AvataxConfigurationTaxesFragment = () => {
const { control, formState } = useFormContext<AvataxConfig>();
const { status } = useAvataxConfigurationStatus();
const disabled = status === "not_authenticated";
return (
<FormSection title="Tax codes" disabled={disabled}>
<div>
<Input
disabled={disabled}
control={control}
name="shippingTaxCode"
label="Shipping tax code"
helperText={formState.errors.shippingTaxCode?.message}
/>
<HelperText disabled={disabled}>
Tax code that for the shipping line sent to Avatax.{" "}
<TextLink newTab href="https://taxcode.avatax.avalara.com">
Must match Avatax tax codes format.
</TextLink>
</HelperText>
</div>
</FormSection>
);
};

View file

@ -5,7 +5,7 @@ import { Section } from "../../ui/app-section";
export const AvataxInstructions = () => { export const AvataxInstructions = () => {
return ( return (
<Section.Description <Section.Description
title="Avatax Configuration" title="AvaTax Configuration"
description={ description={
<> <>
<Text as="p" marginBottom={8}> <Text as="p" marginBottom={8}>
@ -49,7 +49,7 @@ export const AvataxInstructions = () => {
</Text> </Text>
<Text as="p" marginBottom={4}> <Text as="p" marginBottom={4}>
Verifying the Address will display suggestions that reflect the resolution of the Verifying the Address will display suggestions that reflect the resolution of the
address by Avatax address validation service. Applying the suggestions is not required address by AvaTax address validation service. Applying the suggestions is not required
but recommended. If the address is not valid, the calculation of taxes will fail. but recommended. If the address is not valid, the calculation of taxes will fail.
</Text> </Text>
</> </>

View file

@ -31,7 +31,7 @@ const useGetTaxCodes = () => {
React.useEffect(() => { React.useEffect(() => {
if (result.error) { if (result.error) {
notifyError("Error", "Unable to fetch Avatax tax codes."); notifyError("Error", "Unable to fetch AvaTax tax codes.");
setTimeout(() => { setTimeout(() => {
router.push("/configuration"); router.push("/configuration");
}, 1000); }, 1000);
@ -61,7 +61,7 @@ const SelectTaxCode = ({ taxClassId }: { taxClassId: string }) => {
const { mutate: updateMutation } = trpcClient.avataxMatches.upsert.useMutation({ const { mutate: updateMutation } = trpcClient.avataxMatches.upsert.useMutation({
onSuccess() { onSuccess() {
notifySuccess("Success", "Updated Avatax tax code matches"); notifySuccess("Success", "Updated AvaTax tax code matches");
}, },
onError(error) { onError(error) {
notifyError("Error", error.message); notifyError("Error", error.message);
@ -113,7 +113,7 @@ export const AvataxTaxCodeMatcherTable = () => {
<Table.THead> <Table.THead>
<Table.TR> <Table.TR>
<Table.TH>Saleor tax class</Table.TH> <Table.TH>Saleor tax class</Table.TH>
<Table.TH>Avatax tax code</Table.TH> <Table.TH>AvaTax tax code</Table.TH>
</Table.TR> </Table.TR>
</Table.THead> </Table.THead>
<Table.TBody> <Table.TBody>

View file

@ -31,7 +31,7 @@ export const EditAvataxConfiguration = () => {
const { mutate: patchMutation, isLoading: isPatchLoading } = const { mutate: patchMutation, isLoading: isPatchLoading } =
trpcClient.avataxConnection.update.useMutation({ trpcClient.avataxConnection.update.useMutation({
onSuccess() { onSuccess() {
notifySuccess("Success", "Updated Avatax configuration"); notifySuccess("Success", "Updated AvaTax configuration");
refetchProvidersConfigurationData(); refetchProvidersConfigurationData();
}, },
onError(error) { onError(error) {
@ -42,7 +42,7 @@ export const EditAvataxConfiguration = () => {
const { mutate: deleteMutation, isLoading: isDeleteLoading } = const { mutate: deleteMutation, isLoading: isDeleteLoading } =
trpcClient.avataxConnection.delete.useMutation({ trpcClient.avataxConnection.delete.useMutation({
onSuccess() { onSuccess() {
notifySuccess("Success", "Deleted Avatax configuration"); notifySuccess("Success", "Deleted AvaTax configuration");
refetchProvidersConfigurationData(); refetchProvidersConfigurationData();
router.push("/configuration"); router.push("/configuration");
}, },

View file

@ -0,0 +1,5 @@
/* when formSection is disabled, change the color of the a element: */
.formSection[disabled] a,
.formSection[disabled] a span {
color: inherit;
}

View file

@ -1,5 +1,6 @@
import { Box, Text } from "@saleor/macaw-ui/next"; import { Box, Text } from "@saleor/macaw-ui/next";
import React, { PropsWithChildren } from "react"; import React, { PropsWithChildren } from "react";
import styles from "./form-section.module.css";
export const FormSection = ({ export const FormSection = ({
title, title,
@ -8,7 +9,14 @@ export const FormSection = ({
disabled = false, disabled = false,
}: PropsWithChildren<{ title: string; subtitle?: string; disabled?: boolean }>) => { }: PropsWithChildren<{ title: string; subtitle?: string; disabled?: boolean }>) => {
return ( return (
<> <Box
as={"fieldset"}
className={styles.formSection}
disabled={disabled}
__borderWidth={0}
padding={0}
margin={0}
>
<Text <Text
marginBottom={4} marginBottom={4}
as="h3" as="h3"
@ -25,6 +33,6 @@ export const FormSection = ({
<Box display="grid" gridTemplateColumns={2} gap={12}> <Box display="grid" gridTemplateColumns={2} gap={12}>
{children} {children}
</Box> </Box>
</> </Box>
); );
}; };

View file

@ -35,7 +35,7 @@ class ActiveTaxProviderService implements ProviderWebhookService {
} }
case "avatax": { case "avatax": {
this.logger.debug("Selecting Avatax as tax provider"); this.logger.debug("Selecting AvaTax as tax provider");
this.client = new AvataxWebhookService(providerConnection.config, this.authData); this.client = new AvataxWebhookService(providerConnection.config, this.authData);
break; break;
} }

View file

@ -54,14 +54,14 @@ const breadcrumbsForRoute: Record<string, Breadcrumb[]> = {
"/providers/avatax": [ "/providers/avatax": [
...newProviderBreadcrumbs, ...newProviderBreadcrumbs,
{ {
label: "Avatax", label: "AvaTax",
href: "/providers/avatax", href: "/providers/avatax",
}, },
], ],
"/providers/avatax/matcher": [ "/providers/avatax/matcher": [
...newProviderBreadcrumbs, ...newProviderBreadcrumbs,
{ {
label: "Avatax", label: "AvaTax",
href: "/providers/avatax", href: "/providers/avatax",
}, },
{ {
@ -72,7 +72,7 @@ const breadcrumbsForRoute: Record<string, Breadcrumb[]> = {
"/providers/avatax/[id]": [ "/providers/avatax/[id]": [
...newProviderBreadcrumbs, ...newProviderBreadcrumbs,
{ {
label: "Editing Avatax provider", label: "Editing AvaTax provider",
href: "/providers/avatax", href: "/providers/avatax",
}, },
], ],

View file

@ -11,7 +11,7 @@ const providerConfig = {
icon: TaxJarIcon, icon: TaxJarIcon,
}, },
avatax: { avatax: {
label: "Avatax", label: "AvaTax",
icon: AvataxIcon, icon: AvataxIcon,
}, },
stripeTax: { stripeTax: {

View file

@ -5,7 +5,7 @@ import { AppColumns } from "../../../modules/ui/app-columns";
import { Section } from "../../../modules/ui/app-section"; import { Section } from "../../../modules/ui/app-section";
const Header = () => { const Header = () => {
return <Section.Header>Edit your existing Avatax configuration</Section.Header>; return <Section.Header>Edit your existing AvaTax configuration</Section.Header>;
}; };
const EditAvataxPage = () => { const EditAvataxPage = () => {

View file

@ -8,7 +8,7 @@ const Header = () => {
return ( return (
<Box> <Box>
<Text as="p" variant="body"> <Text as="p" variant="body">
Create new Avatax configuration Create new AvaTax configuration
</Text> </Text>
</Box> </Box>
); );

View file

@ -9,21 +9,21 @@ import { useRouter } from "next/router";
import { trpcClient } from "../../../modules/trpc/trpc-client"; import { trpcClient } from "../../../modules/trpc/trpc-client";
const Header = () => { const Header = () => {
return <Section.Header>Match Saleor tax classes to Avatax tax codes</Section.Header>; return <Section.Header>Match Saleor tax classes to AvaTax tax codes</Section.Header>;
}; };
const Description = () => { const Description = () => {
return ( return (
<Section.Description <Section.Description
title="Avatax tax code matcher" title="AvaTax tax code matcher"
description={ description={
<> <>
<Text display="block" as="span" marginBottom={4}> <Text display="block" as="span" marginBottom={4}>
To extend the base tax rate of your products, you can map Saleor tax classes to Avatax To extend the base tax rate of your products, you can map Saleor tax classes to AvaTax
tax codes. tax codes.
</Text> </Text>
<Text display="block" as="span" marginBottom={4}> <Text display="block" as="span" marginBottom={4}>
This way, the product&apos;s Saleor tax class will be used to determine the Avatax tax This way, the product&apos;s Saleor tax class will be used to determine the AvaTax tax
code needed to calculate the tax rate. code needed to calculate the tax rate.
</Text> </Text>
<Text as="p" marginBottom={4}> <Text as="p" marginBottom={4}>
@ -37,9 +37,9 @@ const Description = () => {
view. view.
</Text> </Text>
<Text as="p" marginBottom={4}> <Text as="p" marginBottom={4}>
To learn more about Avatax tax codes, please visit{" "} To learn more about AvaTax tax codes, please visit{" "}
<TextLink href="https://taxcode.avatax.avalara.com/search?q=OF400000" newTab> <TextLink href="https://taxcode.avatax.avalara.com/search?q=OF400000" newTab>
Avatax documentation AvaTax documentation
</TextLink> </TextLink>
. .
</Text> </Text>
@ -55,7 +55,7 @@ const AvataxMatcher = () => {
const { isLoading } = trpcClient.avataxConnection.verifyConnections.useQuery(undefined, { const { isLoading } = trpcClient.avataxConnection.verifyConnections.useQuery(undefined, {
onError: () => { onError: () => {
notifyError("Error", "You must configure Avatax first."); notifyError("Error", "You must configure AvaTax first.");
router.push("/configuration"); router.push("/configuration");
}, },
}); });

View file

@ -38,7 +38,7 @@ const providerConfig = {
avatax: { avatax: {
description: ( description: (
<p> <p>
Avatax is a comprehensive tax automation software service that helps businesses calculate AvaTax is a comprehensive tax automation software service that helps businesses calculate
and manage sales tax accurately and efficiently. and manage sales tax accurately and efficiently.
</p> </p>
), ),