diff --git a/apps/taxes/src/error.ts b/apps/taxes/src/error.ts new file mode 100644 index 0000000..f7e2747 --- /dev/null +++ b/apps/taxes/src/error.ts @@ -0,0 +1,6 @@ +import ModernError from "modern-errors"; +import modernErrorsSerialize from "modern-errors-serialize"; + +export const BaseError = ModernError.subclass("BaseError", { + plugins: [modernErrorsSerialize], +}); diff --git a/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-payload-transformer.ts b/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-payload-transformer.ts index e3cf91e..fc3b02c 100644 --- a/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-payload-transformer.ts +++ b/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-payload-transformer.ts @@ -1,14 +1,14 @@ import { DocumentType } from "avatax/lib/enums/DocumentType"; -import { TaxBaseFragment } from "../../../../generated/graphql"; +import { CalculateTaxesPayload } from "../../../pages/api/webhooks/checkout-calculate-taxes"; import { discountUtils } from "../../taxes/discount-utils"; +import { TaxUnexpectedError } from "../../taxes/tax-error"; +import { taxProviderUtils } from "../../taxes/tax-provider-utils"; import { avataxAddressFactory } from "../address-factory"; import { AvataxClient, CreateTransactionArgs } from "../avatax-client"; import { AvataxConfig, defaultAvataxConfig } from "../avatax-connection-schema"; +import { AvataxEntityTypeMatcher } from "../avatax-entity-type-matcher"; import { AvataxTaxCodeMatches } from "../tax-code/avatax-tax-code-match-repository"; import { AvataxCalculateTaxesPayloadLinesTransformer } from "./avatax-calculate-taxes-payload-lines-transformer"; -import { AvataxEntityTypeMatcher } from "../avatax-entity-type-matcher"; -import { taxProviderUtils } from "../../taxes/tax-provider-utils"; -import { CalculateTaxesPayload } from "../../../pages/api/webhooks/checkout-calculate-taxes"; export class AvataxCalculateTaxesPayloadTransformer { private matchDocumentType(config: AvataxConfig): DocumentType { @@ -31,7 +31,7 @@ export class AvataxCalculateTaxesPayloadTransformer { return taxProviderUtils.resolveStringOrThrow(payload.taxBase.sourceObject.userEmail); } - throw new Error("Cannot resolve customer code"); + throw new TaxUnexpectedError("Cannot resolve customer code"); } async transform( diff --git a/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-response-lines-transformer.ts b/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-response-lines-transformer.ts index ef88670..b0700ea 100644 --- a/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-response-lines-transformer.ts +++ b/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-response-lines-transformer.ts @@ -14,11 +14,11 @@ export class AvataxCalculateTaxesResponseLinesTransformer { return { total_gross_amount: taxProviderUtils.resolveOptionalOrThrow( line.lineAmount, - new Error("line.lineAmount is undefined") + "line.lineAmount is undefined", ), total_net_amount: taxProviderUtils.resolveOptionalOrThrow( line.lineAmount, - new Error("line.lineAmount is undefined") + "line.lineAmount is undefined", ), tax_rate: 0, }; @@ -26,21 +26,21 @@ export class AvataxCalculateTaxesResponseLinesTransformer { const lineTaxCalculated = taxProviderUtils.resolveOptionalOrThrow( line.taxCalculated, - new Error("line.taxCalculated is undefined") + "line.taxCalculated is undefined", ); const lineTotalNetAmount = taxProviderUtils.resolveOptionalOrThrow( line.taxableAmount, - new Error("line.taxableAmount is undefined") + "line.taxableAmount is undefined", ); const lineTotalGrossAmount = numbers.roundFloatToTwoDecimals( - lineTotalNetAmount + lineTaxCalculated + lineTotalNetAmount + lineTaxCalculated, ); return { total_gross_amount: lineTotalGrossAmount, total_net_amount: lineTotalNetAmount, /* - * avatax doesnt return combined tax rate + * avatax doesn't return combined tax rate * // todo: calculate percentage tax rate */ tax_rate: 0, }; diff --git a/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-response-shipping-transformer.ts b/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-response-shipping-transformer.ts index 4cb259a..d47adfe 100644 --- a/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-response-shipping-transformer.ts +++ b/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-response-shipping-transformer.ts @@ -6,7 +6,7 @@ import { SHIPPING_ITEM_CODE } from "./avatax-calculate-taxes-adapter"; export class AvataxCalculateTaxesResponseShippingTransformer { transform( - transaction: TransactionModel + transaction: TransactionModel, ): Pick< CalculateTaxesResponse, "shipping_price_gross_amount" | "shipping_price_net_amount" | "shipping_tax_rate" @@ -25,14 +25,14 @@ export class AvataxCalculateTaxesResponseShippingTransformer { return { shipping_price_gross_amount: taxProviderUtils.resolveOptionalOrThrow( shippingLine.lineAmount, - new Error("shippingLine.lineAmount is undefined") + "shippingLine.lineAmount is undefined", ), shipping_price_net_amount: taxProviderUtils.resolveOptionalOrThrow( shippingLine.lineAmount, - new Error("shippingLine.lineAmount is undefined") + "shippingLine.lineAmount is undefined", ), /* - * avatax doesnt return combined tax rate + * avatax doesn't return combined tax rate * // todo: calculate percentage tax rate */ shipping_tax_rate: 0, @@ -41,14 +41,14 @@ export class AvataxCalculateTaxesResponseShippingTransformer { const shippingTaxCalculated = taxProviderUtils.resolveOptionalOrThrow( shippingLine.taxCalculated, - new Error("shippingLine.taxCalculated is undefined") + "shippingLine.taxCalculated is undefined", ); const shippingTaxableAmount = taxProviderUtils.resolveOptionalOrThrow( shippingLine.taxableAmount, - new Error("shippingLine.taxableAmount is undefined") + "shippingLine.taxableAmount is undefined", ); const shippingGrossAmount = numbers.roundFloatToTwoDecimals( - shippingTaxableAmount + shippingTaxCalculated + shippingTaxableAmount + shippingTaxCalculated, ); return { diff --git a/apps/taxes/src/modules/avatax/order-confirmed/avatax-order-confirmed-response-transformer.ts b/apps/taxes/src/modules/avatax/order-confirmed/avatax-order-confirmed-response-transformer.ts index a7d6ae0..3de7c30 100644 --- a/apps/taxes/src/modules/avatax/order-confirmed/avatax-order-confirmed-response-transformer.ts +++ b/apps/taxes/src/modules/avatax/order-confirmed/avatax-order-confirmed-response-transformer.ts @@ -7,9 +7,7 @@ export class AvataxOrderConfirmedResponseTransformer { return { id: taxProviderUtils.resolveOptionalOrThrow( response.code, - 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.", ), }; } diff --git a/apps/taxes/src/modules/taxes/tax-error.ts b/apps/taxes/src/modules/taxes/tax-error.ts new file mode 100644 index 0000000..8c50061 --- /dev/null +++ b/apps/taxes/src/modules/taxes/tax-error.ts @@ -0,0 +1,9 @@ +import { BaseError } from "../../error"; + +const TaxError = BaseError.subclass("TaxError"); + +// Errors that shouldn't happen +export const TaxUnexpectedError = TaxError.subclass("TaxUnexpectedError"); + +// Errors that are expected to happen +export const TaxExpectedError = TaxError.subclass("TaxExpectedError"); diff --git a/apps/taxes/src/modules/taxes/tax-provider-utils.test.ts b/apps/taxes/src/modules/taxes/tax-provider-utils.test.ts index 2fa6026..7306a4e 100644 --- a/apps/taxes/src/modules/taxes/tax-provider-utils.test.ts +++ b/apps/taxes/src/modules/taxes/tax-provider-utils.test.ts @@ -9,9 +9,7 @@ describe("taxProviderUtils", () => { expect(() => taxProviderUtils.resolveOptionalOrThrow(undefined)).toThrowError(); }); it("throws a custom error if value is undefined", () => { - expect(() => - taxProviderUtils.resolveOptionalOrThrow(undefined, new Error("test")) - ).toThrowError("test"); + expect(() => taxProviderUtils.resolveOptionalOrThrow(undefined, "test")).toThrowError("test"); }), it("returns value if value is not undefined", () => { expect(taxProviderUtils.resolveOptionalOrThrow("test")).toBe("test"); diff --git a/apps/taxes/src/modules/taxes/tax-provider-utils.ts b/apps/taxes/src/modules/taxes/tax-provider-utils.ts index 5e12986..b71398c 100644 --- a/apps/taxes/src/modules/taxes/tax-provider-utils.ts +++ b/apps/taxes/src/modules/taxes/tax-provider-utils.ts @@ -1,25 +1,34 @@ import { z } from "zod"; +import { TaxUnexpectedError } from "./tax-error"; /* * The providers sdk types claim to sometimes return undefined. * If it ever happens, we have nothing to fall back to, so we throw an error. * Should only be used for values that are required for further calculation. */ -function resolveOptionalOrThrow(value: T | undefined | null, error?: Error): T { +function resolveOptionalOrThrow(value: T | undefined | null, errorMessage?: string): T { if (value === undefined || value === null) { - throw error - ? error - : new Error("Could not resolve data. Value needed for further calculation is undefined."); + throw new TaxUnexpectedError( + errorMessage + ? errorMessage + : "Could not resolve data. Value needed for further calculation is undefined.", + ); } return value; } function resolveStringOrThrow(value: string | undefined | null): string { - return z + const parseResult = z .string({ required_error: "This field must be defined." }) .min(1, { message: "This field can not be empty." }) - .parse(value); + .safeParse(value); + + if (!parseResult.success) { + throw new TaxUnexpectedError(parseResult.error.message); + } + + return parseResult.data; } export const taxProviderUtils = { diff --git a/apps/taxes/src/modules/taxjar/calculate-taxes/taxjar-calculate-taxes-response-lines-transformer.ts b/apps/taxes/src/modules/taxjar/calculate-taxes/taxjar-calculate-taxes-response-lines-transformer.ts index efc0fb2..cd33228 100644 --- a/apps/taxes/src/modules/taxjar/calculate-taxes/taxjar-calculate-taxes-response-lines-transformer.ts +++ b/apps/taxes/src/modules/taxjar/calculate-taxes/taxjar-calculate-taxes-response-lines-transformer.ts @@ -13,14 +13,14 @@ import { */ export function matchPayloadLinesToResponseLines( payloadLines: TaxBaseFragment["lines"], - responseLines: NonNullable + responseLines: NonNullable, ) { return payloadLines.map((payloadLine) => { const responseLine = responseLines.find((line) => line.id === payloadLine.sourceLine.id); if (!responseLine) { throw new Error( - `Saleor product line with id ${payloadLine.sourceLine.id} not found in TaxJar response.` + `Saleor product line with id ${payloadLine.sourceLine.id} not found in TaxJar response.`, ); } @@ -31,7 +31,7 @@ export function matchPayloadLinesToResponseLines( export class TaxJarCalculateTaxesResponseLinesTransformer { transform( payload: TaxJarCalculateTaxesPayload, - response: TaxForOrderRes + response: TaxForOrderRes, ): TaxJarCalculateTaxesResponse["lines"] { const responseLines = response.tax.breakdown?.line_items ?? []; @@ -40,15 +40,15 @@ export class TaxJarCalculateTaxesResponseLinesTransformer { return lines.map((line) => { const taxableAmount = taxProviderUtils.resolveOptionalOrThrow( line?.taxable_amount, - new Error("Line taxable amount is required to calculate net amount") + "Line taxable amount is required to calculate net amount", ); const taxCollectable = taxProviderUtils.resolveOptionalOrThrow( line?.tax_collectable, - new Error("Line tax collectable is required to calculate net amount") + "Line tax collectable is required to calculate net amount", ); const taxRate = taxProviderUtils.resolveOptionalOrThrow( line?.combined_tax_rate, - new Error("Line combined tax rate is required to calculate net amount") + "Line combined tax rate is required to calculate net amount", ); return { diff --git a/apps/taxes/src/modules/taxjar/order-confirmed/taxjar-order-confirmed-payload-transformer.ts b/apps/taxes/src/modules/taxjar/order-confirmed/taxjar-order-confirmed-payload-transformer.ts index f4d6bf7..269bfb3 100644 --- a/apps/taxes/src/modules/taxjar/order-confirmed/taxjar-order-confirmed-payload-transformer.ts +++ b/apps/taxes/src/modules/taxjar/order-confirmed/taxjar-order-confirmed-payload-transformer.ts @@ -15,14 +15,14 @@ export function sumPayloadLines(lines: LineItem[]): number { prev + taxProviderUtils.resolveOptionalOrThrow( line.unit_price, - new Error("Line unit_price is required to calculate order taxes") + "Line unit_price is required to calculate order taxes", ) * taxProviderUtils.resolveOptionalOrThrow( line.quantity, - new Error("Line quantity is required to calculate order taxes") + "Line quantity is required to calculate order taxes", ), - 0 - ) + 0, + ), ); } @@ -30,7 +30,7 @@ export class TaxJarOrderConfirmedPayloadTransformer { transform( order: OrderConfirmedSubscriptionFragment, taxJarConfig: TaxJarConfig, - matches: TaxJarTaxCodeMatches + matches: TaxJarTaxCodeMatches, ): TaxJarOrderConfirmedTarget { const linesTransformer = new TaxJarOrderConfirmedPayloadLinesTransformer(); const lineItems = linesTransformer.transform(order.lines, matches); diff --git a/cspell.json b/cspell.json index fc6df1c..efde75f 100644 --- a/cspell.json +++ b/cspell.json @@ -1,5 +1,6 @@ { "words": [ + "Autocommit", "Adyen", "Afterpay", "Algolia",