GuidesReference Endpoints
Guides

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 up

In 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_totals