diff --git a/apps/taxes/TESTING.md b/apps/taxes/TESTING.md new file mode 100644 index 0000000..34532ea --- /dev/null +++ b/apps/taxes/TESTING.md @@ -0,0 +1,37 @@ +# Testing + +The difficulty in testing the Taxes App lies in learning about the tax providers themselves. This document will attempt to outline the various edge-cases you can encounter while interacting with the Taxes App. It will be updated as we learn more about the various tax providers. + +## TaxJar + +### UI + +#### 1. Throws "invalid credentials" error while creating a provider + +If you are using the sandbox token, make sure to check the "Sandbox mode" checkbox in the Taxes App UI. + +### Calculating taxes + +#### 1. Transaction doesn't appear in TaxJar + +[It can take up to an hour for TaxJar to process the transaction](https://support.taxjar.com/article/643-why-are-my-transactions-not-appearing-in-taxjar). Please check again after some time. + +If that's not the case, make sure you are using the live token, as the sandbox token does not send transactions to TaxJar. + +#### 2. Taxes App returns taxes = 0 + +Here are known reasons for why the Taxes App may returns taxes = 0 while calculating taxes: + +1. The sales tax in this state is 0. You can check it [here](https://www.taxjar.com/resources/sales-tax/states). +2. You don't have a nexus in this state. Read up on Nexus [here](https://www.taxjar.com/sales-tax/nexus). +3. The customer address in Saleor is invalid. We validate the "ship from" address, but we can't validate the customer address. If you are using a fake database, the generated addresses may be non-existant. You can find examples of valid addresses [here](https://developers.taxjar.com/demo/). +4. TaxJar does not respond. +5. Taxes App broke. + +## Avatax + +### UI + +#### 1. Taxes App UI throws "invalid credentials" error while creating a provider + +If you are using the sandbox token, make sure to check the "Sandbox mode" checkbox in the Taxes App UI. diff --git a/apps/taxes/src/modules/avatax/avatax-connection.router.ts b/apps/taxes/src/modules/avatax/avatax-connection.router.ts index 5aab1bb..9d6c511 100644 --- a/apps/taxes/src/modules/avatax/avatax-connection.router.ts +++ b/apps/taxes/src/modules/avatax/avatax-connection.router.ts @@ -35,6 +35,19 @@ const protectedWithConfigurationService = protectedClientProcedure.use(({ next, ); export const avataxConnectionRouter = router({ + verifyConnections: protectedWithConfigurationService.query(async ({ ctx }) => { + const logger = createLogger({ + name: "avataxConnectionRouter.verifyConnections", + }); + + logger.debug("Route verifyConnections called"); + + await ctx.connectionService.verifyConnections(); + + logger.info("Avatax connections were successfully verified"); + + return { ok: true }; + }), getById: protectedWithConfigurationService.input(getInputSchema).query(async ({ ctx, input }) => { const logger = createLogger({ name: "avataxConnectionRouter.get", diff --git a/apps/taxes/src/modules/avatax/configuration/public-avatax-connection.service.ts b/apps/taxes/src/modules/avatax/configuration/public-avatax-connection.service.ts index f635240..b9504ee 100644 --- a/apps/taxes/src/modules/avatax/configuration/public-avatax-connection.service.ts +++ b/apps/taxes/src/modules/avatax/configuration/public-avatax-connection.service.ts @@ -35,4 +35,12 @@ export class PublicAvataxConnectionService { async delete(id: string) { return this.connectionService.delete(id); } + + async verifyConnections() { + const connections = await this.connectionService.getAll(); + + if (connections.length === 0) { + throw new Error("No Avatax connections found"); + } + } } diff --git a/apps/taxes/src/modules/avatax/ui/avatax-configuration-form.tsx b/apps/taxes/src/modules/avatax/ui/avatax-configuration-form.tsx index f3f21c5..e0f6fef 100644 --- a/apps/taxes/src/modules/avatax/ui/avatax-configuration-form.tsx +++ b/apps/taxes/src/modules/avatax/ui/avatax-configuration-form.tsx @@ -22,7 +22,7 @@ type AvataxConfigurationFormProps = { onSubmit: (data: AvataxConfig) => void; defaultValues: AvataxConfig; isLoading: boolean; - cancelButton: React.ReactNode; + leftButton: React.ReactNode; }; export const AvataxConfigurationForm = (props: AvataxConfigurationFormProps) => { @@ -48,7 +48,7 @@ export const AvataxConfigurationForm = (props: AvataxConfigurationFormProps) => -
+ - {props.cancelButton} + {props.leftButton} - diff --git a/apps/taxes/src/modules/avatax/ui/avatax-tax-code-matcher-table.tsx b/apps/taxes/src/modules/avatax/ui/avatax-tax-code-matcher-table.tsx index 8229d07..9b44476 100644 --- a/apps/taxes/src/modules/avatax/ui/avatax-tax-code-matcher-table.tsx +++ b/apps/taxes/src/modules/avatax/ui/avatax-tax-code-matcher-table.tsx @@ -5,10 +5,46 @@ import { Table } from "../../ui/table"; import { Select } from "../../ui/_select"; import { Box, Text } from "@saleor/macaw-ui/next"; import { AppCard } from "../../ui/app-card"; +import { useRouter } from "next/router"; + +const useGetTaxCodes = () => { + const { data: providers } = trpcClient.providersConfiguration.getAll.useQuery(); + const { notifyError } = useDashboardNotification(); + const router = useRouter(); + + /* + * Tax Code Matcher is only available when there is at least one connection. + * The reason for it is that we need any working credentials to fetch the provider tax codes. + */ + const firstConnectionId = providers?.[0].id; + + const result = trpcClient.avataxTaxCodes.getAllForId.useQuery( + { + connectionId: firstConnectionId!, + }, + { + enabled: firstConnectionId !== undefined, + // Retry once, because it's possible we may get a timeout for such a big request. + retry: 1, + } + ); + + React.useEffect(() => { + if (result.error) { + notifyError("Error", "Unable to fetch Avatax tax codes."); + setTimeout(() => { + router.push("/configuration"); + }, 1000); + } + }, [notifyError, result.error, router]); + + return result; +}; const SelectTaxCode = ({ taxClassId }: { taxClassId: string }) => { const [value, setValue] = React.useState(""); const { notifySuccess, notifyError } = useDashboardNotification(); + const { data: taxCodes = [], isLoading: isCodesLoading } = useGetTaxCodes(); const { data: avataxMatches, isLoading: isMatchesLoading } = trpcClient.avataxMatches.getAll.useQuery(); @@ -32,24 +68,6 @@ const SelectTaxCode = ({ taxClassId }: { taxClassId: string }) => { }, }); - const { data: providers } = trpcClient.providersConfiguration.getAll.useQuery(); - - /* - * Tax Code Matcher is only available when there is at least one connection. - * The reason for it is that we need any working credentials to fetch the provider tax codes. - */ - const firstConnectionId = providers?.[0].id; - - const { data: taxCodes = [], isLoading: isCodesLoading } = - trpcClient.avataxTaxCodes.getAllForId.useQuery( - { - connectionId: firstConnectionId!, - }, - { - enabled: firstConnectionId !== undefined, - } - ); - const changeValue = (avataxTaxCode: string) => { setValue(avataxTaxCode); updateMutation({ diff --git a/apps/taxes/src/modules/avatax/ui/create-avatax-configuration.tsx b/apps/taxes/src/modules/avatax/ui/create-avatax-configuration.tsx index 9ac2d43..6237735 100644 --- a/apps/taxes/src/modules/avatax/ui/create-avatax-configuration.tsx +++ b/apps/taxes/src/modules/avatax/ui/create-avatax-configuration.tsx @@ -37,8 +37,12 @@ export const CreateAvataxConfiguration = () => { isLoading={isCreateLoading} onSubmit={submitHandler} defaultValues={defaultAvataxConfig} - cancelButton={ - } diff --git a/apps/taxes/src/modules/avatax/ui/edit-avatax-configuration.tsx b/apps/taxes/src/modules/avatax/ui/edit-avatax-configuration.tsx index 8fb434a..23f6fac 100644 --- a/apps/taxes/src/modules/avatax/ui/edit-avatax-configuration.tsx +++ b/apps/taxes/src/modules/avatax/ui/edit-avatax-configuration.tsx @@ -96,8 +96,8 @@ export const EditAvataxConfiguration = () => { isLoading={isPatchLoading} onSubmit={submitHandler} defaultValues={data.config} - cancelButton={ - } diff --git a/apps/taxes/src/modules/channel-configuration/ui/channel-list.tsx b/apps/taxes/src/modules/channel-configuration/ui/channel-list.tsx index eed38ac..5979821 100644 --- a/apps/taxes/src/modules/channel-configuration/ui/channel-list.tsx +++ b/apps/taxes/src/modules/channel-configuration/ui/channel-list.tsx @@ -21,7 +21,9 @@ const NoChannelConfigured = () => { justifyContent={"center"} > No channels configured yet - + ); }; @@ -43,7 +45,7 @@ export const ChannelList = () => { const isEmpty = isFetched && !isAnyChannelConfigured; return ( - + {isFetching ? ( ) : ( diff --git a/apps/taxes/src/modules/channel-configuration/ui/channel-section.tsx b/apps/taxes/src/modules/channel-configuration/ui/channel-section.tsx index c68fac0..f353db4 100644 --- a/apps/taxes/src/modules/channel-configuration/ui/channel-section.tsx +++ b/apps/taxes/src/modules/channel-configuration/ui/channel-section.tsx @@ -6,6 +6,7 @@ const Intro = () => { return ( This table displays all the channels configured to use the tax app as the tax calculation @@ -13,7 +14,10 @@ const Intro = () => {

You can change the tax configuration method for each channel in the{" "} - Configuration → Taxes view. + + Configuration → Taxes + {" "} + view. } /> diff --git a/apps/taxes/src/modules/provider-connections/ui/providers-list.tsx b/apps/taxes/src/modules/provider-connections/ui/providers-list.tsx index bca22f2..89bb6f8 100644 --- a/apps/taxes/src/modules/provider-connections/ui/providers-list.tsx +++ b/apps/taxes/src/modules/provider-connections/ui/providers-list.tsx @@ -17,7 +17,9 @@ const AddProvider = () => { justifyContent={"center"} > No providers configured yet - + ); }; @@ -40,7 +42,7 @@ export const ProvidersList = () => { const isNoResult = isFetched && !isProvider; return ( - + {isFetching ? ( ) : ( @@ -55,7 +57,12 @@ export const ProvidersList = () => { > - + )} diff --git a/apps/taxes/src/modules/provider-connections/ui/providers-section.tsx b/apps/taxes/src/modules/provider-connections/ui/providers-section.tsx index a0c9824..0510108 100644 --- a/apps/taxes/src/modules/provider-connections/ui/providers-section.tsx +++ b/apps/taxes/src/modules/provider-connections/ui/providers-section.tsx @@ -6,6 +6,7 @@ const Intro = () => { return ( Saleor offers two ways of calculating taxes: flat or dynamic rates. diff --git a/apps/taxes/src/modules/taxes/active-connection.test.ts b/apps/taxes/src/modules/taxes/get-active-connection-service.test.ts similarity index 89% rename from apps/taxes/src/modules/taxes/active-connection.test.ts rename to apps/taxes/src/modules/taxes/get-active-connection-service.test.ts index ced0957..2c40793 100644 --- a/apps/taxes/src/modules/taxes/active-connection.test.ts +++ b/apps/taxes/src/modules/taxes/get-active-connection-service.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it, vi } from "vitest"; import { MetadataItem } from "../../../generated/graphql"; import { ChannelsConfig } from "../channel-configuration/channel-config"; import { ProviderConnections } from "../provider-connections/provider-connections"; -import { getActiveConnection } from "./active-connection"; +import { getActiveConnectionService } from "./get-active-connection-service"; import { AuthData } from "@saleor/app-sdk/APL"; const mockedInvalidMetadata: MetadataItem[] = [ @@ -95,22 +95,22 @@ const mockedAuthData: AuthData = { vi.stubEnv("SECRET_KEY", mockedSecretKey); -describe("getActiveConnection", () => { +describe("getActiveConnectionService", () => { it("throws error when channel slug is missing", () => { - expect(() => getActiveConnection("", mockedInvalidMetadata, mockedAuthData)).toThrow( + expect(() => getActiveConnectionService("", mockedInvalidMetadata, mockedAuthData)).toThrow( "Channel slug was not found in the webhook payload" ); }); it("throws error when there are no metadata items", () => { - expect(() => getActiveConnection("default-channel", [], mockedAuthData)).toThrow( + expect(() => getActiveConnectionService("default-channel", [], mockedAuthData)).toThrow( "App encryptedMetadata was not found in the webhook payload" ); }); it("throws error when no providerConnectionId was found", () => { expect(() => - getActiveConnection( + getActiveConnectionService( "default-channel", [ { @@ -129,7 +129,7 @@ describe("getActiveConnection", () => { it("throws error when no channel was found for channelSlug", () => { expect(() => - getActiveConnection( + getActiveConnectionService( "invalid-channel", [ { @@ -147,7 +147,7 @@ describe("getActiveConnection", () => { }); it("returns provider when data is correct", () => { - const result = getActiveConnection( + const result = getActiveConnectionService( "default-channel", [ { diff --git a/apps/taxes/src/modules/taxes/active-connection.ts b/apps/taxes/src/modules/taxes/get-active-connection-service.ts similarity index 91% rename from apps/taxes/src/modules/taxes/active-connection.ts rename to apps/taxes/src/modules/taxes/get-active-connection-service.ts index 2d3e59a..480b165 100644 --- a/apps/taxes/src/modules/taxes/active-connection.ts +++ b/apps/taxes/src/modules/taxes/get-active-connection-service.ts @@ -14,13 +14,13 @@ import { TaxJarWebhookService } from "../taxjar/taxjar-webhook.service"; import { ProviderWebhookService } from "./tax-provider-webhook"; // todo: refactor to a factory -export class ActiveTaxProvider implements ProviderWebhookService { +class ActiveTaxProviderService implements ProviderWebhookService { private logger: Logger; private client: TaxJarWebhookService | AvataxWebhookService; constructor(providerConnection: ProviderConnection, private authData: AuthData) { this.logger = createLogger({ - name: "ActiveTaxProvider", + name: "ActiveTaxProviderService", }); const taxProviderName = providerConnection.provider; @@ -57,13 +57,13 @@ export class ActiveTaxProvider implements ProviderWebhookService { } } -export function getActiveConnection( +export function getActiveConnectionService( channelSlug: string | undefined, encryptedMetadata: MetadataItem[], authData: AuthData -): ActiveTaxProvider { +): ActiveTaxProviderService { const logger = createLogger({ - name: "getActiveConnection", + name: "getActiveConnectionService", }); if (!channelSlug) { @@ -100,7 +100,7 @@ export function getActiveConnection( throw new Error(`Channel config providerConnectionId does not match any providers`); } - const taxProvider = new ActiveTaxProvider(providerConnection, authData); + const taxProvider = new ActiveTaxProviderService(providerConnection, authData); return taxProvider; } diff --git a/apps/taxes/src/modules/taxjar/configuration/public-taxjar-connection.service.ts b/apps/taxes/src/modules/taxjar/configuration/public-taxjar-connection.service.ts index a22da58..f6cf40d 100644 --- a/apps/taxes/src/modules/taxjar/configuration/public-taxjar-connection.service.ts +++ b/apps/taxes/src/modules/taxjar/configuration/public-taxjar-connection.service.ts @@ -35,4 +35,12 @@ export class PublicTaxJarConnectionService { async delete(id: string) { return this.connectionService.delete(id); } + + async verifyConnections() { + const connections = await this.connectionService.getAll(); + + if (connections.length === 0) { + throw new Error("No TaxJar connections found"); + } + } } diff --git a/apps/taxes/src/modules/taxjar/taxjar-client.ts b/apps/taxes/src/modules/taxjar/taxjar-client.ts index 6922696..90349ff 100644 --- a/apps/taxes/src/modules/taxjar/taxjar-client.ts +++ b/apps/taxes/src/modules/taxjar/taxjar-client.ts @@ -47,12 +47,11 @@ export class TaxJarClient { } /** - * TaxJar validateAddress doesn't work. It's turned off for now. + * In the past, we've had some problems with TaxJar validateAddress. It looks like it works now, but we should keep an eye on it. * @see https://github.com/taxjar/taxjar-node/issues/70 - * @todo Revisit this when TaxJar fixes the issue. Alternatively, create a custom validation. */ async validateAddress({ params }: ValidateAddressArgs) { - // return this.client.validateAddress(params); + return this.client.validateAddress(params); } async getTaxCodes() { diff --git a/apps/taxes/src/modules/taxjar/taxjar-connection.router.ts b/apps/taxes/src/modules/taxjar/taxjar-connection.router.ts index bac452d..9f67d2d 100644 --- a/apps/taxes/src/modules/taxjar/taxjar-connection.router.ts +++ b/apps/taxes/src/modules/taxjar/taxjar-connection.router.ts @@ -35,6 +35,19 @@ const protectedWithConfigurationService = protectedClientProcedure.use(({ next, ); export const taxjarConnectionRouter = router({ + verifyConnections: protectedWithConfigurationService.query(async ({ ctx }) => { + const logger = createLogger({ + name: "taxjarConnectionRouter.verifyConnections", + }); + + logger.debug("Route verifyConnections called"); + + await ctx.connectionService.verifyConnections(); + + logger.info("TaxJar connections were successfully verified"); + + return { ok: true }; + }), getById: protectedWithConfigurationService.input(getInputSchema).query(async ({ ctx, input }) => { const logger = createLogger({ name: "taxjarConnectionRouter.get", diff --git a/apps/taxes/src/modules/taxjar/ui/create-taxjar-configuration.tsx b/apps/taxes/src/modules/taxjar/ui/create-taxjar-configuration.tsx index 3dfca16..a4b2087 100644 --- a/apps/taxes/src/modules/taxjar/ui/create-taxjar-configuration.tsx +++ b/apps/taxes/src/modules/taxjar/ui/create-taxjar-configuration.tsx @@ -37,8 +37,12 @@ export const CreateTaxJarConfiguration = () => { isLoading={isCreateLoading} onSubmit={submitHandler} defaultValues={defaultTaxJarConfig} - cancelButton={ - } diff --git a/apps/taxes/src/modules/taxjar/ui/edit-taxjar-configuration.tsx b/apps/taxes/src/modules/taxjar/ui/edit-taxjar-configuration.tsx index 146f314..1a0154d 100644 --- a/apps/taxes/src/modules/taxjar/ui/edit-taxjar-configuration.tsx +++ b/apps/taxes/src/modules/taxjar/ui/edit-taxjar-configuration.tsx @@ -95,8 +95,8 @@ export const EditTaxJarConfiguration = () => { isLoading={isPatchLoading} onSubmit={submitHandler} defaultValues={data.config} - cancelButton={ - } diff --git a/apps/taxes/src/modules/taxjar/ui/taxjar-configuration-form.tsx b/apps/taxes/src/modules/taxjar/ui/taxjar-configuration-form.tsx index 44aad3a..b22351a 100644 --- a/apps/taxes/src/modules/taxjar/ui/taxjar-configuration-form.tsx +++ b/apps/taxes/src/modules/taxjar/ui/taxjar-configuration-form.tsx @@ -23,7 +23,7 @@ type TaxJarConfigurationFormProps = { onSubmit: (data: TaxJarConfig) => void; defaultValues: TaxJarConfig; isLoading: boolean; - cancelButton: React.ReactNode; + leftButton: React.ReactNode; }; export const TaxJarConfigurationForm = (props: TaxJarConfigurationFormProps) => { @@ -49,7 +49,7 @@ export const TaxJarConfigurationForm = (props: TaxJarConfigurationFormProps) => - + - {props.cancelButton} - diff --git a/apps/taxes/src/modules/taxjar/ui/taxjar-tax-code-matcher-table.tsx b/apps/taxes/src/modules/taxjar/ui/taxjar-tax-code-matcher-table.tsx index 989103c..7f434b0 100644 --- a/apps/taxes/src/modules/taxjar/ui/taxjar-tax-code-matcher-table.tsx +++ b/apps/taxes/src/modules/taxjar/ui/taxjar-tax-code-matcher-table.tsx @@ -5,6 +5,41 @@ import { Table } from "../../ui/table"; import { Select } from "../../ui/_select"; import { Box, Text } from "@saleor/macaw-ui/next"; import { AppCard } from "../../ui/app-card"; +import { useRouter } from "next/router"; + +const useGetTaxCodes = () => { + const { data: providers } = trpcClient.providersConfiguration.getAll.useQuery(); + const { notifyError } = useDashboardNotification(); + const router = useRouter(); + + /* + * Tax Code Matcher is only available when there is at least one connection. + * The reason for it is that we need any working credentials to fetch the provider tax codes. + */ + const firstConnectionId = providers?.[0].id; + + const result = trpcClient.taxJarTaxCodes.getAllForId.useQuery( + { + connectionId: firstConnectionId!, + }, + { + enabled: firstConnectionId !== undefined, + // Retry once, because it's possible we may get a timeout for such a big request. + retry: 1, + } + ); + + React.useEffect(() => { + if (result.error) { + notifyError("Error", "Unable to fetch TaxJar tax codes."); + setTimeout(() => { + router.push("/configuration"); + }, 1000); + } + }, [notifyError, result.error, router]); + + return result; +}; const SelectTaxCode = ({ taxClassId }: { taxClassId: string }) => { const [value, setValue] = React.useState(""); @@ -32,23 +67,7 @@ const SelectTaxCode = ({ taxClassId }: { taxClassId: string }) => { }, }); - const { data: providers } = trpcClient.providersConfiguration.getAll.useQuery(); - - /* - * Tax Code Matcher is only available when there is at least one connection. - * The reason for it is that we need any working credentials to fetch the provider tax codes. - */ - const firstConnectionId = providers?.[0].id; - - const { data: taxCodes = [], isLoading: isCodesLoading } = - trpcClient.taxJarTaxCodes.getAllForId.useQuery( - { - connectionId: firstConnectionId!, - }, - { - enabled: firstConnectionId !== undefined, - } - ); + const { data: taxCodes = [], isLoading: isCodesLoading } = useGetTaxCodes(); const changeValue = (taxJarTaxCode: string) => { setValue(taxJarTaxCode); diff --git a/apps/taxes/src/modules/ui/app-section.tsx b/apps/taxes/src/modules/ui/app-section.tsx index 2f5fe28..3eb488a 100644 --- a/apps/taxes/src/modules/ui/app-section.tsx +++ b/apps/taxes/src/modules/ui/app-section.tsx @@ -1,11 +1,11 @@ -import { Box, Text } from "@saleor/macaw-ui/next"; -import { PropsWithChildren } from "react"; +import { Box, PropsWithBox, Text } from "@saleor/macaw-ui/next"; +import React from "react"; const MAX_WIDTH = "480px"; -const Header = ({ children }: PropsWithChildren) => { +const Header = ({ children, ...props }: PropsWithBox<{ children: React.ReactNode }>) => { return ( - + {children} @@ -16,12 +16,13 @@ const Header = ({ children }: PropsWithChildren) => { const Description = ({ title, description, -}: { + ...props +}: PropsWithBox<{ title: React.ReactNode; description: React.ReactNode; -}) => { +}>) => { return ( - + {title} diff --git a/apps/taxes/src/modules/ui/app-toggle.tsx b/apps/taxes/src/modules/ui/app-toggle.tsx index a997d69..c47f55f 100644 --- a/apps/taxes/src/modules/ui/app-toggle.tsx +++ b/apps/taxes/src/modules/ui/app-toggle.tsx @@ -22,7 +22,7 @@ export const AppToggle = ({ {/* without type="button", radix toggle value change triggers form submission */} - {label} + {label} {helperText} diff --git a/apps/taxes/src/modules/ui/matcher-section.tsx b/apps/taxes/src/modules/ui/matcher-section.tsx index fd8804a..6627e7b 100644 --- a/apps/taxes/src/modules/ui/matcher-section.tsx +++ b/apps/taxes/src/modules/ui/matcher-section.tsx @@ -7,58 +7,75 @@ import { Table } from "./table"; import { useRouter } from "next/router"; const MatcherTable = () => { - const { data: connections = [] } = trpcClient.providersConfiguration.getAll.useQuery(); + const { data: connections = [], isLoading } = trpcClient.providersConfiguration.getAll.useQuery(); const isAvatax = connections.some(({ provider }) => provider === "avatax"); const isTaxJar = connections.some(({ provider }) => provider === "taxjar"); + const isConfigured = isAvatax || isTaxJar; const router = useRouter(); return ( - - - - - Provider - - - - {isAvatax && ( - - - - - - - {" "} - {" "} - - + + {isLoading ? ( + + Loading... + + ) : ( + <> + {isConfigured ? ( + + + + Provider + + + + {isAvatax && ( + + + + + + + {" "} + {" "} + + + )} + {isTaxJar && ( + + + + + + + {" "} + + + + )} + + + ) : ( + + You must configure a tax provider first + )} - {isTaxJar && ( - - - - - - - {" "} - - - - )} - - + + )} ); }; @@ -66,6 +83,7 @@ const MatcherTable = () => { const Intro = () => { return ( diff --git a/apps/taxes/src/modules/ui/providers-table.tsx b/apps/taxes/src/modules/ui/providers-table.tsx index 5de7c28..127a0d1 100644 --- a/apps/taxes/src/modules/ui/providers-table.tsx +++ b/apps/taxes/src/modules/ui/providers-table.tsx @@ -30,7 +30,9 @@ export const ProvidersTable = () => { itemClickHandler(item)}> - + diff --git a/apps/taxes/src/pages/api/webhooks/checkout-calculate-taxes.ts b/apps/taxes/src/pages/api/webhooks/checkout-calculate-taxes.ts index cd355d0..3987f59 100644 --- a/apps/taxes/src/pages/api/webhooks/checkout-calculate-taxes.ts +++ b/apps/taxes/src/pages/api/webhooks/checkout-calculate-taxes.ts @@ -6,7 +6,7 @@ import { import { saleorApp } from "../../../../saleor-app"; import { createLogger } from "../../../lib/logger"; import { WebhookResponse } from "../../../modules/app/webhook-response"; -import { getActiveConnection } from "../../../modules/taxes/active-connection"; +import { getActiveConnectionService } from "../../../modules/taxes/get-active-connection-service"; export const config = { api: { @@ -54,10 +54,14 @@ export default checkoutCalculateTaxesSyncWebhook.createHandler(async (req, res, try { const appMetadata = payload.recipient?.privateMetadata ?? []; const channelSlug = payload.taxBase.channel.slug; - const taxProvider = getActiveConnection(channelSlug, appMetadata, ctx.authData); + const activeConnectionService = getActiveConnectionService( + channelSlug, + appMetadata, + ctx.authData + ); - logger.info({ taxProvider }, "Will calculate taxes using the tax provider:"); - const calculatedTaxes = await taxProvider.calculateTaxes(payload.taxBase); + logger.info("Found active connection service. Calculating taxes..."); + const calculatedTaxes = await activeConnectionService.calculateTaxes(payload.taxBase); logger.info({ calculatedTaxes }, "Taxes calculated"); return webhookResponse.success(ctx.buildResponse(calculatedTaxes)); diff --git a/apps/taxes/src/pages/api/webhooks/order-calculate-taxes.ts b/apps/taxes/src/pages/api/webhooks/order-calculate-taxes.ts index df221d1..ec54daf 100644 --- a/apps/taxes/src/pages/api/webhooks/order-calculate-taxes.ts +++ b/apps/taxes/src/pages/api/webhooks/order-calculate-taxes.ts @@ -5,7 +5,7 @@ import { } from "../../../../generated/graphql"; import { saleorApp } from "../../../../saleor-app"; import { createLogger } from "../../../lib/logger"; -import { getActiveConnection } from "../../../modules/taxes/active-connection"; +import { getActiveConnectionService } from "../../../modules/taxes/get-active-connection-service"; import { WebhookResponse } from "../../../modules/app/webhook-response"; export const config = { @@ -54,10 +54,14 @@ export default orderCalculateTaxesSyncWebhook.createHandler(async (req, res, ctx try { const appMetadata = payload.recipient?.privateMetadata ?? []; const channelSlug = payload.taxBase.channel.slug; - const taxProvider = getActiveConnection(channelSlug, appMetadata, ctx.authData); + const activeConnectionService = getActiveConnectionService( + channelSlug, + appMetadata, + ctx.authData + ); - logger.info({ taxProvider }, "Will calculate taxes using the tax provider:"); - const calculatedTaxes = await taxProvider.calculateTaxes(payload.taxBase); + logger.info("Found active connection service. Calculating taxes..."); + const calculatedTaxes = await activeConnectionService.calculateTaxes(payload.taxBase); logger.info({ calculatedTaxes }, "Taxes calculated"); return webhookResponse.success(ctx.buildResponse(calculatedTaxes)); diff --git a/apps/taxes/src/pages/api/webhooks/order-created.ts b/apps/taxes/src/pages/api/webhooks/order-created.ts index 1ea03e8..76e12c2 100644 --- a/apps/taxes/src/pages/api/webhooks/order-created.ts +++ b/apps/taxes/src/pages/api/webhooks/order-created.ts @@ -9,7 +9,7 @@ import { } from "../../../../generated/graphql"; import { saleorApp } from "../../../../saleor-app"; import { createLogger } from "../../../lib/logger"; -import { getActiveConnection } from "../../../modules/taxes/active-connection"; +import { getActiveConnectionService } from "../../../modules/taxes/get-active-connection-service"; import { Client } from "urql"; import { WebhookResponse } from "../../../modules/app/webhook-response"; import { PROVIDER_ORDER_ID_KEY } from "../../../modules/avatax/order-fulfilled/avatax-order-fulfilled-payload-transformer"; @@ -69,14 +69,14 @@ export default orderCreatedAsyncWebhook.createHandler(async (req, res, ctx) => { const { saleorApiUrl, token } = authData; const webhookResponse = new WebhookResponse(res); - logger.info({ payload }, "Handler called with payload"); + logger.info("Handler called with payload"); try { const appMetadata = payload.recipient?.privateMetadata ?? []; const channelSlug = payload.order?.channel.slug; - const taxProvider = getActiveConnection(channelSlug, appMetadata, ctx.authData); + const taxProvider = getActiveConnectionService(channelSlug, appMetadata, ctx.authData); - logger.info({ taxProvider }, "Fetched taxProvider"); + logger.info("Fetched taxProvider"); // todo: figure out what fields are needed and add validation if (!payload.order) { diff --git a/apps/taxes/src/pages/api/webhooks/order-fulfilled.ts b/apps/taxes/src/pages/api/webhooks/order-fulfilled.ts index d8d92f2..b7278f3 100644 --- a/apps/taxes/src/pages/api/webhooks/order-fulfilled.ts +++ b/apps/taxes/src/pages/api/webhooks/order-fulfilled.ts @@ -5,7 +5,7 @@ import { } from "../../../../generated/graphql"; import { saleorApp } from "../../../../saleor-app"; import { createLogger } from "../../../lib/logger"; -import { getActiveConnection } from "../../../modules/taxes/active-connection"; +import { getActiveConnectionService } from "../../../modules/taxes/get-active-connection-service"; import { WebhookResponse } from "../../../modules/app/webhook-response"; export const config = { api: { @@ -31,22 +31,22 @@ export default orderFulfilledAsyncWebhook.createHandler(async (req, res, ctx) => const { payload } = ctx; const webhookResponse = new WebhookResponse(res); - logger.info({ payload }, "Handler called with payload"); + logger.info("Handler called with payload"); try { const appMetadata = payload.recipient?.privateMetadata ?? []; const channelSlug = payload.order?.channel.slug; - const taxProvider = getActiveConnection(channelSlug, appMetadata, ctx.authData); + const taxProvider = getActiveConnectionService(channelSlug, appMetadata, ctx.authData); - logger.info({ taxProvider }, "Fetched taxProvider"); + logger.info("Fetched taxProvider"); // todo: figure out what fields are needed and add validation if (!payload.order) { return webhookResponse.error(new Error("Insufficient order data")); } - const fulfilledOrder = await taxProvider.fulfillOrder(payload.order); + await taxProvider.fulfillOrder(payload.order); - logger.info({ fulfilledOrder }, "Order fulfilled"); + logger.info("Order fulfilled"); return webhookResponse.success(); } catch (error) { diff --git a/apps/taxes/src/pages/configuration.tsx b/apps/taxes/src/pages/configuration.tsx index 75bb06f..2f8026f 100644 --- a/apps/taxes/src/pages/configuration.tsx +++ b/apps/taxes/src/pages/configuration.tsx @@ -1,6 +1,5 @@ import { ChannelSection } from "../modules/channel-configuration/ui/channel-section"; import { ProvidersSection } from "../modules/provider-connections/ui/providers-section"; -import { trpcClient } from "../modules/trpc/trpc-client"; import { AppColumns } from "../modules/ui/app-columns"; import { Section } from "../modules/ui/app-section"; import { MatcherSection } from "../modules/ui/matcher-section"; @@ -14,14 +13,11 @@ const Header = () => { }; const ConfigurationPage = () => { - const { data: providers = [] } = trpcClient.providersConfiguration.getAll.useQuery(); - const isProviders = providers.length > 0; - return ( }> - {isProviders && } + ); }; diff --git a/apps/taxes/src/pages/providers/avatax/matcher.tsx b/apps/taxes/src/pages/providers/avatax/matcher.tsx index 3b84f48..bb1b3ca 100644 --- a/apps/taxes/src/pages/providers/avatax/matcher.tsx +++ b/apps/taxes/src/pages/providers/avatax/matcher.tsx @@ -1,9 +1,12 @@ -import { Text } from "@saleor/macaw-ui/next"; +import { Box, Text } from "@saleor/macaw-ui/next"; import { AvataxTaxCodeMatcherTable } from "../../../modules/avatax/ui/avatax-tax-code-matcher-table"; import { AppColumns } from "../../../modules/ui/app-columns"; import { AppDashboardLink } from "../../../modules/ui/app-dashboard-link"; import { Section } from "../../../modules/ui/app-section"; import { TextLink } from "@saleor/apps-ui"; +import { useDashboardNotification } from "@saleor/apps-shared"; +import { useRouter } from "next/router"; +import { trpcClient } from "../../../modules/trpc/trpc-client"; const Header = () => { return Match Saleor tax classes to Avatax tax codes; @@ -25,7 +28,10 @@ const Description = () => { If you haven't created any tax classes yet, you can do it in the{" "} - + Configuration → Taxes → Tax classes {" "} view. @@ -44,6 +50,24 @@ const Description = () => { }; const AvataxMatcher = () => { + const router = useRouter(); + const { notifyError } = useDashboardNotification(); + + const { isLoading } = trpcClient.avataxConnection.verifyConnections.useQuery(undefined, { + onError: () => { + notifyError("Error", "You must configure Avatax first."); + router.push("/configuration"); + }, + }); + + if (isLoading) { + return ( + + Loading... + + ); + } + return ( }> @@ -52,7 +76,4 @@ const AvataxMatcher = () => { ); }; -/* - * todo: add redirect if no connection - */ export default AvataxMatcher; diff --git a/apps/taxes/src/pages/providers/index.tsx b/apps/taxes/src/pages/providers/index.tsx index 4887dfa..4f26b1e 100644 --- a/apps/taxes/src/pages/providers/index.tsx +++ b/apps/taxes/src/pages/providers/index.tsx @@ -83,7 +83,12 @@ const ProviderCard = ({ {!isComingSoon && ( - + )} diff --git a/apps/taxes/src/pages/providers/taxjar/matcher.tsx b/apps/taxes/src/pages/providers/taxjar/matcher.tsx index 069f278..ce8e0a3 100644 --- a/apps/taxes/src/pages/providers/taxjar/matcher.tsx +++ b/apps/taxes/src/pages/providers/taxjar/matcher.tsx @@ -1,9 +1,12 @@ -import { Text } from "@saleor/macaw-ui/next"; +import { Box, Text } from "@saleor/macaw-ui/next"; import { AppColumns } from "../../../modules/ui/app-columns"; import { AppDashboardLink } from "../../../modules/ui/app-dashboard-link"; import { Section } from "../../../modules/ui/app-section"; import { TextLink } from "@saleor/apps-ui"; import { TaxJarTaxCodeMatcherTable } from "../../../modules/taxjar/ui/taxjar-tax-code-matcher-table"; +import { trpcClient } from "../../../modules/trpc/trpc-client"; +import { useRouter } from "next/router"; +import { useDashboardNotification } from "@saleor/apps-shared"; const Header = () => { return Match Saleor tax classes to TaxJar tax categories; @@ -25,7 +28,10 @@ const Description = () => { If you haven't created any tax classes yet, you can do it in the{" "} - + Configuration → Taxes → Tax classes {" "} view. @@ -47,6 +53,24 @@ const Description = () => { }; const TaxJarMatcher = () => { + const router = useRouter(); + const { notifyError } = useDashboardNotification(); + + const { isLoading } = trpcClient.taxJarConnection.verifyConnections.useQuery(undefined, { + onError: () => { + notifyError("Error", "You must configure TaxJar first."); + router.push("/configuration"); + }, + }); + + if (isLoading) { + return ( + + Loading... + + ); + } + return ( }> @@ -55,7 +79,4 @@ const TaxJarMatcher = () => { ); }; -/* - * todo: add redirect if no connection - */ export default TaxJarMatcher;