Greasy Fork 支持简体中文。

DoorDash fees buster

Display DoorDash fees in a clearer way.

// ==UserScript==
// @name         DoorDash fees buster
// @namespace    http://tampermonkey.net/
// @version      2025-03-06.2
// @description  Display DoorDash fees in a clearer way.
// @author       Somebody
// @match        https://www.doordash.com/consumer/checkout/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=doordash.com
// @grant        none
// @run-at       document-end
// @license      GNU GPLv3
// ==/UserScript==

(function () {
    'use strict';

    const D = window.document;
    const GLOBAL_TIMEOUT = 10 * 1000;

    const KNOWN_DELIVERY_MARKUP_BUSINESS_NAMES = [
        'Popeyes Louisiana Kitchen',
        'Jollibee',
        'KFC',
        'Qdoba Mexican Eats',
    ];

    const KNOWN_PICKUP_MARKUP_BUSINESS_NAMES = [
        'Qdoba Mexican Eats',
    ];

    const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

    const createElementWithInnerTextAndStyle = (ElementName, innerText, style = {}) => {
        const element = D.createElement(ElementName);
        if (typeof innerText === 'string') {
            element.innerText = innerText;
        }
        Object.assign(
            element.style,
            style
        );
        return element;
    };

    const createToastWithText = (() => {
        const timeoutIdToElementMap = new Map();
        const showToast = (text, disappearDelayInMs = 1000) => {
            const defaultStyle = {
                position: 'fixed',
                top: '10vh',
                zIndex: 9999,
                transform: 'translateX(-50%)',
                left: '50%',
                transition: 'all 0.25s ease',
                borderRadius: '2em',
                backgroundColor: 'black',
                padding: '0.5em 2em',
                color: 'white',
                textAlign: 'center',
                opacity: '0',
            };
            const toast = createElementWithInnerTextAndStyle('div', text, defaultStyle);
            D.body.append(toast);
            const timeoutId = setTimeout(
                () => {
                    timeoutIdToElementMap.delete(timeoutId);
                    D.body.removeChild(toast);
                },
                disappearDelayInMs
            );
            timeoutIdToElementMap.set(timeoutId, toast);
            setTimeout(
                () => {
                    Object.assign(
                        toast.style, {
                        opacity: '100%'
                    }
                    );
                },
                0
            );
            setTimeout(
                () => {
                    Object.assign(
                        toast.style, {
                        opacity: '0'
                    }
                    );
                },
                disappearDelayInMs - 250
            );
        };
        return showToast;
    })();

    const getInitialServiceFee = () => {
        // This is no longer true if items are modified after page load
        const keyword = 'Service Fee: ';
        let line = Array.from(D.querySelectorAll('script')).find(e => e.innerText.includes(keyword))?.innerText;
        line = line?.substring(line?.indexOf(keyword));
        line = line?.substring(0, line?.indexOf('\"'));
        return line;
    };

    const getInitialEstimatedTax = () => {
        // This is no longer true if items are modified after page load
        const keyword = 'Estimated Tax: ';
        let line = Array.from(D.querySelectorAll('script')).find(e => e.innerText.includes(keyword))?.innerText;
        line = line?.substring(line?.indexOf(keyword));
        line = line?.substring(0, line?.indexOf('\"'));
        return line;
    };

    const getInitialBagFee = () => {
        // This is no longer true if items are modified after page load
        const keyword = 'Bag Fee: ';
        let line = Array.from(D.querySelectorAll('script')).find(e => e.innerText.includes(keyword))?.innerText;
        line = line?.substring(line?.indexOf(keyword));
        line = line?.substring(0, line?.indexOf('\"'));
        return line;
    };

    const getInitialBusinessName = () => {
        const businessNameElement = Array.from(document.querySelectorAll('span')).find(spenElement => ['Your cart from', 'Order From', 'Checkout'].includes(spenElement.innerText))?.nextSibling;
        if (businessNameElement instanceof HTMLElement) {
            return Array.from(businessNameElement.querySelectorAll('span')).find(spenElement => typeof spenElement.innerText === 'string' && spenElement.innerText.length > 0)?.innerText;
        }
    };

    const triggerHover = (element) => {
        if (element instanceof HTMLElement) {
            // Create the mouseenter event
            const mouseEnterEvent = new MouseEvent('mouseenter', {
                bubbles: true,
                cancelable: true,
                view: window,
            });

            // Create the mouseover event
            const mouseOverEvent = new MouseEvent('mouseover', {
                bubbles: true,
                cancelable: true,
                view: window,
            });

            // Create the mouseleave event
            const mouseLeaveEvent = new MouseEvent('mouseleave', {
                bubbles: true,
                cancelable: true,
                view: window,
            });

            // Dispatch mouseenter and mouseover events
            element.dispatchEvent(mouseEnterEvent);
            element.dispatchEvent(mouseOverEvent);

            const triggerHoverEnd = () => {
                element.dispatchEvent(mouseLeaveEvent);
            };
            return triggerHoverEnd;
        }
    };

    const findLineItemsElement = () => D.querySelector('[data-testid="LineItems"]');

    // const findStackChildrenElement = () => D.querySelector('[class^="StackChildren"]');

    // const findTooltipElements = () => D.querySelectorAll('[role="tooltip"]');

    const findEstimatedTaxElement = () => D.querySelector('[data-testid="Estimated Tax"]');

    const findFeesAndEstimatedTaxElement = () => D.querySelector('[data-testid="Fees & Estimated Tax"]');

    const findFeesAndEstimatedTaxTooltipTriggerElement = () => findFeesAndEstimatedTaxElement()?.querySelector('button');

    const findServiceFeeElement = () => D.querySelector('[data-testid="Service Fee"]');

    const findBagFeeElement = () => D.querySelector('[data-testid="Bag Fee"]');

    const findSubtotalElement = () => D.querySelector('[data-testid="Subtotal"]');

    const findDeliveryFeeElement = () => D.querySelector('[data-testid="Delivery Fee"]');

    const findLongDistanceFeeElement = () => D.querySelector('[data-testid="Long Distance Fee"]');

    const findDasherTipElement = () => D.querySelector('[data-testid="Dasher Tip"]');

    const findTipTheStuffElement = () => D.querySelector('[data-testid="Tip the staff"]');

    const findTotalElement = () => D.querySelector('[data-testid="Total"]');

    const findCartTotalElement = () => D.querySelector('[data-anchor-id="OrderCartTotal"]');

    const findPopupLayerElement = () => D.querySelector('[data-testid="LAYER-MANAGER-POPOVER_CONTENT"]');

    const findFeesAndEstimatedTaxTotalElement = () => {
        const feesAndEstimatedTaxElement = findFeesAndEstimatedTaxElement();
        if (feesAndEstimatedTaxElement instanceof HTMLElement) {
            const totalElement = Array.from(feesAndEstimatedTaxElement.querySelectorAll('span')).find(spanElement => spanElement.innerText.includes('$') && !window.getComputedStyle(spanElement).textDecoration.includes('line-through'));
            return totalElement;
        }
        return undefined;
    };

    const findSubtotalTotalElement = () => {
        const subtotalElement = findSubtotalElement();
        if (subtotalElement instanceof HTMLElement) {
            const totalElement = Array.from(subtotalElement.querySelectorAll('span')).find(spanElement => spanElement.innerText.includes('$') && !window.getComputedStyle(spanElement).textDecoration.includes('line-through'));
            return totalElement;
        }
        return undefined;
    };

    const findDoorDashCreditsTotalElement = () => {
        const lineItemsElement = D.querySelector('[data-testid="LineItems"]');
        if (lineItemsElement instanceof HTMLElement) {
            const doorDashCreditsDescriptionElement = Array.from(lineItemsElement.querySelectorAll('span')).find(spanElement => spanElement.innerText.includes('DoorDash Credits'));
            const doorDashCreditsTotalElement = doorDashCreditsDescriptionElement?.parentNode?.lastChild;
            return doorDashCreditsTotalElement;
        }
        return undefined;
    };

    const findDasherTipTotalElement = () => {
        const dasherTipElement = findDasherTipElement();
        if (dasherTipElement instanceof HTMLElement) {
            const doorDashTipTotalElement = Array.from(dasherTipElement.querySelectorAll('span')).find(spanElement => spanElement.innerText.includes('$'));
            return doorDashTipTotalElement;
        }
        return undefined;
    };

    const findTipTheStuffTotalElement = () => {
        const tipTheStuffElement = findTipTheStuffElement();
        if (tipTheStuffElement instanceof HTMLElement) {
            const tipTheStuffTotalElement = Array.from(tipTheStuffElement.querySelectorAll('span')).find(spanElement => spanElement.innerText.includes('$'));
            return tipTheStuffTotalElement;
        }
        return undefined;
    };

    const findDeliveryFeeElementTotalElement = () => {
        const deliveryFeeElement = findDeliveryFeeElement();
        if (deliveryFeeElement instanceof HTMLElement) {
            const deliveryFeeTotalElement = Array.from(deliveryFeeElement.querySelectorAll('span')).find(spanElement => spanElement.innerText.includes('$') && !window.getComputedStyle(spanElement).textDecoration.includes('line-through'));
            return deliveryFeeTotalElement;
        }
        return undefined;
    };

    const findLongDistanceFeeTotalElement = () => {
        const longDistanceFeeElement = findLongDistanceFeeElement();
        if (longDistanceFeeElement instanceof HTMLElement) {
            const longDistanceFeeTotalElement = Array.from(longDistanceFeeElement.querySelectorAll('span')).find(spanElement => spanElement.innerText.includes('$') && !window.getComputedStyle(spanElement).textDecoration.includes('line-through'));
            return longDistanceFeeTotalElement;
        }
        return undefined;
    };

    const findServiceFeeTotalElement = () => {
        const serviceFeeElement = findServiceFeeElement();
        if (serviceFeeElement instanceof HTMLElement) {
            const serviceFeeTotalElement = Array.from(serviceFeeElement.querySelectorAll('span')).find(spanElement => spanElement.innerText.includes('$') && !window.getComputedStyle(spanElement).textDecoration.includes('line-through'));
            return serviceFeeTotalElement;
        }
        return undefined;
    };

    const findEstimatedTaxTotalElement = () => {
        const estimatedTaxElement = findEstimatedTaxElement();
        if (estimatedTaxElement instanceof HTMLElement) {
            const estimatedTaxTotalElement = Array.from(estimatedTaxElement.querySelectorAll('span')).find(spanElement => spanElement.innerText.includes('$') && !window.getComputedStyle(spanElement).textDecoration.includes('line-through'));
            return estimatedTaxTotalElement;
        }
        return undefined;
    };

    const findBagFeeTotalElement = () => {
        const bagFeeElement = findBagFeeElement();
        if (bagFeeElement instanceof HTMLElement) {
            const bagFeeTotalElement = Array.from(bagFeeElement.querySelectorAll('span')).find(spanElement => spanElement.innerText.includes('$') && !window.getComputedStyle(spanElement).textDecoration.includes('line-through'));
            return bagFeeTotalElement;
        }
        return undefined;
    };

    const findTooltipByKeywordsWithObserver = (keywords) => new Promise((resolve, reject) => {
        const popupLayerElement = findPopupLayerElement();
        if (popupLayerElement instanceof HTMLElement) {
            const timeoutId = setTimeout(() => {
                createToastWithText('Unable to find target popup element before timeout');
                reject(new Error('unable to find target popup element before timeout'));
                observer.disconnect();
            }, GLOBAL_TIMEOUT);
            const observer = new MutationObserver(async (mutationsList, observer) => {
                try {
                    // Iterate over all mutations
                    loop:
                    for (let mutation of mutationsList) {
                        // Check if nodes were added to the DOM
                        if (mutation.type === 'childList') {
                            // Look for the element with the specific keyword
                            for (const addedNode of mutation.addedNodes) {
                                if (keywords.every(keyword => addedNode.innerText?.includes(keyword))) {
                                    // disconnect the observer once the element is found
                                    observer.disconnect();
                                    clearTimeout(timeoutId);
                                    resolve(addedNode);
                                    break loop;
                                }
                            }
                        }
                    }
                } catch (err) {
                    clearTimeout(timeoutId);
                    createToastWithText('Unable to find tooltip due to an error');
                    console.error(err);
                    reject(err);
                }
            });

            // Configure the observer to look for added nodes in the popup layer
            observer.observe(popupLayerElement, {
                childList: true, // Look for added/removed child nodes
                subtree: true, // Include all descendants in the search
            });
        } else {
            reject(new Error('unable to find popup layer element'));
        }
    });

    const getFeesAndTaxFromTooltip = async () => {
        const targetTooltipElementPromise = findTooltipByKeywordsWithObserver(['Service Fee', 'Estimated Tax']);
        const triggerHoverEnd = triggerHover(findFeesAndEstimatedTaxTooltipTriggerElement());
        const targetTooltipElement = await targetTooltipElementPromise;
        const targetTooltipElementInnerText = targetTooltipElement?.innerText;
        triggerHoverEnd?.();
        const targetTooltipElementInnerTextLines = targetTooltipElementInnerText?.split('\n')?.map(line => line.trim());
        const serviceFee = targetTooltipElementInnerTextLines?.find(line => line.startsWith('Service Fee'));
        const estimatedTax = targetTooltipElementInnerTextLines?.find(line => line.startsWith('Estimated Tax'));
        const bagFee = targetTooltipElementInnerTextLines?.find(line => line.startsWith('Bag Fee')); // Very innovative, what else will they come up with?
        return [serviceFee, estimatedTax, bagFee];
    };

    const getNumberOnly = (text, fallback = '0') => {
        const decimalNumberRegExp = new RegExp(/(\d*\.)?\d+/);
        if (typeof text !== 'string') {
            if (typeof fallback === 'undefined') {
                throw new TypeError('text must be string');
            }
            return fallback;
        }
        // Find the last index of $, to get only the original price
        const originalNumberValue = text.substring(text.lastIndexOf('$'));
        const numberPart = originalNumberValue.match(decimalNumberRegExp)?.[0];
        return numberPart;
    };

    const getFeesAndTaxFromTooltipMemorized = (() => {
        let serviceFee = 'Service Fee: $0.00';
        let estimatedTax = 'Estimated Tax: $0.00';
        let bagFee = 'Bag Fee: $0.00';
        let isFirstRun = true;
        return async () => {
            const serviceFeeAndEstimatedTax = findFeesAndEstimatedTaxTotalElement()?.innerText;
            // Be careful about float point number precision error
            if (Math.round(parseFloat(getNumberOnly(serviceFeeAndEstimatedTax)) * 100) !== Math.round(parseFloat(getNumberOnly(serviceFee)) * 100) + Math.round(parseFloat(getNumberOnly(estimatedTax)) * 100) + Math.round(parseFloat(getNumberOnly(bagFee)) * 100)) {
                if (isFirstRun) {
                    [serviceFee, estimatedTax, bagFee] = [getInitialServiceFee(), getInitialEstimatedTax(), getInitialBagFee()];
                    isFirstRun = false;
                    if (typeof serviceFee === 'undefined' || typeof estimatedTax === 'undefined') {
                        [serviceFee, estimatedTax, bagFee] = await getFeesAndTaxFromTooltip();
                    }
                } else {
                    [serviceFee, estimatedTax, bagFee] = await getFeesAndTaxFromTooltip();
                }
            }
            return [serviceFee, estimatedTax, bagFee];
        };
    })();

    const getTotalCost = () => {
        const cartTotalElement = findCartTotalElement();
        const cartTotal = parseFloat(getNumberOnly(cartTotalElement.innerText));
        let totalCost = cartTotal;
        const doorDashCreditsTotalElement = findDoorDashCreditsTotalElement();
        if (doorDashCreditsTotalElement instanceof HTMLElement) {
            const doorDashCreditsTotal = Math.abs(parseFloat(getNumberOnly(doorDashCreditsTotalElement.innerText)));
            totalCost += doorDashCreditsTotal;
        }
        return totalCost;
    };

    const insertNewRow = (description, total, insertAfterElement, testid = '') => {
        const newRowElement = findSubtotalElement().cloneNode(true);
        newRowElement.dataset.testid = testid;
        const descriptionElement = Array.from(newRowElement.querySelectorAll('span')).find(spanElement => spanElement.innerText.includes('Subtotal'));
        descriptionElement.innerText = description;
        const totalElement = Array.from(newRowElement.querySelectorAll('span')).find(spanElement => spanElement.innerText.includes('$'));
        totalElement.innerText = total;
        if (insertAfterElement.nextSibling) {
            insertAfterElement.parentNode.insertBefore(newRowElement, insertAfterElement.nextSibling);
        } else {
            insertAfterElement.parentNode.appendChild(newRowElement);
        }
        return newRowElement;
    };

    const calcCostPercentage = (cost) => {
        const subtotalTotalElement = findSubtotalTotalElement();
        const subtotal = parseFloat(getNumberOnly(subtotalTotalElement.innerText));
        const percentage = Math.ceil(cost * 10000 / subtotal) / 100;
        return percentage;
    };

    const setColorRecursively = (parentElement, color = 'red') => {
        Array.from(parentElement.querySelectorAll('span, div')).forEach(spanOrDivElement => {
            Object.assign(
                spanOrDivElement.style,
                {
                    color: color,
                },
            );
        });
    };

    const updateLineItems = async () => {
        const estimatedTaxElement = findEstimatedTaxElement();
        const feesAndEstimatedTaxElement = findFeesAndEstimatedTaxElement();
        // Only perform remaining logic if we can find fees and estimated tax element
        // Usually this means we are getting the order delivered
        // After recent update doordash is showing service fee and estimated tax separately already?
        // Maybe it is just an A/B test hehe
        if (feesAndEstimatedTaxElement instanceof HTMLElement) {
            const [serviceFee, estimatedTax, bagFee] = await getFeesAndTaxFromTooltipMemorized();
            if (feesAndEstimatedTaxElement instanceof HTMLElement) {
                const serviceFeeElementTestid = 'Service Fee';
                D.querySelector(`[data-testid="${serviceFeeElementTestid}"]`)?.remove();
                const serviceFeeElement = insertNewRow(serviceFee.split(': ')[0], `$${getNumberOnly(serviceFee)}`, feesAndEstimatedTaxElement, serviceFeeElementTestid);
                setColorRecursively(serviceFeeElement, 'red');
                const estimatedTaxElementTestid = 'Estimated Tax';
                D.querySelector(`[data-testid="${estimatedTaxElementTestid}"]`)?.remove();
                const estimatedTaxElement = insertNewRow(estimatedTax.split(': ')[0], `$${getNumberOnly(estimatedTax)}`, serviceFeeElement, estimatedTaxElementTestid);
                const bagFeeElementTestid = 'Bag Fee';
                D.querySelector(`[data-testid="${bagFeeElementTestid}"]`)?.remove();
                if (typeof bagFee === 'string' && bagFee.length > 0) {
                    const bagFeeElement = insertNewRow(bagFee.split(': ')[0], `$${getNumberOnly(bagFee)}`, estimatedTaxElement, bagFeeElementTestid);
                    setColorRecursively(bagFeeElement, 'red');
                }

                setColorRecursively(feesAndEstimatedTaxElement, 'darkgray');
            }

            const totalElement = findTotalElement();
            if (totalElement instanceof HTMLElement) {
                const businessName = getInitialBusinessName();
                const deliveryMarkUpWarningTestid = 'Delivery Markup Warning';
                D.querySelector(`[data-testid="${deliveryMarkUpWarningTestid}"]`)?.remove();
                const pickupMarkUpWarningTestid = 'Pickup Markup Warning';
                D.querySelector(`[data-testid="${pickupMarkUpWarningTestid}"]`)?.remove();
                if (KNOWN_DELIVERY_MARKUP_BUSINESS_NAMES.includes(businessName)) {
                    const deliveryMarkUpWarningElement = insertNewRow('This business typically charges higher prices for DoorDash delivery, so the pre-tax subtotal would be lower if purchased in-store.\n(Hint: Switch to the pickup and take a look?)', ' ', totalElement, deliveryMarkUpWarningTestid);
                    setColorRecursively(deliveryMarkUpWarningElement, 'red');
                }
            }
        } else if (estimatedTaxElement instanceof HTMLElement) {
            // We only resort to use this logic branch when fees and estimated tax element is not found
            // Be careful, only use this when feesAndEstimatedTaxElement is not found, as we are adding estimated tax element ourselves when the current order is for delivery
            // Usually this means we are getting the order for pickup
            const totalElement = findTotalElement();
            if (totalElement instanceof HTMLElement) {
                const businessName = getInitialBusinessName();
                const deliveryMarkUpWarningTestid = 'Delivery Markup Warning';
                D.querySelector(`[data-testid="${deliveryMarkUpWarningTestid}"]`)?.remove();
                const pickupMarkUpWarningTestid = 'Pickup Markup Warning';
                D.querySelector(`[data-testid="${pickupMarkUpWarningTestid}"]`)?.remove();
                if (KNOWN_PICKUP_MARKUP_BUSINESS_NAMES.includes(businessName)) {
                    const pickupMarkUpWarningElement = insertNewRow('This business typically charges higher prices for DoorDash pickup, so the pre-tax subtotal would be lower if purchased in-store.\n(Hint: Go to the business official website and take a look?)', ' ', totalElement, pickupMarkUpWarningTestid);
                    setColorRecursively(pickupMarkUpWarningElement, 'red');
                }
            }
        } else {
            // Not sure what is going on but we don't try anything smart
            restoreLineItems();
        }

        // Calc percentages, the fun part
        const deliveryFeeElement = findDeliveryFeeElement();
        if (deliveryFeeElement instanceof HTMLElement) {
            const deliveryFeeTotalElement = findDeliveryFeeElementTotalElement();
            deliveryFeeTotalElement.innerText = `${calcCostPercentage(parseFloat(getNumberOnly(deliveryFeeTotalElement.innerText)))}% = $${getNumberOnly(deliveryFeeTotalElement.innerText)}`;
            setColorRecursively(deliveryFeeElement, 'red');
        }

        const longDistanceFeeElement = findLongDistanceFeeElement();
        if (longDistanceFeeElement instanceof HTMLElement) {
            const longDistanceFeeTotalElement = findLongDistanceFeeTotalElement();
            longDistanceFeeTotalElement.innerText = `${calcCostPercentage(parseFloat(getNumberOnly(longDistanceFeeElement.innerText)))}% = $${getNumberOnly(longDistanceFeeElement.innerText)}`;
            setColorRecursively(longDistanceFeeElement, 'red');
        }

        const serviceFeeElement = findServiceFeeElement();
        if (serviceFeeElement instanceof HTMLElement) {
            const serviceFeeTotalElement = findServiceFeeTotalElement();
            serviceFeeTotalElement.innerText = `${calcCostPercentage(parseFloat(getNumberOnly(serviceFeeTotalElement.innerText)))}% = $${getNumberOnly(serviceFeeTotalElement.innerText)}`;
            setColorRecursively(serviceFeeTotalElement, 'red');
        }

        const bagFeeElement = findBagFeeElement();
        if (bagFeeElement instanceof HTMLElement) {
            const bagFeeTotalElement = findBagFeeTotalElement();
            bagFeeTotalElement.innerText = `${calcCostPercentage(parseFloat(getNumberOnly(bagFeeTotalElement.innerText)))}% = $${getNumberOnly(bagFeeTotalElement.innerText)}`;
            setColorRecursively(bagFeeTotalElement, 'red');
        }

        // Find current estimated tax element, it might already be there, it might be inserted by us
        const currentEstimatedTaxElement = findEstimatedTaxElement();
        if (currentEstimatedTaxElement instanceof HTMLElement) {
            const currentEstimatedTaxTotalElement = findEstimatedTaxTotalElement();
            currentEstimatedTaxTotalElement.innerText = `${calcCostPercentage(parseFloat(getNumberOnly(currentEstimatedTaxTotalElement.innerText)))}% = $${getNumberOnly(currentEstimatedTaxTotalElement.innerText)}`;
        }

        const dasherTipElement = findDasherTipElement();
        if (dasherTipElement instanceof HTMLElement) {
            const dasherTipTotalElement = findDasherTipTotalElement();
            dasherTipTotalElement.innerText = `${calcCostPercentage(parseFloat(getNumberOnly(dasherTipTotalElement.innerText)))}% = $${getNumberOnly(dasherTipTotalElement.innerText)}`;
            setColorRecursively(dasherTipElement, 'red');
        }

        const tipTheStuffElement = findTipTheStuffElement();
        if (tipTheStuffElement instanceof HTMLElement) {
            const tipTheStuffTotalElement = findTipTheStuffTotalElement();
            tipTheStuffTotalElement.innerText = `${calcCostPercentage(parseFloat(getNumberOnly(tipTheStuffTotalElement.innerText)))}% = $${getNumberOnly(tipTheStuffTotalElement.innerText)}`;
            setColorRecursively(tipTheStuffElement, 'red');
        }

        const totalCost = getTotalCost();
        const cartTotalElement = findCartTotalElement();
        if (!Number.isNaN(totalCost) && cartTotalElement instanceof HTMLElement && currentEstimatedTaxElement instanceof HTMLElement) {
            const estimatedTax = getNumberOnly(currentEstimatedTaxElement.innerText);
            cartTotalElement.innerText = `${calcCostPercentage(totalCost - parseFloat(estimatedTax))}% (Pretax) / ${calcCostPercentage(totalCost)}% = $${getNumberOnly(cartTotalElement.innerText)}`;
        }
    };

    const restoreLineItems = () => {
        // const feesElementTestid = 'Service Fee';
        // D.querySelector(`[data-testid="${feesElementTestid}"]`)?.remove();
        // const estimatedTaxElementTestid = 'Estimated Tax';
        // D.querySelector(`[data-testid="${estimatedTaxElementTestid}"]`)?.remove();
        const feesAndEstimatedTaxElement = findFeesAndEstimatedTaxElement();
        if (feesAndEstimatedTaxElement instanceof HTMLElement) {
            setColorRecursively(feesAndEstimatedTaxElement, 'initial');
        }

        const deliveryFeeElement = findDeliveryFeeElement();
        if (deliveryFeeElement instanceof HTMLElement) {
            const deliveryFeeTotalElement = findDeliveryFeeElementTotalElement();
            deliveryFeeTotalElement.innerText = `$${getNumberOnly(deliveryFeeTotalElement.innerText)}`;
            setColorRecursively(deliveryFeeElement, 'initial');
        }

        const longDistanceFeeElement = findLongDistanceFeeElement();
        if (longDistanceFeeElement instanceof HTMLElement) {
            const longDistanceFeeElement = findLongDistanceFeeTotalElement();
            longDistanceFeeElement.innerText = `$${getNumberOnly(longDistanceFeeElement.innerText)}`;
            setColorRecursively(longDistanceFeeElement, 'initial');
        }

        const dasherTipElement = findDasherTipElement();
        if (dasherTipElement instanceof HTMLElement) {
            const dasherTipTotalElement = findDasherTipTotalElement();
            dasherTipTotalElement.innerText = `$${getNumberOnly(dasherTipTotalElement.innerText)}`;
            setColorRecursively(dasherTipElement, 'initial');
        }

        const totalElement = findTotalElement();
        if (totalElement instanceof HTMLElement) {
            const cartTotalElement = findCartTotalElement();
            cartTotalElement.innerText = `$${getNumberOnly(cartTotalElement.innerText)}`;
            const deliveryMarkUpWarningTestid = 'Delivery Markup Warning';
            D.querySelector(`[data-testid="${deliveryMarkUpWarningTestid}"]`)?.remove();
            const pickupMarkUpWarningTestid = 'Pickup Markup Warning';
            D.querySelector(`[data-testid="${pickupMarkUpWarningTestid}"]`)?.remove();
        }
    };

    const main = () => {
        const timeoutId = setTimeout(() => {
            createToastWithText('Failed to display Fees & Estimated Tax before timeout');
        }, GLOBAL_TIMEOUT);
        const observer = new MutationObserver(async (mutationsList, observer) => {
            try {
                // Iterate over all mutations
                for (let mutation of mutationsList) {
                    // Check if nodes were added to the DOM
                    if (mutation.type === 'childList') {
                        // Look for the element with the specific data-testid
                        const lineItemsElement = findLineItemsElement();
                        if (lineItemsElement instanceof HTMLElement) {
                            // disconnect the observer once the element is found
                            observer.disconnect();
                            try {
                                await updateLineItems();
                                clearTimeout(timeoutId);
                                createToastWithText('Fees & Estimated Tax rate calculated');
                            } catch (err) {
                                console.error(err);
                                restoreLineItems();
                            }
                            
                            // Create new observer to observe changes within lineItemsElement, such as changes to tip etc.
                            const lineItemsElementObserverOptions = {
                                childList: true, // Look for added/removed child nodes
                                subtree: true, // Include all descendants in the search
                                characterData: true,
                            };

                            const lineItemsObserver = new MutationObserver(async (lineItemsMutationsList, lineItemsObserver) => {
                                // Avoid recursive calls
                                lineItemsObserver.disconnect();
                                try {
                                    await updateLineItems();
                                } catch (err) {
                                    console.error(err);
                                    restoreLineItems();
                                }
                                lineItemsObserver.observe(lineItemsElement, lineItemsElementObserverOptions);
                            });

                            lineItemsObserver.observe(lineItemsElement, lineItemsElementObserverOptions);

                            break;
                        }
                    }
                }
            } catch (err) {
                clearTimeout(timeoutId);
                createToastWithText('Failed to display Fees & Estimated Tax due to an error');
                console.error(err);
            }
        });

        // Configure the observer to look for added nodes in the entire document
        observer.observe(D.body, {
            childList: true, // Look for added/removed child nodes
            subtree: true, // Include all descendants in the search
        });
    };

    main();
})();