Handling Taxes & Fees
It's a common scenario where taxes and fees are added to a customer’s cart when making a purchase. If the customer has to submit a refund request, we want to ensure they receive their total amount back when the refund request is approved.
When quoting a cart & submitting an order to Teak, all additional taxes and processing fees must be included within the cost of the line item. The additional taxes and fees must be split proportionately based on item cost across all line items.
This ensures the customer receives a total refund, including additional taxes and fees for their line items.
NOTE:Ticket Protection is NOT included as a tax or fee nor submitted within the Order call to Teak.
Customer Cart Example 1 - Same Priced Items:
In this example, the customer purchases two tickets for $50.00 each AND purchases Ticket Protection for $7.98. Additional Taxes and Processing Fees (from the merchant) are added to the Customer’s Cart.
- Ticket 1 = $50.00
- Ticket 2 = $50.00
- Ticket Sub Total = $100.00
- Taxes = $5.00
- Processing Fees = $10.00
- Sub Total w/ Taxes and Fees: $115.00
- Ticket Protection = $7.98
- Cart Total = $122.98
Ticket 1 Percentage of Subtotal: $50 / $100 = .50 Ticket 2 Percentage of Subtotal: $50 / $100 = .50
Total Taxes and Fees = $15.00
Percentage of Taxes and Fees allocated to Ticket 1: $15 multiplied by .50 = $7.50 Percentage of Taxes and Fees allocated to Ticket 2: $15 multiplied by .50 = $7.50
Ticket 1 Cost (w/ taxes and fees): $57.50 Ticket 2 Cost (w/ taxes and fees): $57.50
The widget should render a quote for two line items, each at $57.50
Example 1 Order Submit:
Total Order to Teak would contain two line items- Ticket 1 for $57.50
- Ticket Cost = $50.00
- Taxes & Fees (50% of taxes and fees) = $7.50
- Ticket 2 for $57.50
- Ticket Cost = $50.00
- Taxes & Fees (50% of taxes and fees) = $7.50
Example 1 - Order Items API Example:
"items": [
{
"reference_number": "merchant_order_number",
"name": "Ticket 1",
"cost": 57.50 – including all taxes and fees from the merchant
},
{
"reference_number": "merchant_order_number",
"name": "Ticket 2",
"cost": 57.50 – including taxes and fees
}
]Splitting the taxes and fees proportionately amongst the number of tickets ensures that when the customer requests a refund, they get refunded for the total amount of each ticket.
Customer Cart Example 2 - Different Priced Items:
In this example, the customer purchases two tickets - one for $5.00 and one for $25.00 each AND purchases Ticket Protection for $7.98. Additional Taxes and Processing Fees (from the merchant) are added to the Customer’s Cart.
- Ticket 1 = $5.00
- Ticket 2 = $25.00
- Ticket Sub Total = $30.00
- Taxes = $5.00
- Processing Fees = $10.00
- Sub Total w/ Taxes and Fees: $45.00
- Ticket Protection = $5.98
- Cart Total = $50.98
Ticket 1 Percentage of Subtotal: $5 / $30 = .167 Ticket 2 Percentage of Subtotal: $25 / $30 = .833
Total Taxes and Fees = $15.00
Percentage of Taxes and Fees allocated to Ticket 1: $15 multiplied by .167 = $2.505 >> $2.51 Percentage of Taxes and Fees allocated to Ticket 2: $15 multiplied by .833 = $12.495 >> $12.50
Heads upIn this scenario where the sum of splitting taxes and fees does not match the tax & fee total ($15.01 calculation vs $15.00 actual), an adjustment would need to be made to one item to remove the extra penny before applying to the ticket cost. For this example, we’ll lower the $2.51 to $2.50 so the fees add up correctly and add to the corresponding ticket cost. This is important when order total (i.e. “Your order of $xx.xx will be protected…” as the total is calculated as the sum of items.
Ticket 1 Cost (w/ taxes and fees) $5 + $2.50 = $7.50 Ticket 2 Cost (w/ taxes and fees): $25 + $12.50 = $37.50
The widget would render with two lines
- Line item 1 for $7.50
- Line item 2 for $37.50
Example 2 Order Submit:
Total Order to Teak would contain two line items- Ticket 1 for $7.50
- Ticket Cost = $5.00
- Taxes & Fees (16.7% of taxes and fees) = $2.50
- Ticket 2 for $37.50
- Ticket Cost = $25.00
- Taxes & Fees (83% of taxes and fees) = $12.50
Example 2 - Order Items API Example:
"items": [
{
"reference_number": "merchant_order_number",
"name": "Ticket 1",
"cost": 7.50, – including all taxes and fees from the merchant
},
{
"reference_number": "merchant_order_number",
"name": "Ticket 2",
"description": "item_description",
"cost": 37.50, – including taxes and fees
}
]Code Examples
Please see the below examples in JavaScript and Python for handling split taxes and fees.
JavaScript
/**
* @summary calculates the cart subtotal and splits fees and taxes proportionally for each line item
*
* PARAMS
* ============================================================================
* @param {{cost: number; fee: number}[]} items - cart line items, including each cost and a fee specific to each item
* @param {number} cartLevelFees - a total fee not specific to any individual item
*/
export const handleSplitFeesAndTaxes = (items, cartLevelFees) => {
// the subtotal of each item, including item-level fees
const itemSubtotals = items.map((item) => item.cost + item.fee);
// the subtotal of the cart
const cartSubtotal = itemSubtotals.reduce((acc, item) => acc + item, 0);
// taxes at a fixed value of $5 for testing purposes
const taxes = 5;
// total taxes and fees that should be split proportionally for each item
const cartLevelFeesAndTaxes = cartLevelFees + taxes;
const itemTotals = itemSubtotals.map((item) => {
// determine the proportion of fees and taxes that should be associated to each item
const proportionOfFeesAndTaxes = item / cartSubtotal;
// return the sum the item value and proportion of cart-level fees
return +(item + cartLevelFeesAndTaxes * proportionOfFeesAndTaxes).toFixed(
2,
);
});
// sum the values of the item totals to check for rounding discrepancies
const itemTotalsSum = itemTotals.reduce((acc, item) => acc + item, 0);
// determine the value of the rounding difference, if any
let diff =
+(itemTotalsSum - (cartSubtotal + cartLevelFeesAndTaxes)).toFixed(2) * 100;
// if no rounding discrepancy, return the item totals
if (diff === 0) return itemTotals;
const validatedItemTotals = itemTotals.map((item) => {
// if there's no more discrepancy to offset, return the initial item cost
if (diff === 0) return item;
// decrement the rounding difference
diff = diff - 1;
// reduce the item cost by the smallest increment, accounting for non-decimal currencies
return ((item * 100) - 1) / 100;
});
return validatedItemTotals;
};Python
def handle_split_fees_and_taxes(items, cart_level_fees):
"""
@summary: calculates the cart subtotal and splits fees and taxes proportionally for each line item.
@params:
items (list of dicts): Each item is a dictionary with keys 'cost' and 'fee'.
cart_level_fees (float): A total fee not specific to any individual item.
"""
# the subtotal of each item, including item-level fees
item_subtotals = [
item.get('cost', 0) + item.get('fee', 0) for item in items
]
# the subtotal of the cart
cart_subtotal = sum(item_subtotals)
# taxes at a fixed value of $5 for testing purposes
taxes = 5
# total taxes and fees that should be split proportionally for each item
cart_level_fees_and_taxes = cart_level_fees + taxes
item_totals = []
for item in item_subtotals:
# determine the proportion of fees and taxes that should be associated with each item
proportion_of_fees_and_taxes = item / cart_subtotal
# return the sum of the item value and proportion of cart-level fees
item_total = round(
item + cart_level_fees_and_taxes * proportion_of_fees_and_taxes, 2)
item_totals.append(item_total)
# sum the values of the item totals to check for rounding discrepancies
item_totals_sum = sum(item_totals)
# determine the value of the rounding difference, if any
diff = round(item_totals_sum -
(cart_subtotal + cart_level_fees_and_taxes), 2) * 100
# if no rounding discrepancy, return the item totals
if diff == 0:
return item_totals
validated_item_totals = []
for item in item_totals:
# if there's no more discrepancy to offset, return the initial item cost
if diff == 0:
validated_item_totals.append(item)
continue
# decrement the rounding difference
diff -= 1
# reduce the item cost by the smallest increment, accounting for non-decimal currencies
validated_item_totals.append(round((item * 100 - 1) / 100, 2))
return validated_item_totalsUpdated 8 months ago
