2023-03-02 11:01:17 +00:00
|
|
|
import { TaxParams } from "taxjar/dist/types/paramTypes";
|
|
|
|
import { TaxForOrderRes } from "taxjar/dist/types/returnTypes";
|
|
|
|
import {
|
|
|
|
TaxBaseFragment,
|
|
|
|
TaxBaseLineFragment,
|
|
|
|
TaxDiscountFragment,
|
2023-03-10 12:04:25 +00:00
|
|
|
} from "../../../generated/graphql";
|
|
|
|
import { ChannelConfig } from "../channels-configuration/channels-config";
|
|
|
|
import { taxLineResolver } from "../taxes/tax-line-resolver";
|
|
|
|
import { ResponseTaxPayload } from "../taxes/types";
|
2023-03-02 11:01:17 +00:00
|
|
|
|
|
|
|
const formatCalculatedAmount = (amount: number) => {
|
|
|
|
return Number(amount.toFixed(2));
|
|
|
|
};
|
|
|
|
|
2023-03-07 11:07:11 +00:00
|
|
|
// * This type is related to `TaxLineItem` from TaxJar. It should be unified.
|
2023-03-07 10:31:44 +00:00
|
|
|
type FetchTaxesLinePayload = {
|
|
|
|
id: string;
|
|
|
|
quantity: number;
|
|
|
|
taxCode?: string | null;
|
|
|
|
discount: number;
|
|
|
|
chargeTaxes: boolean;
|
|
|
|
unitAmount: number;
|
|
|
|
totalAmount: number;
|
|
|
|
};
|
|
|
|
|
2023-03-02 11:01:17 +00:00
|
|
|
const prepareLinesWithDiscountPayload = (
|
|
|
|
lines: Array<TaxBaseLineFragment>,
|
|
|
|
discounts: Array<TaxDiscountFragment>
|
|
|
|
): Array<FetchTaxesLinePayload> => {
|
|
|
|
const allLinesTotal = lines.reduce(
|
|
|
|
(total, current) => total + Number(current.totalPrice.amount),
|
|
|
|
0
|
|
|
|
);
|
|
|
|
const discountsSum =
|
|
|
|
discounts?.reduce((total, current) => total + Number(current.amount.amount), 0) || 0;
|
|
|
|
|
|
|
|
// Make sure that totalDiscount doesn't exceed a sum of all lines
|
|
|
|
const totalDiscount = discountsSum <= allLinesTotal ? discountsSum : allLinesTotal;
|
|
|
|
|
|
|
|
return lines.map((line) => {
|
|
|
|
const discountAmount = taxLineResolver.getLineDiscount(line, totalDiscount, allLinesTotal);
|
|
|
|
const taxCode = taxLineResolver.getLineTaxCode(line);
|
|
|
|
|
|
|
|
return {
|
|
|
|
id: line.sourceLine.id,
|
|
|
|
chargeTaxes: line.chargeTaxes,
|
|
|
|
taxCode: taxCode,
|
|
|
|
quantity: line.quantity,
|
|
|
|
totalAmount: Number(line.totalPrice.amount),
|
|
|
|
unitAmount: Number(line.unitPrice.amount),
|
|
|
|
discount: discountAmount,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const prepareResponse = (
|
|
|
|
payload: TaxBaseFragment,
|
|
|
|
response: TaxForOrderRes,
|
|
|
|
linesWithChargeTaxes: FetchTaxesLinePayload[],
|
|
|
|
linesWithDiscount: FetchTaxesLinePayload[]
|
|
|
|
): ResponseTaxPayload => {
|
|
|
|
const taxResponse = linesWithChargeTaxes.length !== 0 ? response : undefined;
|
|
|
|
const taxDetails = taxResponse?.tax.breakdown;
|
|
|
|
// todo: investigate
|
|
|
|
// ! There is no shipping in tax.breakdown from TaxJar.
|
|
|
|
const shippingDetails = taxDetails?.shipping;
|
|
|
|
|
|
|
|
const shippingPriceGross = shippingDetails
|
|
|
|
? shippingDetails.taxable_amount + shippingDetails.tax_collectable
|
|
|
|
: payload.shippingPrice.amount;
|
|
|
|
const shippingPriceNet = shippingDetails
|
|
|
|
? shippingDetails.taxable_amount
|
|
|
|
: payload.shippingPrice.amount;
|
|
|
|
const shippingTaxRate = shippingDetails ? shippingDetails.combined_tax_rate : 0;
|
|
|
|
// ! It appears shippingTaxRate is always 0 from TaxJar.
|
|
|
|
return {
|
|
|
|
shipping_price_gross_amount: formatCalculatedAmount(shippingPriceGross),
|
|
|
|
shipping_price_net_amount: formatCalculatedAmount(shippingPriceNet),
|
2023-03-07 10:31:44 +00:00
|
|
|
shipping_tax_rate: shippingTaxRate,
|
2023-03-02 11:01:17 +00:00
|
|
|
// lines order needs to be the same as for recieved payload.
|
|
|
|
// lines that have chargeTaxes === false will have returned default value
|
|
|
|
lines: linesWithDiscount.map((line) => {
|
|
|
|
const lineTax = taxDetails?.line_items?.find((l) => l.id === line.id);
|
|
|
|
const totalGrossAmount = lineTax
|
|
|
|
? lineTax.taxable_amount + lineTax.tax_collectable
|
|
|
|
: line.totalAmount - line.discount;
|
|
|
|
const totalNetAmount = lineTax ? lineTax.taxable_amount : line.totalAmount - line.discount;
|
2023-03-07 10:31:44 +00:00
|
|
|
const taxRate = lineTax ? lineTax.combined_tax_rate : 0;
|
2023-03-02 11:01:17 +00:00
|
|
|
return {
|
|
|
|
total_gross_amount: formatCalculatedAmount(totalGrossAmount),
|
|
|
|
total_net_amount: formatCalculatedAmount(totalNetAmount),
|
2023-03-07 10:31:44 +00:00
|
|
|
tax_rate: taxRate ?? 0,
|
2023-03-02 11:01:17 +00:00
|
|
|
};
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
const preparePayload = (
|
|
|
|
taxBase: TaxBaseFragment,
|
|
|
|
channel: ChannelConfig,
|
|
|
|
linesWithChargeTaxes: FetchTaxesLinePayload[]
|
|
|
|
): TaxParams => {
|
|
|
|
const taxParams = {
|
|
|
|
from_country: channel.address.country,
|
|
|
|
from_zip: channel.address.zip,
|
|
|
|
from_state: channel.address.state,
|
|
|
|
from_city: channel.address.city,
|
|
|
|
from_street: channel.address.street,
|
|
|
|
to_country: taxBase.address!.country.code,
|
|
|
|
to_zip: taxBase.address!.postalCode,
|
|
|
|
to_state: taxBase.address!.countryArea,
|
|
|
|
to_city: taxBase.address!.city,
|
|
|
|
to_street: `${taxBase.address!.streetAddress1} ${taxBase.address!.streetAddress2}`,
|
|
|
|
shipping: taxBase.shippingPrice.amount,
|
|
|
|
line_items: linesWithChargeTaxes.map((line) => ({
|
|
|
|
id: line.id,
|
|
|
|
quantity: line.quantity,
|
|
|
|
product_tax_code: line.taxCode || undefined,
|
|
|
|
unit_price: line.unitAmount,
|
|
|
|
discount: line.discount,
|
|
|
|
})),
|
|
|
|
};
|
|
|
|
|
|
|
|
return taxParams;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const taxJarCalculate = {
|
|
|
|
prepareLinesWithDiscountPayload,
|
|
|
|
prepareResponse,
|
|
|
|
preparePayload,
|
|
|
|
};
|