diff --git a/.changeset/thin-ravens-begin.md b/.changeset/thin-ravens-begin.md new file mode 100644 index 0000000..4a0a47e --- /dev/null +++ b/.changeset/thin-ravens-begin.md @@ -0,0 +1,5 @@ +--- +"saleor-app-taxes": minor +--- + +Adds `resolveOptionalOrThrow` util that throws when not able to resolve an optional value. Now, when critical values from external APIs are not found, the app will crash instead of falling back to 0s. diff --git a/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.test.ts b/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.test.ts index 6d80b3d..0123944 100644 --- a/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.test.ts +++ b/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.test.ts @@ -9,6 +9,15 @@ import { mapPayloadArgsMocks, transactionModelMocks } from "./mocks"; describe("avataxCalculateTaxesMaps", () => { describe("mapResponseShippingLine", () => { + it("when shipping line is not present, returns 0s", () => { + const shippingLine = mapResponseShippingLine(transactionModelMocks.noShippingLine); + + expect(shippingLine).toEqual({ + shipping_price_gross_amount: 0, + shipping_price_net_amount: 0, + shipping_tax_rate: 0, + }); + }); it("when shipping line is not taxable, returns line amount", () => { const nonTaxableShippingLine = mapResponseShippingLine(transactionModelMocks.nonTaxable); diff --git a/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.ts b/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.ts index d5d8b75..6a46e7a 100644 --- a/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.ts +++ b/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.ts @@ -9,6 +9,7 @@ import { CalculateTaxesResponse } from "../../taxes/tax-provider-webhook"; import { CreateTransactionArgs } from "../avatax-client"; import { AvataxConfig } from "../avatax-config"; import { avataxAddressFactory } from "./address-factory"; +import { taxProviderUtils } from "../../taxes/tax-provider-utils"; /** * * Shipping is a regular line item in Avatax @@ -76,10 +77,24 @@ export function mapResponseShippingLine( > { const shippingLine = transaction.lines?.find((line) => line.itemCode === SHIPPING_ITEM_CODE); - if (!shippingLine?.isItemTaxable) { + if (!shippingLine) { return { - shipping_price_gross_amount: shippingLine?.lineAmount ?? 0, - shipping_price_net_amount: shippingLine?.lineAmount ?? 0, + shipping_price_gross_amount: 0, + shipping_price_net_amount: 0, + shipping_tax_rate: 0, + }; + } + + if (!shippingLine.isItemTaxable) { + return { + shipping_price_gross_amount: taxProviderUtils.resolveOptionalOrThrow( + shippingLine.lineAmount, + new Error("shippingLine.lineAmount is undefined") + ), + shipping_price_net_amount: taxProviderUtils.resolveOptionalOrThrow( + shippingLine.lineAmount, + new Error("shippingLine.lineAmount is undefined") + ), /* * avatax doesnt return combined tax rate * // todo: calculate percentage tax rate @@ -88,8 +103,14 @@ export function mapResponseShippingLine( }; } - const shippingTaxCalculated = shippingLine?.taxCalculated ?? 0; - const shippingTaxableAmount = shippingLine?.taxableAmount ?? 0; + const shippingTaxCalculated = taxProviderUtils.resolveOptionalOrThrow( + shippingLine.taxCalculated, + new Error("shippingLine.taxCalculated is undefined") + ); + const shippingTaxableAmount = taxProviderUtils.resolveOptionalOrThrow( + shippingLine.taxableAmount, + new Error("shippingLine.taxableAmount is undefined") + ); const shippingGrossAmount = numbers.roundFloatToTwoDecimals( shippingTaxableAmount + shippingTaxCalculated ); @@ -110,14 +131,26 @@ export function mapResponseProductLines( productLines?.map((line) => { if (!line.isItemTaxable) { return { - total_gross_amount: line.lineAmount ?? 0, - total_net_amount: line.lineAmount ?? 0, + total_gross_amount: taxProviderUtils.resolveOptionalOrThrow( + line.lineAmount, + new Error("line.lineAmount is undefined") + ), + total_net_amount: taxProviderUtils.resolveOptionalOrThrow( + line.lineAmount, + new Error("line.lineAmount is undefined") + ), tax_rate: 0, }; } - const lineTaxCalculated = line.taxCalculated ?? 0; - const lineTotalNetAmount = line.taxableAmount ?? 0; + const lineTaxCalculated = taxProviderUtils.resolveOptionalOrThrow( + line.taxCalculated, + new Error("line.taxCalculated is undefined") + ); + const lineTotalNetAmount = taxProviderUtils.resolveOptionalOrThrow( + line.taxableAmount, + new Error("line.taxableAmount is undefined") + ); const lineTotalGrossAmount = numbers.roundFloatToTwoDecimals( lineTotalNetAmount + lineTaxCalculated ); diff --git a/apps/taxes/src/modules/avatax/maps/mocks.ts b/apps/taxes/src/modules/avatax/maps/mocks.ts index d1525d3..fdde194 100644 --- a/apps/taxes/src/modules/avatax/maps/mocks.ts +++ b/apps/taxes/src/modules/avatax/maps/mocks.ts @@ -10,6 +10,492 @@ import { RateType } from "avatax/lib/enums/RateType"; import { TransactionModel } from "avatax/lib/models/TransactionModel"; import { AvataxCalculateTaxesMapPayloadArgs } from "./avatax-calculate-taxes-map"; +const NO_SHIPPING_TRANSACTION_MOCK: TransactionModel = { + id: 0, + code: "30ddd987-ed4a-49f1-bba1-ec555e417511", + companyId: 7799660, + date: new Date(), + paymentDate: new Date(), + status: DocumentStatus.Temporary, + type: DocumentType.SalesOrder, + batchCode: "", + currencyCode: "USD", + exchangeRateCurrencyCode: "USD", + customerUsageType: "", + entityUseCode: "", + customerVendorCode: "VXNlcjoyMDg0NTEwNDEw", + customerCode: "VXNlcjoyMDg0NTEwNDEw", + exemptNo: "", + reconciled: false, + locationCode: "", + reportingLocationCode: "", + purchaseOrderNo: "", + referenceCode: "", + salespersonCode: "", + totalAmount: 199.17, + totalExempt: 0, + totalDiscount: 0, + totalTax: 18.92, + totalTaxable: 199.17, + totalTaxCalculated: 18.92, + adjustmentReason: AdjustmentReason.NotAdjusted, + locked: false, + version: 1, + exchangeRateEffectiveDate: new Date(), + exchangeRate: 1, + modifiedDate: new Date(), + modifiedUserId: 6479978, + taxDate: new Date(), + lines: [ + { + id: 0, + transactionId: 0, + lineNumber: "1", + customerUsageType: "", + entityUseCode: "", + discountAmount: 0, + exemptAmount: 0, + exemptCertId: 0, + exemptNo: "", + isItemTaxable: true, + itemCode: "", + lineAmount: 80, + quantity: 4, + ref1: "", + ref2: "", + reportingDate: new Date(), + tax: 7.6, + taxableAmount: 80, + taxCalculated: 7.6, + taxCode: "P0000000", + taxCodeId: 8087, + taxDate: new Date(), + taxIncluded: false, + details: [ + { + id: 0, + transactionLineId: 0, + transactionId: 0, + country: "US", + region: "CA", + exemptAmount: 0, + jurisCode: "06", + jurisName: "CALIFORNIA", + stateAssignedNo: "", + jurisType: JurisTypeId.STA, + jurisdictionType: JurisdictionType.State, + nonTaxableAmount: 0, + rate: 0.06, + tax: 4.8, + taxableAmount: 80, + taxType: "Use", + taxSubTypeId: "U", + taxName: "CA STATE TAX", + taxAuthorityTypeId: 45, + taxCalculated: 4.8, + rateType: RateType.General, + rateTypeCode: "G", + unitOfBasis: "PerCurrencyUnit", + isNonPassThru: false, + isFee: false, + reportingTaxableUnits: 80, + reportingNonTaxableUnits: 0, + reportingExemptUnits: 0, + reportingTax: 4.8, + reportingTaxCalculated: 4.8, + liabilityType: LiabilityType.Seller, + chargedTo: ChargedTo.Buyer, + }, + { + id: 0, + transactionLineId: 0, + transactionId: 0, + country: "US", + region: "CA", + exemptAmount: 0, + jurisCode: "037", + jurisName: "LOS ANGELES", + stateAssignedNo: "", + jurisType: JurisTypeId.CTY, + jurisdictionType: JurisdictionType.County, + nonTaxableAmount: 0, + rate: 0.0025, + tax: 0.2, + taxableAmount: 80, + taxType: "Use", + taxSubTypeId: "U", + taxName: "CA COUNTY TAX", + taxAuthorityTypeId: 45, + taxCalculated: 0.2, + rateType: RateType.General, + rateTypeCode: "G", + unitOfBasis: "PerCurrencyUnit", + isNonPassThru: false, + isFee: false, + reportingTaxableUnits: 80, + reportingNonTaxableUnits: 0, + reportingExemptUnits: 0, + reportingTax: 0.2, + reportingTaxCalculated: 0.2, + liabilityType: LiabilityType.Seller, + chargedTo: ChargedTo.Buyer, + }, + { + id: 0, + transactionLineId: 0, + transactionId: 0, + country: "US", + region: "CA", + exemptAmount: 0, + jurisCode: "EMAR0", + jurisName: "LOS ANGELES COUNTY DISTRICT TAX SP", + stateAssignedNo: "594", + jurisType: JurisTypeId.STJ, + jurisdictionType: JurisdictionType.Special, + nonTaxableAmount: 0, + rate: 0.0225, + tax: 1.8, + taxableAmount: 80, + taxType: "Use", + taxSubTypeId: "U", + taxName: "CA SPECIAL TAX", + taxAuthorityTypeId: 45, + taxCalculated: 1.8, + rateType: RateType.General, + rateTypeCode: "G", + unitOfBasis: "PerCurrencyUnit", + isNonPassThru: false, + isFee: false, + reportingTaxableUnits: 80, + reportingNonTaxableUnits: 0, + reportingExemptUnits: 0, + reportingTax: 1.8, + reportingTaxCalculated: 1.8, + liabilityType: LiabilityType.Seller, + chargedTo: ChargedTo.Buyer, + }, + { + id: 0, + transactionLineId: 0, + transactionId: 0, + country: "US", + region: "CA", + exemptAmount: 0, + jurisCode: "EMTC0", + jurisName: "LOS ANGELES CO LOCAL TAX SL", + stateAssignedNo: "19", + jurisType: JurisTypeId.STJ, + jurisdictionType: JurisdictionType.Special, + nonTaxableAmount: 0, + rate: 0.01, + tax: 0.8, + taxableAmount: 80, + taxType: "Use", + taxSubTypeId: "U", + taxName: "CA SPECIAL TAX", + taxAuthorityTypeId: 45, + taxCalculated: 0.8, + rateType: RateType.General, + rateTypeCode: "G", + unitOfBasis: "PerCurrencyUnit", + isNonPassThru: false, + isFee: false, + reportingTaxableUnits: 80, + reportingNonTaxableUnits: 0, + reportingExemptUnits: 0, + reportingTax: 0.8, + reportingTaxCalculated: 0.8, + liabilityType: LiabilityType.Seller, + chargedTo: ChargedTo.Buyer, + }, + ], + nonPassthroughDetails: [], + hsCode: "", + costInsuranceFreight: 0, + vatCode: "", + vatNumberTypeId: 0, + }, + { + id: 0, + transactionId: 0, + lineNumber: "2", + customerUsageType: "", + entityUseCode: "", + discountAmount: 0, + exemptAmount: 0, + exemptCertId: 0, + exemptNo: "", + isItemTaxable: true, + itemCode: "", + lineAmount: 60, + quantity: 3, + ref1: "", + ref2: "", + reportingDate: new Date(), + tax: 5.7, + taxableAmount: 60, + taxCalculated: 5.7, + taxCode: "P0000000", + taxCodeId: 8087, + taxDate: new Date(), + taxIncluded: false, + details: [ + { + id: 0, + transactionLineId: 0, + transactionId: 0, + country: "US", + region: "CA", + exemptAmount: 0, + jurisCode: "06", + jurisName: "CALIFORNIA", + stateAssignedNo: "", + jurisType: JurisTypeId.STA, + jurisdictionType: JurisdictionType.State, + nonTaxableAmount: 0, + rate: 0.06, + tax: 3.6, + taxableAmount: 60, + taxType: "Use", + taxSubTypeId: "U", + taxName: "CA STATE TAX", + taxAuthorityTypeId: 45, + taxCalculated: 3.6, + rateType: RateType.General, + rateTypeCode: "G", + unitOfBasis: "PerCurrencyUnit", + isNonPassThru: false, + isFee: false, + reportingTaxableUnits: 60, + reportingNonTaxableUnits: 0, + reportingExemptUnits: 0, + reportingTax: 3.6, + reportingTaxCalculated: 3.6, + liabilityType: LiabilityType.Seller, + chargedTo: ChargedTo.Buyer, + }, + { + id: 0, + transactionLineId: 0, + transactionId: 0, + country: "US", + region: "CA", + exemptAmount: 0, + jurisCode: "037", + jurisName: "LOS ANGELES", + stateAssignedNo: "", + jurisType: JurisTypeId.CTY, + jurisdictionType: JurisdictionType.County, + nonTaxableAmount: 0, + rate: 0.0025, + tax: 0.15, + taxableAmount: 60, + taxType: "Use", + taxSubTypeId: "U", + taxName: "CA COUNTY TAX", + taxAuthorityTypeId: 45, + taxCalculated: 0.15, + rateType: RateType.General, + rateTypeCode: "G", + unitOfBasis: "PerCurrencyUnit", + isNonPassThru: false, + isFee: false, + reportingTaxableUnits: 60, + reportingNonTaxableUnits: 0, + reportingExemptUnits: 0, + reportingTax: 0.15, + reportingTaxCalculated: 0.15, + liabilityType: LiabilityType.Seller, + chargedTo: ChargedTo.Buyer, + }, + { + id: 0, + transactionLineId: 0, + transactionId: 0, + country: "US", + region: "CA", + exemptAmount: 0, + jurisCode: "EMAR0", + jurisName: "LOS ANGELES COUNTY DISTRICT TAX SP", + stateAssignedNo: "594", + jurisType: JurisTypeId.STJ, + jurisdictionType: JurisdictionType.Special, + nonTaxableAmount: 0, + rate: 0.0225, + tax: 1.35, + taxableAmount: 60, + taxType: "Use", + taxSubTypeId: "U", + taxName: "CA SPECIAL TAX", + taxAuthorityTypeId: 45, + taxCalculated: 1.35, + rateType: RateType.General, + rateTypeCode: "G", + unitOfBasis: "PerCurrencyUnit", + isNonPassThru: false, + isFee: false, + reportingTaxableUnits: 60, + reportingNonTaxableUnits: 0, + reportingExemptUnits: 0, + reportingTax: 1.35, + reportingTaxCalculated: 1.35, + liabilityType: LiabilityType.Seller, + chargedTo: ChargedTo.Buyer, + }, + { + id: 0, + transactionLineId: 0, + transactionId: 0, + country: "US", + region: "CA", + exemptAmount: 0, + jurisCode: "EMTC0", + jurisName: "LOS ANGELES CO LOCAL TAX SL", + stateAssignedNo: "19", + jurisType: JurisTypeId.STJ, + jurisdictionType: JurisdictionType.Special, + nonTaxableAmount: 0, + rate: 0.01, + tax: 0.6, + taxableAmount: 60, + taxType: "Use", + taxSubTypeId: "U", + taxName: "CA SPECIAL TAX", + taxAuthorityTypeId: 45, + taxCalculated: 0.6, + rateType: RateType.General, + rateTypeCode: "G", + unitOfBasis: "PerCurrencyUnit", + isNonPassThru: false, + isFee: false, + reportingTaxableUnits: 60, + reportingNonTaxableUnits: 0, + reportingExemptUnits: 0, + reportingTax: 0.6, + reportingTaxCalculated: 0.6, + liabilityType: LiabilityType.Seller, + chargedTo: ChargedTo.Buyer, + }, + ], + nonPassthroughDetails: [], + hsCode: "", + costInsuranceFreight: 0, + vatCode: "", + vatNumberTypeId: 0, + }, + ], + addresses: [ + { + id: 0, + transactionId: 0, + boundaryLevel: BoundaryLevel.Zip5, + line1: "123 Palm Grove Ln", + line2: "", + line3: "", + city: "LOS ANGELES", + region: "CA", + postalCode: "90002", + country: "US", + taxRegionId: 4017056, + latitude: "33.948712", + longitude: "-118.245951", + }, + { + id: 0, + transactionId: 0, + boundaryLevel: BoundaryLevel.Zip5, + line1: "8559 Lake Avenue", + line2: "", + line3: "", + city: "New York", + region: "NY", + postalCode: "10001", + country: "US", + taxRegionId: 2088629, + latitude: "40.748481", + longitude: "-73.993125", + }, + ], + summary: [ + { + country: "US", + region: "CA", + jurisType: JurisdictionType.State, + jurisCode: "06", + jurisName: "CALIFORNIA", + taxAuthorityType: 45, + stateAssignedNo: "", + taxType: "Use", + taxSubType: "U", + taxName: "CA STATE TAX", + rateType: RateType.General, + taxable: 199.17, + rate: 0.06, + tax: 11.95, + taxCalculated: 11.95, + nonTaxable: 0, + exemption: 0, + }, + { + country: "US", + region: "CA", + jurisType: JurisdictionType.County, + jurisCode: "037", + jurisName: "LOS ANGELES", + taxAuthorityType: 45, + stateAssignedNo: "", + taxType: "Use", + taxSubType: "U", + taxName: "CA COUNTY TAX", + rateType: RateType.General, + taxable: 199.17, + rate: 0.0025, + tax: 0.5, + taxCalculated: 0.5, + nonTaxable: 0, + exemption: 0, + }, + { + country: "US", + region: "CA", + jurisType: JurisdictionType.Special, + jurisCode: "EMTC0", + jurisName: "LOS ANGELES CO LOCAL TAX SL", + taxAuthorityType: 45, + stateAssignedNo: "19", + taxType: "Use", + taxSubType: "U", + taxName: "CA SPECIAL TAX", + rateType: RateType.General, + taxable: 199.17, + rate: 0.01, + tax: 1.99, + taxCalculated: 1.99, + nonTaxable: 0, + exemption: 0, + }, + { + country: "US", + region: "CA", + jurisType: JurisdictionType.Special, + jurisCode: "EMAR0", + jurisName: "LOS ANGELES COUNTY DISTRICT TAX SP", + taxAuthorityType: 45, + stateAssignedNo: "594", + taxType: "Use", + taxSubType: "U", + taxName: "CA SPECIAL TAX", + rateType: RateType.General, + taxable: 199.17, + rate: 0.0225, + tax: 4.48, + taxCalculated: 4.48, + nonTaxable: 0, + exemption: 0, + }, + ], +}; + const TAXABLE_TAX_INCLUDED_TRANSACTION_MOCK: TransactionModel = { id: 0, code: "8fc875ce-a929-4556-9f30-0165b1597d9f", @@ -1309,6 +1795,7 @@ export const transactionModelMocks = { taxNotIncluded: TAXABLE_TAX_NOT_INCLUDED_TRANSACTION_MOCK, }, nonTaxable: NON_TAXABLE_TRANSACTION_MOCK, + noShippingLine: NO_SHIPPING_TRANSACTION_MOCK, }; export const mapPayloadArgsMocks = { diff --git a/apps/taxes/src/modules/taxes/tax-line-resolver.ts b/apps/taxes/src/modules/taxes/tax-line-resolver.ts deleted file mode 100644 index d7ed5d3..0000000 --- a/apps/taxes/src/modules/taxes/tax-line-resolver.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { TaxBaseLineFragment } from "../../../generated/graphql"; - -function getTaxBaseLineDiscount( - line: TaxBaseLineFragment, - totalDiscount: number, - allLinesTotal: number -) { - if (totalDiscount === 0 || allLinesTotal === 0) { - return 0; - } - const lineTotalAmount = Number(line.totalPrice.amount); - const discountAmount = (lineTotalAmount / allLinesTotal) * totalDiscount; - - if (discountAmount > lineTotalAmount) { - return lineTotalAmount; - } - return discountAmount; -} - -/** - * * currently the CalculateTaxes subscription uses only the taxjar code (see: TaxBaseLine in TaxBase.graphql) - * todo: add ability to pass providers or get codes for all providers - * todo: add `getOrderLineTaxCode` - * todo: later, replace with tax code matcher - */ -function getTaxBaseLineTaxCode(line: TaxBaseLineFragment): string { - if (line.sourceLine.__typename === "OrderLine") { - return ( - line.sourceLine.variant?.product.metafield ?? - line.sourceLine.variant?.product.productType.metafield ?? - "" - ); - } - - return ( - (line.sourceLine.productVariant.product.metafield || - line.sourceLine.productVariant.product.productType.metafield) ?? - "" - ); -} - -export const taxLineResolver = { - getTaxBaseLineDiscount, - getTaxBaseLineTaxCode, -}; diff --git a/apps/taxes/src/modules/taxes/tax-provider-utils-test.ts b/apps/taxes/src/modules/taxes/tax-provider-utils-test.ts new file mode 100644 index 0000000..fca689d --- /dev/null +++ b/apps/taxes/src/modules/taxes/tax-provider-utils-test.ts @@ -0,0 +1,20 @@ +// write vitest tests for checking if taxProviderUtils.resolveOptionalOrThrow throws an error for undefined value + +import { describe, expect, it } from "vitest"; +import { taxProviderUtils } from "./tax-provider-utils"; + +describe("taxProviderUtils", () => { + describe("resolveOptionalOrThrow", () => { + it("should throw a default error if value is undefined", () => { + expect(() => taxProviderUtils.resolveOptionalOrThrow(undefined)).toThrowError(); + }); + it("should throw a custom error if value is undefined", () => { + expect(() => + taxProviderUtils.resolveOptionalOrThrow(undefined, new Error("test")) + ).toThrowError("test"); + }), + it("should return 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 new file mode 100644 index 0000000..568ee54 --- /dev/null +++ b/apps/taxes/src/modules/taxes/tax-provider-utils.ts @@ -0,0 +1,18 @@ +/* + * 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, error?: Error): T { + if (value === undefined) { + throw error + ? error + : new Error("Could not resolve data. Value needed for further calculation is undefined."); + } + + return value; +} + +export const taxProviderUtils = { + resolveOptionalOrThrow, +}; diff --git a/apps/taxes/src/modules/taxjar/maps/taxjar-calculate-taxes-map.ts b/apps/taxes/src/modules/taxjar/maps/taxjar-calculate-taxes-map.ts index 8321c26..aaa7e6e 100644 --- a/apps/taxes/src/modules/taxjar/maps/taxjar-calculate-taxes-map.ts +++ b/apps/taxes/src/modules/taxjar/maps/taxjar-calculate-taxes-map.ts @@ -1,10 +1,26 @@ import { TaxForOrderRes } from "taxjar/dist/types/returnTypes"; -import { TaxBaseFragment } from "../../../../generated/graphql"; +import { TaxBaseFragment, TaxBaseLineFragment } from "../../../../generated/graphql"; import { ChannelConfig } from "../../channels-configuration/channels-config"; -import { taxLineResolver } from "../../taxes/tax-line-resolver"; import { CalculateTaxesResponse } from "../../taxes/tax-provider-webhook"; import { FetchTaxForOrderArgs } from "../taxjar-client"; +function getTaxBaseLineDiscount( + line: TaxBaseLineFragment, + totalDiscount: number, + allLinesTotal: number +) { + if (totalDiscount === 0 || allLinesTotal === 0) { + return 0; + } + const lineTotalAmount = Number(line.totalPrice.amount); + const discountAmount = (lineTotalAmount / allLinesTotal) * totalDiscount; + + if (discountAmount > lineTotalAmount) { + return lineTotalAmount; + } + return discountAmount; +} + const formatCalculatedAmount = (amount: number) => { return Number(amount.toFixed(2)); }; @@ -35,11 +51,7 @@ const prepareLinesWithDiscountPayload = ( const totalDiscount = discountsSum <= allLinesTotal ? discountsSum : allLinesTotal; return lines.map((line) => { - const discountAmount = taxLineResolver.getTaxBaseLineDiscount( - line, - totalDiscount, - allLinesTotal - ); + const discountAmount = getTaxBaseLineDiscount(line, totalDiscount, allLinesTotal); return { id: line.sourceLine.id, diff --git a/apps/taxes/src/modules/taxjar/maps/taxjar-order-created-map.ts b/apps/taxes/src/modules/taxjar/maps/taxjar-order-created-map.ts index 5983c65..e44d5f7 100644 --- a/apps/taxes/src/modules/taxjar/maps/taxjar-order-created-map.ts +++ b/apps/taxes/src/modules/taxjar/maps/taxjar-order-created-map.ts @@ -5,6 +5,7 @@ import { ChannelConfig } from "../../channels-configuration/channels-config"; import { CreateOrderResponse } from "../../taxes/tax-provider-webhook"; import { CreateOrderArgs } from "../taxjar-client"; import { numbers } from "../../taxes/numbers"; +import { taxProviderUtils } from "../../taxes/tax-provider-utils"; function mapLines(lines: OrderCreatedSubscriptionFragment["lines"]): LineItem[] { return lines.map((line) => ({ @@ -20,7 +21,19 @@ function mapLines(lines: OrderCreatedSubscriptionFragment["lines"]): LineItem[] function sumLines(lines: LineItem[]): number { return numbers.roundFloatToTwoDecimals( - lines.reduce((prev, next) => prev + (next.unit_price ?? 0) * (next.quantity ?? 0), 0) + lines.reduce( + (prev, line) => + prev + + taxProviderUtils.resolveOptionalOrThrow( + line.unit_price, + new Error("line.unit_price is undefined") + ) * + taxProviderUtils.resolveOptionalOrThrow( + line.quantity, + new Error("line.quantity is undefined") + ), + 0 + ) ); } 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 0989f0b..5e55d27 100644 --- a/apps/taxes/src/pages/api/webhooks/checkout-calculate-taxes.ts +++ b/apps/taxes/src/pages/api/webhooks/checkout-calculate-taxes.ts @@ -28,6 +28,15 @@ function verifyCalculateTaxesPayload(payload: CalculateTaxesPayload) { return payload; } +// ? maybe make it a part of WebhookResponse? +function handleWebhookError(error: unknown) { + const logger = createLogger({ service: "checkout-calculate-taxes", name: "handleWebhookError" }); + + if (error instanceof Error) { + logger.error(error.stack); + } +} + export const checkoutCalculateTaxesSyncWebhook = new SaleorSyncWebhook({ name: "CheckoutCalculateTaxes", apl: saleorApp.apl, @@ -68,7 +77,7 @@ export default checkoutCalculateTaxesSyncWebhook.createHandler(async (req, res, logger.info({ calculatedTaxes }, "Taxes calculated"); return webhookResponse.success(ctx.buildResponse(calculatedTaxes)); } catch (error) { - logger.error({ error }); + handleWebhookError(error); return webhookResponse.failure("Error while calculating taxes"); } }); 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 2502c73..dec830c 100644 --- a/apps/taxes/src/pages/api/webhooks/order-calculate-taxes.ts +++ b/apps/taxes/src/pages/api/webhooks/order-calculate-taxes.ts @@ -28,6 +28,15 @@ function verifyCalculateTaxesPayload(payload: CalculateTaxesPayload) { return payload; } +// ? maybe make it a part of WebhookResponse? +function handleWebhookError(error: unknown) { + const logger = createLogger({ service: "order-calculate-taxes", name: "handleWebhookError" }); + + if (error instanceof Error) { + logger.error(error.stack); + } +} + export const orderCalculateTaxesSyncWebhook = new SaleorSyncWebhook({ name: "OrderCalculateTaxes", apl: saleorApp.apl, @@ -68,7 +77,7 @@ export default orderCalculateTaxesSyncWebhook.createHandler(async (req, res, ctx logger.info({ calculatedTaxes }, "Taxes calculated"); return webhookResponse.success(ctx.buildResponse(calculatedTaxes)); } catch (error) { - logger.error({ error }); + handleWebhookError(error); return webhookResponse.failure("Error while calculating taxes"); } });