fix/333 taxes not calculated (#345)

* refactor: 🔊 improve logging in taxes

* refactor: 🔥 redundant channels call

* refactor: ♻️ get app config from payload

* build: add changeset

* refactor: 🔊 routers info -> debug

* refactor: 🔊 redact logs
This commit is contained in:
Adrian Pilarczyk 2023-03-30 13:12:52 +02:00 committed by GitHub
parent 741e9104ed
commit d55b2f9b2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 174 additions and 105 deletions

View file

@ -0,0 +1,5 @@
---
"saleor-app-taxes": minor
---
Improve webhook processing time by adding the app config to subscription payload instead of fetching it separately.

View file

@ -4,5 +4,11 @@ fragment CalculateTaxesEvent on Event {
taxBase { taxBase {
...TaxBase ...TaxBase
} }
recipient {
privateMetadata {
key
value
}
}
} }
} }

View file

@ -11,6 +11,12 @@ export const logger = pino({
}, },
} }
: undefined, : undefined,
redact: [
"metadata",
"providerInstance.config.username",
"providerInstance.config.password",
"providerInstance.config.apiKey",
],
}); });
export const createLogger = logger.child.bind(logger); export const createLogger = logger.child.bind(logger);

View file

@ -65,6 +65,14 @@ const taxBaseLineSchema = z.object({
export const calculateTaxesPayloadSchema: z.ZodType<ExpectedWebhookPayload> = z.object({ export const calculateTaxesPayloadSchema: z.ZodType<ExpectedWebhookPayload> = z.object({
__typename: z.literal("CalculateTaxes"), __typename: z.literal("CalculateTaxes"),
recipient: z.object({
privateMetadata: z.array(
z.object({
key: z.string(),
value: z.string(),
})
),
}),
taxBase: z.object({ taxBase: z.object({
currency: z.string(), currency: z.string(),
channel: z.object({ channel: z.object({

View file

@ -0,0 +1,39 @@
import { decrypt } from "@saleor/app-sdk/settings-manager";
import { ExpectedWebhookPayload } from "../../lib/saleor/schema";
import { ChannelsConfig, channelsSchema } from "../channels-configuration/channels-config";
import { ProvidersConfig, providersSchema } from "../providers-configuration/providers-config";
export const getAppConfig = (payload: ExpectedWebhookPayload) => {
const metadata = payload.recipient?.privateMetadata;
let providersConfig = [] as ProvidersConfig;
let channelsConfig = {} as ChannelsConfig;
const secretKey = process.env.SECRET_KEY;
if (!secretKey) {
throw new Error("SECRET_KEY env variable is not set");
}
// * The App Config contains two types of data: providers and channels.
// * We must recognize which one we are dealing with and parse it accordingly.
metadata?.forEach((item) => {
const decrypted = decrypt(item.value, secretKey);
const parsed = JSON.parse(decrypted);
const providersValidation = providersSchema.safeParse(parsed);
if (providersValidation.success) {
providersConfig = providersValidation.data;
return;
}
const channelsValidation = channelsSchema.safeParse(parsed);
if (channelsValidation.success) {
channelsConfig = channelsValidation.data;
return;
}
});
return { providers: providersConfig, channels: channelsConfig };
};

View file

@ -5,12 +5,19 @@ import {
FetchAppDetailsQuery, FetchAppDetailsQuery,
UpdateAppMetadataDocument, UpdateAppMetadataDocument,
} from "../../../generated/graphql"; } from "../../../generated/graphql";
import { logger as pinoLogger } from "../../lib/logger";
export async function fetchAllMetadata(client: Client): Promise<MetadataEntry[]> { export async function fetchAllMetadata(client: Client): Promise<MetadataEntry[]> {
const logger = pinoLogger.child({ service: "fetchAllMetadata" });
logger.debug("Fetching metadata from Saleor");
const { error, data } = await client const { error, data } = await client
.query<FetchAppDetailsQuery>(FetchAppDetailsDocument, {}) .query<FetchAppDetailsQuery>(FetchAppDetailsDocument, {})
.toPromise(); .toPromise();
// * `metadata` name is required for secrets censorship
logger.debug({ error, metadata: data }, "Metadata fetched");
if (error) { if (error) {
return []; return [];
} }
@ -19,11 +26,15 @@ export async function fetchAllMetadata(client: Client): Promise<MetadataEntry[]>
} }
export async function mutateMetadata(client: Client, metadata: MetadataEntry[]) { export async function mutateMetadata(client: Client, metadata: MetadataEntry[]) {
const logger = pinoLogger.child({ service: "mutateMetadata" });
logger.debug({ metadata }, "Mutating metadata");
// to update the metadata, ID is required // to update the metadata, ID is required
const { error: idQueryError, data: idQueryData } = await client const { error: idQueryError, data: idQueryData } = await client
.query(FetchAppDetailsDocument, {}) .query(FetchAppDetailsDocument, {})
.toPromise(); .toPromise();
logger.debug({ error: idQueryError, data: idQueryData }, "Metadata mutated");
if (idQueryError) { if (idQueryError) {
throw new Error( throw new Error(
"Could not fetch the app id. Please check if auth data for the client are valid." "Could not fetch the app id. Please check if auth data for the client are valid."

View file

@ -1,7 +1,8 @@
import Avatax from "avatax"; import Avatax from "avatax";
import { CreateTransactionModel } from "avatax/lib/models/CreateTransactionModel"; import { CreateTransactionModel } from "avatax/lib/models/CreateTransactionModel";
import pino from "pino";
import packageJson from "../../../package.json"; import packageJson from "../../../package.json";
import { logger } from "../../lib/logger"; import { createLogger } from "../../lib/logger";
import { AvataxConfig } from "./avatax-config"; import { AvataxConfig } from "./avatax-config";
type AvataxSettings = { type AvataxSettings = {
@ -36,9 +37,11 @@ const createAvataxSettings = (config: AvataxConfig): AvataxSettings => {
export class AvataxClient { export class AvataxClient {
private client: Avatax; private client: Avatax;
private logger: pino.Logger;
constructor(config: AvataxConfig) { constructor(config: AvataxConfig) {
logger.debug("AvataxClient constructor"); this.logger = createLogger({ service: "AvataxClient" });
this.logger.trace("AvataxClient constructor");
const { username, password } = config; const { username, password } = config;
const credentials = { const credentials = {
username, username,
@ -46,15 +49,18 @@ export class AvataxClient {
}; };
const settings = createAvataxSettings(config); const settings = createAvataxSettings(config);
const avataxClient = new Avatax(settings).withSecurity(credentials); const avataxClient = new Avatax(settings).withSecurity(credentials);
logger.info({ client: avataxClient }, "External Avatax client created"); this.logger.trace({ client: avataxClient }, "External Avatax client created");
this.client = avataxClient; this.client = avataxClient;
} }
async fetchTaxesForOrder(model: CreateTransactionModel) { async fetchTaxesForOrder(model: CreateTransactionModel) {
this.logger.debug({ model }, "fetchTaxesForOrder called with:");
return this.client.createTransaction({ model }); return this.client.createTransaction({ model });
} }
async ping() { async ping() {
this.logger.debug("ping called");
try { try {
const result = await this.client.ping(); const result = await this.client.ping();

View file

@ -37,14 +37,15 @@ export const avataxConfigurationRouter = router({
procedure: "avataxConfigurationRouter.get", procedure: "avataxConfigurationRouter.get",
}); });
logger.debug("avataxConfigurationRouter.get called"); logger.debug({ input }, "avataxConfigurationRouter.get called with:");
const { apiClient, saleorApiUrl } = ctx; const { apiClient, saleorApiUrl } = ctx;
const avataxConfigurationService = new AvataxConfigurationService(apiClient, saleorApiUrl); const avataxConfigurationService = new AvataxConfigurationService(apiClient, saleorApiUrl);
const result = await avataxConfigurationService.get(input.id); const result = await avataxConfigurationService.get(input.id);
logger.debug({ result }, "avataxConfigurationRouter.get finished"); // * `providerInstance` name is required for secrets censorship
logger.debug({ providerInstance: result }, "avataxConfigurationRouter.get finished");
return { ...result, config: obfuscateAvataxConfig(result.config) }; return { ...result, config: obfuscateAvataxConfig(result.config) };
}), }),
@ -54,7 +55,7 @@ export const avataxConfigurationRouter = router({
procedure: "avataxConfigurationRouter.post", procedure: "avataxConfigurationRouter.post",
}); });
logger.debug("avataxConfigurationRouter.post called"); logger.debug({ input }, "avataxConfigurationRouter.post called with:");
const { apiClient, saleorApiUrl } = ctx; const { apiClient, saleorApiUrl } = ctx;
const avataxConfigurationService = new AvataxConfigurationService(apiClient, saleorApiUrl); const avataxConfigurationService = new AvataxConfigurationService(apiClient, saleorApiUrl);
@ -71,7 +72,7 @@ export const avataxConfigurationRouter = router({
procedure: "avataxConfigurationRouter.delete", procedure: "avataxConfigurationRouter.delete",
}); });
logger.debug("avataxConfigurationRouter.delete called"); logger.debug({ input }, "avataxConfigurationRouter.delete called with:");
const { apiClient, saleorApiUrl } = ctx; const { apiClient, saleorApiUrl } = ctx;
const avataxConfigurationService = new AvataxConfigurationService(apiClient, saleorApiUrl); const avataxConfigurationService = new AvataxConfigurationService(apiClient, saleorApiUrl);
@ -88,7 +89,7 @@ export const avataxConfigurationRouter = router({
procedure: "avataxConfigurationRouter.patch", procedure: "avataxConfigurationRouter.patch",
}); });
logger.debug("avataxConfigurationRouter.patch called"); logger.debug({ input }, "avataxConfigurationRouter.patch called with:");
const { apiClient, saleorApiUrl } = ctx; const { apiClient, saleorApiUrl } = ctx;
const avataxConfigurationService = new AvataxConfigurationService(apiClient, saleorApiUrl); const avataxConfigurationService = new AvataxConfigurationService(apiClient, saleorApiUrl);

View file

@ -45,7 +45,7 @@ export class AvataxConfigurationService {
async get(id: string): Promise<AvataxInstanceConfig> { async get(id: string): Promise<AvataxInstanceConfig> {
this.logger.debug(`.get called with id: ${id}`); this.logger.debug(`.get called with id: ${id}`);
const { data } = await this.crudSettingsConfigurator.read(id); const { data } = await this.crudSettingsConfigurator.read(id);
this.logger.debug({ setting: data }, `Fetched setting from crudSettingsConfigurator`); this.logger.debug(`Fetched setting from crudSettingsConfigurator`);
const validation = getSchema.safeParse(data); const validation = getSchema.safeParse(data);

View file

@ -1,5 +1,6 @@
import pino from "pino";
import { TaxBaseFragment } from "../../../generated/graphql"; import { TaxBaseFragment } from "../../../generated/graphql";
import { logger } from "../../lib/logger"; import { createLogger } from "../../lib/logger";
import { ChannelConfig } from "../channels-configuration/channels-config"; import { ChannelConfig } from "../channels-configuration/channels-config";
import { TaxProvider } from "../taxes/tax-provider"; import { TaxProvider } from "../taxes/tax-provider";
import { avataxCalculate } from "./avatax-calculate"; import { avataxCalculate } from "./avatax-calculate";
@ -10,21 +11,24 @@ export class AvataxProvider implements TaxProvider {
readonly name = "avatax"; readonly name = "avatax";
config = defaultAvataxConfig; config = defaultAvataxConfig;
client: AvataxClient; client: AvataxClient;
private logger: pino.Logger;
constructor(config: AvataxConfig) { constructor(config: AvataxConfig) {
this.logger = createLogger({
service: "AvataxProvider",
});
const avataxClient = new AvataxClient(config); const avataxClient = new AvataxClient(config);
logger.info({ client: avataxClient }, "Internal Avatax client created"); this.logger.trace({ client: avataxClient }, "Internal Avatax client created");
this.config = config; this.config = config;
this.client = avataxClient; this.client = avataxClient;
} }
async calculate(payload: TaxBaseFragment, channel: ChannelConfig) { async calculate(payload: TaxBaseFragment, channel: ChannelConfig) {
logger.info("Avatax calculate"); this.logger.debug({ payload, channel }, "Avatax calculate called with:");
const model = avataxCalculate.preparePayload(payload, channel, this.config); const model = avataxCalculate.preparePayload(payload, channel, this.config);
logger.info(model, "Payload used for Avatax fetchTaxesForOrder");
const result = await this.client.fetchTaxesForOrder(model); const result = await this.client.fetchTaxesForOrder(model);
logger.info({ createOrderTransaction: result }, "Avatax createOrderTransaction response"); this.logger.debug({ createOrderTransaction: result }, "Avatax createOrderTransaction response");
return avataxCalculate.prepareResponse(result); return avataxCalculate.prepareResponse(result);
} }
} }

View file

@ -9,7 +9,7 @@ export const channelSchema = z.object({
}); });
export type ChannelConfig = z.infer<typeof channelSchema>; export type ChannelConfig = z.infer<typeof channelSchema>;
const channelsSchema = z.record(channelSchema); export const channelsSchema = z.record(channelSchema);
export type ChannelsConfig = z.infer<typeof channelsSchema>; export type ChannelsConfig = z.infer<typeof channelsSchema>;
export const defaultChannelConfig: ChannelConfig = { export const defaultChannelConfig: ChannelConfig = {

View file

@ -7,9 +7,13 @@ import { setAndReplaceChannelsInputSchema } from "./channels-config-input-schema
import { TaxChannelsConfigurator } from "./channels-configurator"; import { TaxChannelsConfigurator } from "./channels-configurator";
import { GetChannelsConfigurationService } from "./get-channels-configuration.service"; import { GetChannelsConfigurationService } from "./get-channels-configuration.service";
// todo: refactor with crud-settings
export const channelsConfigurationRouter = router({ export const channelsConfigurationRouter = router({
fetch: protectedClientProcedure.query(async ({ ctx, input }) => { fetch: protectedClientProcedure.query(async ({ ctx, input }) => {
const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl }); const logger = pinoLogger.child({
saleorApiUrl: ctx.saleorApiUrl,
procedure: "channelsConfigurationRouter.fetch",
});
logger.debug("channelsConfigurationRouter.fetch called"); logger.debug("channelsConfigurationRouter.fetch called");
@ -21,14 +25,17 @@ export const channelsConfigurationRouter = router({
upsert: protectedClientProcedure upsert: protectedClientProcedure
.input(setAndReplaceChannelsInputSchema) .input(setAndReplaceChannelsInputSchema)
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl }); const logger = pinoLogger.child({
logger.info(input, "channelsConfigurationRouter.upsert called with input"); saleorApiUrl: ctx.saleorApiUrl,
procedure: "channelsConfigurationRouter.upsert",
});
logger.debug(input, "channelsConfigurationRouter.upsert called with input");
const config = await new GetChannelsConfigurationService({ const config = await new GetChannelsConfigurationService({
apiClient: ctx.apiClient, apiClient: ctx.apiClient,
saleorApiUrl: ctx.saleorApiUrl, saleorApiUrl: ctx.saleorApiUrl,
}).getConfiguration(); }).getConfiguration();
logger.info(config, "Fetched current channels config to update it"); logger.debug(config, "Fetched current channels config to update it");
const taxChannelsConfigurator = new TaxChannelsConfigurator( const taxChannelsConfigurator = new TaxChannelsConfigurator(
createSettingsManager(ctx.apiClient), createSettingsManager(ctx.apiClient),
@ -43,7 +50,7 @@ export const channelsConfigurationRouter = router({
}, },
}; };
logger.info(channelsConfig, "Merged configs. Will set it now"); logger.debug(channelsConfig, "Merged configs. Will set it now");
await taxChannelsConfigurator.setConfig(channelsConfig); await taxChannelsConfigurator.setConfig(channelsConfig);

View file

@ -1,8 +1,6 @@
import { Client } from "urql"; import { Client } from "urql";
import { logger as pinoLogger } from "../../lib/logger"; import { logger as pinoLogger } from "../../lib/logger";
import { createSettingsManager } from "../app-configuration/metadata-manager"; import { createSettingsManager } from "../app-configuration/metadata-manager";
import { ChannelsFetcher } from "../channels/channels-fetcher";
import { createDefaultChannelsConfig } from "./channels-config";
import { TaxChannelsConfigurator } from "./channels-configurator"; import { TaxChannelsConfigurator } from "./channels-configurator";
export class GetChannelsConfigurationService { export class GetChannelsConfigurationService {
@ -26,17 +24,11 @@ export class GetChannelsConfigurationService {
saleorApiUrl saleorApiUrl
); );
const channelsFetcher = new ChannelsFetcher(apiClient);
const channels = await channelsFetcher.fetchChannels();
logger.info({ channels }, "Fetched Saleor channels that use TAX_APP for tax calculation");
const defaultConfig = createDefaultChannelsConfig(channels ?? []);
logger.info({ defaultConfig }, "Generated config from Saleor channels");
// todo: validate config // todo: validate config
const appChannelsConfig = (await taxConfigurator.getConfig()) ?? null; const appChannelsConfig = (await taxConfigurator.getConfig()) ?? null;
logger.debug(appChannelsConfig, "Retrieved channels config from Metadata"); logger.debug(appChannelsConfig, "Retrieved channels config from Metadata");
return { ...defaultConfig, ...appChannelsConfig }; return { ...appChannelsConfig };
} }
} }

View file

@ -20,11 +20,11 @@ export class CrudSettingsConfigurator {
} }
async readAll() { async readAll() {
this.logger.debug(".readAll called"); this.logger.trace(".readAll called");
const result = await this.metadataManager.get(this.metadataKey, this.saleorApiUrl); const result = await this.metadataManager.get(this.metadataKey, this.saleorApiUrl);
if (!result) { if (!result) {
this.logger.debug("No metadata found"); this.logger.trace("No metadata found");
return { data: [] }; return { data: [] };
} }
@ -42,7 +42,7 @@ export class CrudSettingsConfigurator {
} }
async read(id: string) { async read(id: string) {
this.logger.debug(".read called"); this.logger.trace(".read called");
const result = await this.readAll(); const result = await this.readAll();
const { data: settings } = result; const { data: settings } = result;
@ -58,7 +58,7 @@ export class CrudSettingsConfigurator {
} }
async create(data: any) { async create(data: any) {
this.logger.debug(data, ".create called with:"); this.logger.trace(data, ".create called with:");
const settings = await this.readAll(); const settings = await this.readAll();
const prevData = settings.data; const prevData = settings.data;
@ -77,7 +77,7 @@ export class CrudSettingsConfigurator {
} }
async delete(id: string) { async delete(id: string) {
this.logger.debug(`.delete called with: ${id}`); this.logger.trace(`.delete called with: ${id}`);
const settings = await this.readAll(); const settings = await this.readAll();
const prevData = settings.data; const prevData = settings.data;
@ -91,7 +91,7 @@ export class CrudSettingsConfigurator {
} }
async update(id: string, data: any) { async update(id: string, data: any) {
this.logger.debug(data, `.update called with: ${id}`); this.logger.trace(data, `.update called with: ${id}`);
const { data: settings } = await this.readAll(); const { data: settings } = await this.readAll();
const nextData = settings.map((item) => { const nextData = settings.map((item) => {
if (item.id === id) { if (item.id === id) {

View file

@ -5,7 +5,10 @@ import { PublicTaxProvidersConfigurationService } from "./public-providers-confi
export const providersConfigurationRouter = router({ export const providersConfigurationRouter = router({
getAll: protectedClientProcedure.query(async ({ ctx }) => { getAll: protectedClientProcedure.query(async ({ ctx }) => {
const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl }); const logger = pinoLogger.child({
saleorApiUrl: ctx.saleorApiUrl,
procedure: "providersConfigurationRouter.getAll",
});
logger.debug("providersConfigurationRouter.fetch called"); logger.debug("providersConfigurationRouter.fetch called");

View file

@ -6,15 +6,19 @@ import { AvataxProvider } from "../avatax/avatax-provider";
import { TaxJarProvider } from "../taxjar/taxjar-provider"; import { TaxJarProvider } from "../taxjar/taxjar-provider";
import { TaxProvider } from "./tax-provider"; import { TaxProvider } from "./tax-provider";
import { TaxProviderError } from "./tax-provider-error"; import { TaxProviderError } from "./tax-provider-error";
import pino from "pino";
export class ActiveTaxProvider { export class ActiveTaxProvider {
private client: TaxProvider; private client: TaxProvider;
private logger: pino.Logger;
constructor(providerInstance: ProviderConfig) { constructor(providerInstance: ProviderConfig) {
const logger = createLogger({}); this.logger = createLogger({
service: "ActiveTaxProvider",
});
const taxProviderName = providerInstance.provider; const taxProviderName = providerInstance.provider;
logger.info({ taxProviderName }, "Constructing tax provider: "); this.logger.trace({ taxProviderName }, "Constructing tax provider: ");
switch (taxProviderName) { switch (taxProviderName) {
case "taxjar": case "taxjar":
@ -34,6 +38,8 @@ export class ActiveTaxProvider {
} }
async calculate(payload: TaxBaseFragment, channel: ChannelConfig) { async calculate(payload: TaxBaseFragment, channel: ChannelConfig) {
this.logger.debug({ payload, channel }, ".calculate called");
return this.client.calculate(payload, channel); return this.client.calculate(payload, channel);
} }
} }

View file

@ -1,9 +1,7 @@
import { TaxBaseFragment } from "../../../generated/graphql"; import { TaxBaseFragment } from "../../../generated/graphql";
import { ResponseTaxPayload } from "./types";
import { ChannelConfig } from "../channels-configuration/channels-config"; import { ChannelConfig } from "../channels-configuration/channels-config";
import { TaxProviderName } from "./providers/config"; import { TaxProviderName } from "./providers/config";
import { ResponseTaxPayload } from "./types";
type ExternalValidationResult = { ok: boolean; error?: string };
export interface TaxProvider { export interface TaxProvider {
name: TaxProviderName; name: TaxProviderName;

View file

@ -1,6 +1,7 @@
import pino from "pino";
import TaxJar from "taxjar"; import TaxJar from "taxjar";
import { Config, TaxForOrderRes, TaxParams } from "taxjar/dist/util/types"; import { Config, TaxForOrderRes, TaxParams } from "taxjar/dist/util/types";
import { logger } from "../../lib/logger"; import { createLogger } from "../../lib/logger";
import { TaxJarConfig } from "./taxjar-config"; import { TaxJarConfig } from "./taxjar-config";
const createTaxJarSettings = (config: TaxJarConfig): Config => { const createTaxJarSettings = (config: TaxJarConfig): Config => {
@ -14,21 +15,25 @@ const createTaxJarSettings = (config: TaxJarConfig): Config => {
export class TaxJarClient { export class TaxJarClient {
private client: TaxJar; private client: TaxJar;
private logger: pino.Logger;
constructor(providerConfig: TaxJarConfig) { constructor(providerConfig: TaxJarConfig) {
logger.debug("TaxJarClient constructor"); this.logger = createLogger({ service: "TaxJarClient" });
this.logger.trace("TaxJarClient constructor");
const settings = createTaxJarSettings(providerConfig); const settings = createTaxJarSettings(providerConfig);
const taxJarClient = new TaxJar(settings); const taxJarClient = new TaxJar(settings);
logger.info({ client: taxJarClient }, "External TaxJar client created"); this.logger.trace({ client: taxJarClient }, "External TaxJar client created");
this.client = taxJarClient; this.client = taxJarClient;
} }
async fetchTaxesForOrder(params: TaxParams) { async fetchTaxesForOrder(params: TaxParams) {
this.logger.debug({ params }, "fetchTaxesForOrder called with:");
const response: TaxForOrderRes = await this.client.taxForOrder(params); const response: TaxForOrderRes = await this.client.taxForOrder(params);
return response; return response;
} }
async ping() { async ping() {
this.logger.debug("ping called");
try { try {
await this.client.categories(); await this.client.categories();
return { authenticated: true }; return { authenticated: true };

View file

@ -43,8 +43,8 @@ export const taxjarConfigurationRouter = router({
const result = await taxjarConfigurationService.get(input.id); const result = await taxjarConfigurationService.get(input.id);
logger.debug({ result }, "taxjarConfigurationRouter.get finished"); // * `providerInstance` name is required for secrets censorship
logger.debug({ providerInstance: result }, "taxjarConfigurationRouter.get finished");
return { ...result, config: obfuscateTaxJarConfig(result.config) }; return { ...result, config: obfuscateTaxJarConfig(result.config) };
}), }),
post: protectedClientProcedure.input(postInputSchema).mutation(async ({ ctx, input }) => { post: protectedClientProcedure.input(postInputSchema).mutation(async ({ ctx, input }) => {

View file

@ -29,7 +29,7 @@ export class TaxJarConfigurationService {
async getAll(): Promise<TaxJarInstanceConfig[]> { async getAll(): Promise<TaxJarInstanceConfig[]> {
this.logger.debug(".getAll called"); this.logger.debug(".getAll called");
const { data } = await this.crudSettingsConfigurator.readAll(); const { data } = await this.crudSettingsConfigurator.readAll();
this.logger.debug({ settings: data }, `Fetched settings from crudSettingsConfigurator`); this.logger.debug(`Fetched settings from crudSettingsConfigurator`);
const validation = providersSchema.safeParse(data); const validation = providersSchema.safeParse(data);
if (!validation.success) { if (!validation.success) {
@ -47,7 +47,7 @@ export class TaxJarConfigurationService {
async get(id: string): Promise<TaxJarInstanceConfig> { async get(id: string): Promise<TaxJarInstanceConfig> {
this.logger.debug(`.get called with id: ${id}`); this.logger.debug(`.get called with id: ${id}`);
const { data } = await this.crudSettingsConfigurator.read(id); const { data } = await this.crudSettingsConfigurator.read(id);
this.logger.debug({ setting: data }, `Fetched setting from crudSettingsConfigurator`); this.logger.debug(`Fetched setting from crudSettingsConfigurator`);
const validation = getSchema.safeParse(data); const validation = getSchema.safeParse(data);

View file

@ -1,35 +1,36 @@
import pino from "pino";
import { TaxBaseFragment } from "../../../generated/graphql"; import { TaxBaseFragment } from "../../../generated/graphql";
import { logger } from "../../lib/logger"; import { createLogger } from "../../lib/logger";
import { ChannelConfig, defaultChannelConfig } from "../channels-configuration/channels-config"; import { ChannelConfig } from "../channels-configuration/channels-config";
import { TaxProvider } from "../taxes/tax-provider"; import { TaxProvider } from "../taxes/tax-provider";
import { taxJarCalculate } from "./taxjar-calculate"; import { taxJarCalculate } from "./taxjar-calculate";
import { TaxJarClient } from "./taxjar-client"; import { TaxJarClient } from "./taxjar-client";
import { defaultTaxJarConfig, TaxJarConfig } from "./taxjar-config"; import { TaxJarConfig } from "./taxjar-config";
export class TaxJarProvider implements TaxProvider { export class TaxJarProvider implements TaxProvider {
readonly name = "taxjar";
config = defaultTaxJarConfig;
channel = defaultChannelConfig;
client: TaxJarClient; client: TaxJarClient;
readonly name = "taxjar";
private logger: pino.Logger;
constructor(config: TaxJarConfig) { constructor(config: TaxJarConfig) {
const avataxClient = new TaxJarClient(config); const avataxClient = new TaxJarClient(config);
this.config = config;
this.client = avataxClient; this.client = avataxClient;
this.logger = createLogger({
service: "TaxJarProvider",
});
} }
async calculate(payload: TaxBaseFragment, channel: ChannelConfig) { async calculate(payload: TaxBaseFragment, channel: ChannelConfig) {
logger.info("TaxJar calculate"); this.logger.debug({ payload, channel }, "TaxJar calculate called with:");
const linesWithDiscount = taxJarCalculate.prepareLinesWithDiscountPayload( const linesWithDiscount = taxJarCalculate.prepareLinesWithDiscountPayload(
payload.lines, payload.lines,
payload.discounts payload.discounts
); );
const linesWithChargeTaxes = linesWithDiscount.filter((line) => line.chargeTaxes === true); const linesWithChargeTaxes = linesWithDiscount.filter((line) => line.chargeTaxes === true);
const taxParams = taxJarCalculate.preparePayload(payload, channel, linesWithDiscount); const taxParams = taxJarCalculate.preparePayload(payload, channel, linesWithDiscount);
logger.info(taxParams, "Payload used for TaxJar fetchTaxesForOrder");
const fetchedTaxes = await this.client.fetchTaxesForOrder(taxParams); const fetchedTaxes = await this.client.fetchTaxesForOrder(taxParams);
logger.info({ fetchedTaxes }, "TaxJar createOrderTransaction response"); this.logger.debug({ fetchedTaxes }, "TaxJar createOrderTransaction response");
return taxJarCalculate.prepareResponse( return taxJarCalculate.prepareResponse(
payload, payload,

View file

@ -1,11 +1,9 @@
import { SaleorSyncWebhook } from "@saleor/app-sdk/handlers/next"; import { SaleorSyncWebhook } from "@saleor/app-sdk/handlers/next";
import { UntypedCalculateTaxesDocument } from "../../../../generated/graphql"; import { UntypedCalculateTaxesDocument } from "../../../../generated/graphql";
import { saleorApp } from "../../../../saleor-app"; import { saleorApp } from "../../../../saleor-app";
import { createClient } from "../../../lib/graphql";
import { createLogger } from "../../../lib/logger"; import { createLogger } from "../../../lib/logger";
import { calculateTaxesPayloadSchema, ExpectedWebhookPayload } from "../../../lib/saleor/schema"; import { calculateTaxesPayloadSchema, ExpectedWebhookPayload } from "../../../lib/saleor/schema";
import { GetChannelsConfigurationService } from "../../../modules/channels-configuration/get-channels-configuration.service"; import { getAppConfig } from "../../../modules/app-configuration/get-app-config";
import { PrivateTaxProvidersConfigurationService } from "../../../modules/providers-configuration/private-providers-configuration-service";
import { ActiveTaxProvider } from "../../../modules/taxes/active-tax-provider"; import { ActiveTaxProvider } from "../../../modules/taxes/active-tax-provider";
@ -25,7 +23,7 @@ export const checkoutCalculateTaxesSyncWebhook = new SaleorSyncWebhook<ExpectedW
export default checkoutCalculateTaxesSyncWebhook.createHandler(async (req, res, ctx) => { export default checkoutCalculateTaxesSyncWebhook.createHandler(async (req, res, ctx) => {
const logger = createLogger({ event: ctx.event }); const logger = createLogger({ event: ctx.event });
const { authData, payload } = ctx; const { payload } = ctx;
logger.info({ payload }, "Handler called with payload"); logger.info({ payload }, "Handler called with payload");
const validation = calculateTaxesPayloadSchema.safeParse(payload); const validation = calculateTaxesPayloadSchema.safeParse(payload);
@ -37,27 +35,14 @@ export default checkoutCalculateTaxesSyncWebhook.createHandler(async (req, res,
} }
const { data } = validation; const { data } = validation;
logger.info({ data }, "Payload is valid."); logger.info("Payload validated succesfully");
const { providers, channels } = getAppConfig(data);
logger.debug("Successfully parsed providers & channels from payload");
try { try {
const client = createClient(authData.saleorApiUrl, async () => const channelSlug = data.taxBase.channel.slug;
Promise.resolve({ token: authData.token }) const channelConfig = channels[channelSlug];
);
const providersConfig = await new PrivateTaxProvidersConfigurationService(
client,
authData.saleorApiUrl
).getAll();
const channelsConfig = await new GetChannelsConfigurationService({
saleorApiUrl: authData.saleorApiUrl,
apiClient: client,
}).getConfiguration();
logger.info({ providersConfig }, "Providers configuration returned");
const channelSlug = payload.taxBase.channel.slug;
const channelConfig = channelsConfig[channelSlug];
if (!channelConfig) { if (!channelConfig) {
logger.error(`Channel config not found for channel ${channelSlug}`); logger.error(`Channel config not found for channel ${channelSlug}`);
@ -65,7 +50,7 @@ export default checkoutCalculateTaxesSyncWebhook.createHandler(async (req, res,
return res.send({}); return res.send({});
} }
const providerInstance = providersConfig.find( const providerInstance = providers.find(
(instance) => instance.id === channelConfig.providerInstanceId (instance) => instance.id === channelConfig.providerInstanceId
); );

View file

@ -1,11 +1,9 @@
import { SaleorSyncWebhook } from "@saleor/app-sdk/handlers/next"; import { SaleorSyncWebhook } from "@saleor/app-sdk/handlers/next";
import { UntypedCalculateTaxesDocument } from "../../../../generated/graphql"; import { UntypedCalculateTaxesDocument } from "../../../../generated/graphql";
import { saleorApp } from "../../../../saleor-app"; import { saleorApp } from "../../../../saleor-app";
import { createClient } from "../../../lib/graphql";
import { createLogger } from "../../../lib/logger"; import { createLogger } from "../../../lib/logger";
import { calculateTaxesPayloadSchema, ExpectedWebhookPayload } from "../../../lib/saleor/schema"; import { calculateTaxesPayloadSchema, ExpectedWebhookPayload } from "../../../lib/saleor/schema";
import { GetChannelsConfigurationService } from "../../../modules/channels-configuration/get-channels-configuration.service"; import { getAppConfig } from "../../../modules/app-configuration/get-app-config";
import { PrivateTaxProvidersConfigurationService } from "../../../modules/providers-configuration/private-providers-configuration-service";
import { ActiveTaxProvider } from "../../../modules/taxes/active-tax-provider"; import { ActiveTaxProvider } from "../../../modules/taxes/active-tax-provider";
export const config = { export const config = {
@ -24,7 +22,7 @@ export const orderCalculateTaxesSyncWebhook = new SaleorSyncWebhook<ExpectedWebh
export default orderCalculateTaxesSyncWebhook.createHandler(async (req, res, ctx) => { export default orderCalculateTaxesSyncWebhook.createHandler(async (req, res, ctx) => {
const logger = createLogger({ event: ctx.event }); const logger = createLogger({ event: ctx.event });
const { authData, payload } = ctx; const { payload } = ctx;
logger.info({ payload }, "Handler called with payload"); logger.info({ payload }, "Handler called with payload");
const validation = calculateTaxesPayloadSchema.safeParse(payload); const validation = calculateTaxesPayloadSchema.safeParse(payload);
@ -36,26 +34,14 @@ export default orderCalculateTaxesSyncWebhook.createHandler(async (req, res, ctx
} }
const { data } = validation; const { data } = validation;
logger.info({ data }, "Payload is valid."); logger.info("Payload validated succesfully");
const { providers, channels } = getAppConfig(data);
logger.debug("Parsed providers & channels from payload");
try { try {
const client = createClient(authData.saleorApiUrl, async () => const channelSlug = data.taxBase.channel.slug;
Promise.resolve({ token: authData.token }) const channelConfig = channels[channelSlug];
);
const providersConfig = await new PrivateTaxProvidersConfigurationService(
client,
authData.saleorApiUrl
).getAll();
const channelsConfig = await new GetChannelsConfigurationService({
saleorApiUrl: authData.saleorApiUrl,
apiClient: client,
}).getConfiguration();
logger.info({ providersConfig }, "Providers configuration returned");
const channelSlug = payload.taxBase.channel.slug;
const channelConfig = channelsConfig[channelSlug];
if (!channelConfig) { if (!channelConfig) {
logger.error(`Channel config not found for channel ${channelSlug}`); logger.error(`Channel config not found for channel ${channelSlug}`);
@ -63,7 +49,7 @@ export default orderCalculateTaxesSyncWebhook.createHandler(async (req, res, ctx
return res.send({}); return res.send({});
} }
const providerInstance = providersConfig.find( const providerInstance = providers.find(
(instance) => instance.id === channelConfig.providerInstanceId (instance) => instance.id === channelConfig.providerInstanceId
); );