refactor: use resolveOptionalOrThrow (#480)
* refactor: 🚚 move tax-line-resolver to taxjar-calculate-taxes-map * feat: ✨ add resolveOptionalOrThrow util * build: 👷 add changeset * fix: 🐛 fix shipping line bug & add handleWebhookError * build: 👷 update changeset * refactor: ♻️ add optional error argument
This commit is contained in:
parent
a5df092828
commit
dd799e6993
11 changed files with 634 additions and 64 deletions
5
.changeset/thin-ravens-begin.md
Normal file
5
.changeset/thin-ravens-begin.md
Normal file
|
@ -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.
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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,
|
||||
};
|
20
apps/taxes/src/modules/taxes/tax-provider-utils-test.ts
Normal file
20
apps/taxes/src/modules/taxes/tax-provider-utils-test.ts
Normal file
|
@ -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");
|
||||
});
|
||||
});
|
||||
});
|
18
apps/taxes/src/modules/taxes/tax-provider-utils.ts
Normal file
18
apps/taxes/src/modules/taxes/tax-provider-utils.ts
Normal file
|
@ -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<T>(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,
|
||||
};
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<CalculateTaxesPayload>({
|
||||
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");
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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<CalculateTaxesPayload>({
|
||||
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");
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue