refactor: ♻️ add new error subclasses and use in tax-provider-utils
This commit is contained in:
parent
3c20d095ff
commit
df4da32b4a
11 changed files with 62 additions and 41 deletions
6
apps/taxes/src/error.ts
Normal file
6
apps/taxes/src/error.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import ModernError from "modern-errors";
|
||||
import modernErrorsSerialize from "modern-errors-serialize";
|
||||
|
||||
export const BaseError = ModernError.subclass("BaseError", {
|
||||
plugins: [modernErrorsSerialize],
|
||||
});
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.",
|
||||
),
|
||||
};
|
||||
}
|
||||
|
|
9
apps/taxes/src/modules/taxes/tax-error.ts
Normal file
9
apps/taxes/src/modules/taxes/tax-error.ts
Normal file
|
@ -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");
|
|
@ -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");
|
||||
|
|
|
@ -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<T>(value: T | undefined | null, error?: Error): T {
|
||||
function resolveOptionalOrThrow<T>(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 = {
|
||||
|
|
|
@ -13,14 +13,14 @@ import {
|
|||
*/
|
||||
export function matchPayloadLinesToResponseLines(
|
||||
payloadLines: TaxBaseFragment["lines"],
|
||||
responseLines: NonNullable<Breakdown["line_items"]>
|
||||
responseLines: NonNullable<Breakdown["line_items"]>,
|
||||
) {
|
||||
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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"words": [
|
||||
"Autocommit",
|
||||
"Adyen",
|
||||
"Afterpay",
|
||||
"Algolia",
|
||||
|
|
Loading…
Reference in a new issue