fix: returning 0 for line price if item is not taxable (#476)
* fix: 🐛 returning 0 for line price if item is not taxable * build: 👷 add changeset
This commit is contained in:
parent
e751459b4d
commit
51134a5a8b
4 changed files with 1466 additions and 129 deletions
5
.changeset/thin-owls-beg.md
Normal file
5
.changeset/thin-owls-beg.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-app-taxes": patch
|
||||
---
|
||||
|
||||
Fix returning 0 for line price if Avatax returns isItemTaxable: false. This happens in, f.e., certain states in the US where there is no sales tax. After the fix, the app will fall back to the original line price.
|
|
@ -1,111 +1,88 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
AvataxCalculateTaxesMapPayloadArgs,
|
||||
avataxCalculateTaxesMaps,
|
||||
SHIPPING_ITEM_CODE,
|
||||
mapPayloadLines,
|
||||
mapResponseProductLines,
|
||||
mapResponseShippingLine,
|
||||
} from "./avatax-calculate-taxes-map";
|
||||
|
||||
// * Mocked payload data, channel config and avatax config
|
||||
const MOCKED_CALCULATE_TAXES_ARGS: AvataxCalculateTaxesMapPayloadArgs = {
|
||||
taxBase: {
|
||||
pricesEnteredWithTax: false,
|
||||
currency: "PLN",
|
||||
channel: {
|
||||
slug: "channel-pln",
|
||||
},
|
||||
sourceObject: {
|
||||
__typename: "Order",
|
||||
user: {
|
||||
id: "VXNlcjo5ZjY3ZjY0Zi1iZjY5LTQ5ZjYtYjQ4Zi1iZjY3ZjY0ZjY0ZjY=",
|
||||
},
|
||||
},
|
||||
discounts: [],
|
||||
address: {
|
||||
streetAddress1: "123 Palm Grove Ln",
|
||||
streetAddress2: "",
|
||||
city: "LOS ANGELES",
|
||||
country: {
|
||||
code: "US",
|
||||
},
|
||||
countryArea: "CA",
|
||||
postalCode: "90002",
|
||||
},
|
||||
shippingPrice: {
|
||||
amount: 48.33,
|
||||
},
|
||||
lines: [
|
||||
{
|
||||
quantity: 3,
|
||||
unitPrice: {
|
||||
amount: 84,
|
||||
},
|
||||
totalPrice: {
|
||||
amount: 252,
|
||||
},
|
||||
sourceLine: {
|
||||
__typename: "OrderLine",
|
||||
id: "T3JkZXJMaW5lOmY1NGQ1MWY2LTc1OTctNGY2OC1hNDk0LTFjYjZlYjRmOTlhMQ==",
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzQ2",
|
||||
product: {
|
||||
metafield: null,
|
||||
productType: {
|
||||
metafield: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
quantity: 1,
|
||||
unitPrice: {
|
||||
amount: 5.99,
|
||||
},
|
||||
totalPrice: {
|
||||
amount: 5.99,
|
||||
},
|
||||
sourceLine: {
|
||||
__typename: "OrderLine",
|
||||
id: "T3JkZXJMaW5lOjU1NTFjNTFjLTM5MWQtNGI0Ny04MGU0LWVjY2Q5ZjU4MjQyNQ==",
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6Mzg1",
|
||||
product: {
|
||||
metafield: null,
|
||||
productType: {
|
||||
metafield: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
channel: {
|
||||
providerInstanceId: "b8c29f49-7cae-4762-8458-e9a27eb83081",
|
||||
enabled: false,
|
||||
address: {
|
||||
country: "US",
|
||||
zip: "92093",
|
||||
state: "CA",
|
||||
city: "La Jolla",
|
||||
street: "9500 Gilman Drive",
|
||||
},
|
||||
},
|
||||
config: {
|
||||
companyCode: "DEFAULT",
|
||||
isAutocommit: false,
|
||||
isSandbox: true,
|
||||
name: "Avatax-1",
|
||||
password: "password",
|
||||
username: "username",
|
||||
shippingTaxCode: "FR000000",
|
||||
},
|
||||
};
|
||||
import { mapPayloadArgsMocks, transactionModelMocks } from "./mocks";
|
||||
|
||||
describe("avataxCalculateTaxesMaps", () => {
|
||||
describe.todo("mapResponse", () => {
|
||||
it.todo("calculation of fields");
|
||||
it.todo("formatting the fields");
|
||||
it.todo("rounding of numbers");
|
||||
describe("mapResponseShippingLine", () => {
|
||||
it("when shipping line is not taxable, returns line amount", () => {
|
||||
const nonTaxableShippingLine = mapResponseShippingLine(transactionModelMocks.nonTaxable);
|
||||
|
||||
expect(nonTaxableShippingLine).toEqual({
|
||||
shipping_price_gross_amount: 77.51,
|
||||
shipping_price_net_amount: 77.51,
|
||||
shipping_tax_rate: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it("when shipping line is taxable and tax is included, returns calculated gross & net amounts", () => {
|
||||
const taxableShippingLine = mapResponseShippingLine(
|
||||
transactionModelMocks.taxable.taxIncluded
|
||||
);
|
||||
|
||||
expect(taxableShippingLine).toEqual({
|
||||
shipping_price_gross_amount: 77.51,
|
||||
shipping_price_net_amount: 70.78,
|
||||
shipping_tax_rate: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it("when shipping line is taxable and tax is not included, returns calculated gross & net amounts", () => {
|
||||
const taxableShippingLine = mapResponseShippingLine(
|
||||
transactionModelMocks.taxable.taxNotIncluded
|
||||
);
|
||||
|
||||
expect(taxableShippingLine).toEqual({
|
||||
shipping_price_gross_amount: 84.87,
|
||||
shipping_price_net_amount: 77.51,
|
||||
shipping_tax_rate: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("mapResponseProductLines", () => {
|
||||
it("when product lines are not taxable, returns line amount", () => {
|
||||
const nonTaxableProductLines = mapResponseProductLines(transactionModelMocks.nonTaxable);
|
||||
|
||||
expect(nonTaxableProductLines).toEqual([
|
||||
{
|
||||
total_gross_amount: 20,
|
||||
total_net_amount: 20,
|
||||
tax_rate: 0,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("when product lines are taxable and tax is included, returns calculated gross & net amounts", () => {
|
||||
const taxableProductLines = mapResponseProductLines(
|
||||
transactionModelMocks.taxable.taxIncluded
|
||||
);
|
||||
|
||||
expect(taxableProductLines).toEqual([
|
||||
{
|
||||
total_gross_amount: 40,
|
||||
total_net_amount: 36.53,
|
||||
tax_rate: 0,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("when product lines are taxable and tax is not included, returns calculated gross & net amounts", () => {
|
||||
const taxableProductLines = mapResponseProductLines(
|
||||
transactionModelMocks.taxable.taxNotIncluded
|
||||
);
|
||||
|
||||
expect(taxableProductLines).toEqual([
|
||||
{
|
||||
total_gross_amount: 43.8,
|
||||
total_net_amount: 40,
|
||||
tax_rate: 0,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe.todo("mapPayload", () => {
|
||||
it.todo("calculation of fields");
|
||||
|
@ -113,17 +90,17 @@ describe("avataxCalculateTaxesMaps", () => {
|
|||
it.todo("rounding of numbers");
|
||||
});
|
||||
describe("mapLines", () => {
|
||||
const lines = avataxCalculateTaxesMaps.mapLines(
|
||||
MOCKED_CALCULATE_TAXES_ARGS.taxBase,
|
||||
MOCKED_CALCULATE_TAXES_ARGS.config
|
||||
const lines = mapPayloadLines(
|
||||
mapPayloadArgsMocks.default.taxBase,
|
||||
mapPayloadArgsMocks.default.config
|
||||
);
|
||||
|
||||
it("includes shipping as a line", () => {
|
||||
expect(lines).toContainEqual({
|
||||
itemCode: avataxCalculateTaxesMaps.shippingItemCode,
|
||||
itemCode: SHIPPING_ITEM_CODE,
|
||||
quantity: 1,
|
||||
amount: 48.33,
|
||||
taxCode: MOCKED_CALCULATE_TAXES_ARGS.config.shippingTaxCode,
|
||||
taxCode: mapPayloadArgsMocks.default.config.shippingTaxCode,
|
||||
taxIncluded: false,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,9 +14,9 @@ import { avataxAddressFactory } from "./address-factory";
|
|||
* * Shipping is a regular line item in Avatax
|
||||
* https://developer.avalara.com/avatax/dev-guide/shipping-and-handling/taxability-of-shipping-charges/
|
||||
*/
|
||||
const SHIPPING_ITEM_CODE = "Shipping";
|
||||
export const SHIPPING_ITEM_CODE = "Shipping";
|
||||
|
||||
function mapLines(taxBase: TaxBaseFragment, config: AvataxConfig): LineItemModel[] {
|
||||
export function mapPayloadLines(taxBase: TaxBaseFragment, config: AvataxConfig): LineItemModel[] {
|
||||
const productLines = taxBase.lines.map((line) => ({
|
||||
amount: line.totalPrice.amount,
|
||||
taxIncluded: taxBase.pricesEnteredWithTax,
|
||||
|
@ -62,49 +62,88 @@ const mapPayload = (props: AvataxCalculateTaxesMapPayloadArgs): CreateTransactio
|
|||
shipTo: avataxAddressFactory.fromSaleorAddress(taxBase.address!),
|
||||
},
|
||||
currencyCode: taxBase.currency,
|
||||
lines: mapLines(taxBase, config),
|
||||
lines: mapPayloadLines(taxBase, config),
|
||||
date: new Date(),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const mapResponse = (transaction: TransactionModel): CalculateTaxesResponse => {
|
||||
export function mapResponseShippingLine(
|
||||
transaction: TransactionModel
|
||||
): Pick<
|
||||
CalculateTaxesResponse,
|
||||
"shipping_price_gross_amount" | "shipping_price_net_amount" | "shipping_tax_rate"
|
||||
> {
|
||||
const shippingLine = transaction.lines?.find((line) => line.itemCode === SHIPPING_ITEM_CODE);
|
||||
|
||||
const productLines = transaction.lines?.filter((line) => line.itemCode !== SHIPPING_ITEM_CODE);
|
||||
if (!shippingLine?.isItemTaxable) {
|
||||
return {
|
||||
shipping_price_gross_amount: shippingLine?.lineAmount ?? 0,
|
||||
shipping_price_net_amount: shippingLine?.lineAmount ?? 0,
|
||||
/*
|
||||
* avatax doesnt return combined tax rate
|
||||
* // todo: calculate percentage tax rate
|
||||
*/
|
||||
shipping_tax_rate: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const shippingTaxCalculated = shippingLine?.taxCalculated ?? 0;
|
||||
const shippingTaxableAmount = shippingLine?.taxableAmount ?? 0;
|
||||
const shippingGrossAmount = numbers.roundFloatToTwoDecimals(
|
||||
shippingTaxableAmount + shippingTaxCalculated
|
||||
);
|
||||
const shippingNetAmount = shippingGrossAmount;
|
||||
|
||||
return {
|
||||
shipping_price_gross_amount: shippingGrossAmount,
|
||||
shipping_price_net_amount: shippingNetAmount,
|
||||
// todo: add shipping tax rate
|
||||
shipping_price_net_amount: shippingTaxableAmount,
|
||||
shipping_tax_rate: 0,
|
||||
lines:
|
||||
productLines?.map((line) => {
|
||||
const lineTaxCalculated = line.taxCalculated ?? 0;
|
||||
const lineTotalNetAmount = line.taxableAmount ?? 0;
|
||||
const lineTotalGrossAmount = numbers.roundFloatToTwoDecimals(
|
||||
lineTotalNetAmount + lineTaxCalculated
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function mapResponseProductLines(
|
||||
transaction: TransactionModel
|
||||
): CalculateTaxesResponse["lines"] {
|
||||
const productLines = transaction.lines?.filter((line) => line.itemCode !== SHIPPING_ITEM_CODE);
|
||||
|
||||
return (
|
||||
productLines?.map((line) => {
|
||||
if (!line.isItemTaxable) {
|
||||
return {
|
||||
total_gross_amount: lineTotalGrossAmount,
|
||||
total_net_amount: lineTotalNetAmount,
|
||||
// todo: add tax rate
|
||||
total_gross_amount: line.lineAmount ?? 0,
|
||||
total_net_amount: line.lineAmount ?? 0,
|
||||
tax_rate: 0,
|
||||
};
|
||||
}) ?? [],
|
||||
}
|
||||
|
||||
const lineTaxCalculated = line.taxCalculated ?? 0;
|
||||
const lineTotalNetAmount = line.taxableAmount ?? 0;
|
||||
const lineTotalGrossAmount = numbers.roundFloatToTwoDecimals(
|
||||
lineTotalNetAmount + lineTaxCalculated
|
||||
);
|
||||
|
||||
return {
|
||||
total_gross_amount: lineTotalGrossAmount,
|
||||
total_net_amount: lineTotalNetAmount,
|
||||
/*
|
||||
* avatax doesnt return combined tax rate
|
||||
* // todo: calculate percentage tax rate
|
||||
*/ tax_rate: 0,
|
||||
};
|
||||
}) ?? []
|
||||
);
|
||||
}
|
||||
|
||||
const mapResponse = (transaction: TransactionModel): CalculateTaxesResponse => {
|
||||
const shipping = mapResponseShippingLine(transaction);
|
||||
|
||||
return {
|
||||
...shipping,
|
||||
lines: mapResponseProductLines(transaction),
|
||||
};
|
||||
};
|
||||
|
||||
export const avataxCalculateTaxesMaps = {
|
||||
mapPayload,
|
||||
mapResponse,
|
||||
mapLines,
|
||||
shippingItemCode: SHIPPING_ITEM_CODE,
|
||||
};
|
||||
|
|
1316
apps/taxes/src/modules/avatax/maps/mocks.ts
Normal file
1316
apps/taxes/src/modules/avatax/maps/mocks.ts
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue