# easy-invoice/2.4.0/includes/Helpers/TemplateTextHelper.php

Easy Invoice – Invoice Generator, PDF Quotes &amp; Payments, version 2.4.0. 601 lines.

- Page: https://pluginprobe.com/plugins/easy-invoice/2.4.0/code/includes/Helpers/TemplateTextHelper.php
- Raw: https://pluginprobe.com/plugins/easy-invoice/2.4.0/raw/includes/Helpers/TemplateTextHelper.php
- Modified: 2026-09-15T12:31:20+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/easy-invoice/2.4.0/code/includes/Helpers/TemplateTextHelper.php#L10-L20`.

```php
<?php
/**
 * Template Text Helper
 * 
 * Centralized helper for retrieving text settings used in invoice and quote templates.
 * This prevents duplication and makes text management easier.
 * 
 * @package EasyInvoice
 * @since 2.0.0
 */

namespace EasyInvoice\Helpers;

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

class TemplateTextHelper {
    
    /**
     * The footer printed under a document: the document's own footer text,
     * else the default from Settings, then the `easy_invoice_pdf_footer_html`
     * filter (Pro's White-Label addon replaces it there).
     *
     * @param object $document Invoice or Quote model.
     * @param string $type     'invoice' or 'quote'.
     */
    public static function footerText($document, string $type = 'invoice'): string {
        $text = is_callable([$document, 'getFooterText']) ? (string) $document->getFooterText() : '';
        if ('' === trim($text)) {
            $text = 'quote' === $type
                ? \EasyInvoice\Controllers\SettingsController::getQuoteFooterText()
                : \EasyInvoice\Controllers\SettingsController::getInvoiceFooterText();
        }
        /**
         * Filter the footer of a rendered document (page, PDF, email attachment).
         *
         * @param string $text     Footer text or HTML.
         * @param object $document The document.
         * @param string $type     'invoice' or 'quote'.
         */
        return (string) apply_filters('easy_invoice_pdf_footer_html', $text, $document, $type);
    }

    /**
     * Is this document a credit note? Credit notes are stored as their own
     * post type but render through the invoice designs.
     *
     * @param object $document Invoice model.
     */
    public static function isCreditNote($document): bool {
        return is_object($document) && is_callable([$document, 'getId'])
            && get_post_type((int) $document->getId()) === \EasyInvoice\Constants\PostTypes::EASY_INVOICE_CREDIT_NOTE_POST_TYPE;
    }

    /**
     * The labels a design should use for this particular document: the
     * invoice labels as saved, or the credit-note wording when the document
     * is a credit note (a credit note is not a bill — no due date, no amount
     * to pay, no payment terms).
     *
     * @param array  $text_settings From getInvoiceTextSettings().
     * @param object $document      Invoice model.
     * @return array
     */
    public static function forDocument(array $text_settings, $document): array {
        if (!self::isCreditNote($document)) {
            return $text_settings;
        }
        $text_settings['invoice']        = __('Credit Note', 'easy-invoice');
        $text_settings['invoice_number'] = __('Credit Note Number', 'easy-invoice');
        $text_settings['invoice_date']   = __('Credit Note Date', 'easy-invoice');
        $text_settings['total']          = __('Total Credited', 'easy-invoice');
        $text_settings['total_due']      = __('Total Credited', 'easy-invoice');
        return $text_settings;
    }

    /**
     * The document kind printed above the title.
     *
     * @param object $document Invoice model.
     */
    public static function documentKind($document): string {
        return self::isCreditNote($document)
            ? __('Credit Note', 'easy-invoice')
            : \EasyInvoice\Controllers\SettingsController::getTextInvoice();
    }

    /**
     * Get all text settings for templates
     * 
     * @return array Array of text settings
     */
    public static function getAllTextSettings(): array {
        return [
            // Invoice specific
            'invoice_number' => \EasyInvoice\Controllers\SettingsController::getTextInvoiceNumber(),
            'invoice_date' => \EasyInvoice\Controllers\SettingsController::getTextInvoiceDate(),
            'due_date' => \EasyInvoice\Controllers\SettingsController::getTextDueDate(),
            'total_due' => \EasyInvoice\Controllers\SettingsController::getTextTotalDue(),
            
            // Quote specific
            'quote_number' => \EasyInvoice\Controllers\SettingsController::getTextQuoteNumber(),
            'quote_date' => \EasyInvoice\Controllers\SettingsController::getTextQuoteDate(),
            'valid_until' => \EasyInvoice\Controllers\SettingsController::getTextValidUntil(),
            
            // Common labels
            'to' => \EasyInvoice\Controllers\SettingsController::getTextTo(),
            'service' => \EasyInvoice\Controllers\SettingsController::getTextService(),
            'qty' => \EasyInvoice\Controllers\SettingsController::getTextQty(),
            'rate_price' => \EasyInvoice\Controllers\SettingsController::getTextRatePrice(),
            'adjust' => \EasyInvoice\Controllers\SettingsController::getTextAdjust(),
            'sub_total' => \EasyInvoice\Controllers\SettingsController::getTextSubTotal(),
            'total' => \EasyInvoice\Controllers\SettingsController::getTextTotal(),
            'line_total' => \EasyInvoice\Controllers\SettingsController::getTextLineTotal(),
            'tax' => \EasyInvoice\Controllers\SettingsController::getTextTax(),
            'discount' => \EasyInvoice\Controllers\SettingsController::getTextDiscount(),
            
            // Button labels
            'print' => \EasyInvoice\Controllers\SettingsController::getTextPrint(),
            'download_pdf' => \EasyInvoice\Controllers\SettingsController::getTextDownloadPdf(),
            'send_email' => \EasyInvoice\Controllers\SettingsController::getTextSendEmail(),
            'pay_now' => \EasyInvoice\Controllers\SettingsController::getTextPayNow(),
            'accept_quote' => \EasyInvoice\Controllers\SettingsController::getTextAcceptQuote(),
            'decline_quote' => \EasyInvoice\Controllers\SettingsController::getTextDeclineQuote(),
            'decline_reason' => \EasyInvoice\Controllers\SettingsController::getTextDeclineReason(),
        ];
    }
    
    /**
     * Get company information from settings
     * 
     * @return array Company information
     */
    public static function getCompanyInfo(): array {
        $info = [
            'name' => get_option('easy_invoice_company_name', 'Your Company Name'),
            'address' => get_option('easy_invoice_company_address', ''),
            'email' => get_option('easy_invoice_company_email', ''),
            'phone' => get_option('easy_invoice_company_phone', ''),
            'website' => get_option('easy_invoice_company_website', ''),
            'logo' => get_option('easy_invoice_company_logo', ''),
            'tax_number' => get_option('easy_invoice_tax_number', '') ?: get_option('easy_invoice_company_vat_number', ''),
        ];
        /**
         * Filter the resolved company info used by invoice/quote templates
         * and PDF generation. The White-Label addon hooks this to replace
         * the logo with its branded header image without forcing every
         * admin to also see the white-label logo on their settings page.
         *
         * @param array<string,string> $info Resolved company info.
         */
        return apply_filters('easy_invoice_template_company_info', $info);
    }
    
    /**
     * Get invoice specific text settings
     * 
     * @return array Invoice text settings
     */
    public static function getInvoiceTextSettings(): array {
        return [
            'invoice_number' => \EasyInvoice\Controllers\SettingsController::getTextInvoiceNumber(),
            'invoice_date' => \EasyInvoice\Controllers\SettingsController::getTextInvoiceDate(),
            'due_date' => \EasyInvoice\Controllers\SettingsController::getTextDueDate(),
            'total_due' => \EasyInvoice\Controllers\SettingsController::getTextTotalDue(),
            'to' => \EasyInvoice\Controllers\SettingsController::getTextTo(),
            'service' => \EasyInvoice\Controllers\SettingsController::getTextService(),
            'qty' => \EasyInvoice\Controllers\SettingsController::getTextQty(),
            'rate_price' => \EasyInvoice\Controllers\SettingsController::getTextRatePrice(),
            'adjust' => \EasyInvoice\Controllers\SettingsController::getTextAdjust(),
            'sub_total' => \EasyInvoice\Controllers\SettingsController::getTextSubTotal(),
            'total' => \EasyInvoice\Controllers\SettingsController::getTextTotal(),
            'line_total' => \EasyInvoice\Controllers\SettingsController::getTextLineTotal(),
            'tax' => \EasyInvoice\Controllers\SettingsController::getTextTax(),
            'discount' => \EasyInvoice\Controllers\SettingsController::getTextDiscount(),
            'print' => \EasyInvoice\Controllers\SettingsController::getTextPrint(),
            'download_pdf' => \EasyInvoice\Controllers\SettingsController::getTextDownloadPdf(),
            'send_email' => \EasyInvoice\Controllers\SettingsController::getTextSendEmail(),
            'pay_now' => \EasyInvoice\Controllers\SettingsController::getTextPayNow(),
        ];
    }
    
    /**
     * Get quote specific text settings
     * 
     * @return array Quote text settings
     */
    public static function getQuoteTextSettings(): array {
        return [
            'quote_number' => \EasyInvoice\Controllers\SettingsController::getTextQuoteNumber(),
            'quote_date' => \EasyInvoice\Controllers\SettingsController::getTextQuoteDate(),
            'valid_until' => \EasyInvoice\Controllers\SettingsController::getTextValidUntil(),
            'to' => \EasyInvoice\Controllers\SettingsController::getTextTo(),
            'service' => \EasyInvoice\Controllers\SettingsController::getTextService(),
            'qty' => \EasyInvoice\Controllers\SettingsController::getTextQty(),
            'rate_price' => \EasyInvoice\Controllers\SettingsController::getTextRatePrice(),
            'adjust' => \EasyInvoice\Controllers\SettingsController::getTextAdjust(),
            'sub_total' => \EasyInvoice\Controllers\SettingsController::getTextSubTotal(),
            'total' => \EasyInvoice\Controllers\SettingsController::getTextTotal(),
            'line_total' => \EasyInvoice\Controllers\SettingsController::getTextLineTotal(),
            'tax' => \EasyInvoice\Controllers\SettingsController::getTextTax(),
            'discount' => \EasyInvoice\Controllers\SettingsController::getTextDiscount(),
            'print' => \EasyInvoice\Controllers\SettingsController::getTextPrint(),
            'download_pdf' => \EasyInvoice\Controllers\SettingsController::getTextDownloadPdf(),
            'send_email' => \EasyInvoice\Controllers\SettingsController::getTextSendEmail(),
            'accept_quote' => \EasyInvoice\Controllers\SettingsController::getTextAcceptQuote(),
            'decline_quote' => \EasyInvoice\Controllers\SettingsController::getTextDeclineQuote(),
            'decline_reason' => \EasyInvoice\Controllers\SettingsController::getTextDeclineReason(),
        ];
    }

    /**
     * Generate "From" section content for templates
     * 
     * @param array $company_info Company information array
     * @param bool $show_label Whether to show the "From" label
     * @param string $style Style variant for the "From" section
     * @return string Generated HTML for "From" section
     */
    public static function generateFromSection($company_info, $show_label = true, $style = 'default'): string {
        $output = '';
        
        switch ($style) {
            case 'minimal':
                // Minimal style - includes company name and details
                $output .= '<div class="company-info-minimal">';
                $output .= '<div class="company-name">' . esc_html($company_info['name']) . '</div>';
                if ($company_info['address']) {
                    $output .= '<div class="company-address">' . nl2br(esc_html($company_info['address'])) . '</div>';
                }
                if ($company_info['email']) {
                    $output .= '<div class="company-email">' . esc_html($company_info['email']) . '</div>';
                }
                if ($company_info['phone']) {
                    $output .= '<div class="company-phone">' . esc_html($company_info['phone']) . '</div>';
                }
                if ($company_info['website']) {
                    $output .= '<div class="company-website">' . esc_html($company_info['website']) . '</div>';
                }
                $output .= '</div>';
                break;
                
            case 'details-only':
                // Details only style - excludes company name (for use with separate company-name div)
                $output .= '<div class="company-info-details-only">';
                if ($company_info['address']) {
                    $output .= '<div class="company-address">' . nl2br(esc_html($company_info['address'])) . '</div>';
                }
                if ($company_info['email']) {
                    $output .= '<div class="company-email">' . esc_html($company_info['email']) . '</div>';
                }
                if ($company_info['phone']) {
                    $output .= '<div class="company-phone">' . esc_html($company_info['phone']) . '</div>';
                }
                if ($company_info['website']) {
                    $output .= '<div class="company-website">' . esc_html($company_info['website']) . '</div>';
                }
                if (!empty($company_info['tax_number'])) {
                    // A tax identifier belongs on the document itself; EU B2B
                    // invoices are not valid without the supplier's VAT ID.
                    $output .= '<div class="company-tax-number">' . esc_html(self::taxIdLabel()) . ' ' . esc_html($company_info['tax_number']) . '</div>';
                }
                $output .= '</div>';
                break;
                
            case 'elegant':
                // Elegant style - subtle design
                $output .= '<div class="company-info-elegant">';
                if ($show_label) {
                    $output .= '<h3 class="from-label">' . esc_html(\EasyInvoice\Controllers\SettingsController::getTextFrom()) . '</h3>';
                }
                $output .= '<div class="company-details">';
                $output .= '<div class="company-name">' . esc_html($company_info['name']) . '</div>';
                if ($company_info['address']) {
                    $output .= '<div class="company-address">' . nl2br(esc_html($company_info['address'])) . '</div>';
                }
                if ($company_info['email']) {
                    $output .= '<div class="company-email">' . esc_html($company_info['email']) . '</div>';
                }
                if ($company_info['phone']) {
                    $output .= '<div class="company-phone">' . esc_html($company_info['phone']) . '</div>';
                }
                if ($company_info['website']) {
                    $output .= '<div class="company-website">' . esc_html($company_info['website']) . '</div>';
                }
                $output .= '</div>';
                $output .= '</div>';
                break;
                
            case 'modern':
                // Modern style - clean and bold
                $output .= '<div class="company-info-modern">';
                if ($show_label) {
                    $output .= '<h2 class="from-label">' . esc_html(\EasyInvoice\Controllers\SettingsController::getTextFrom()) . ':</h2>';
                }
                $output .= '<div class="address-content">';
                $output .= '<div class="company-name">' . esc_html($company_info['name']) . '</div>';
                if ($company_info['address']) {
                    $output .= nl2br(esc_html($company_info['address'])) . '<br>';
                }
                if ($company_info['email']) {
                    $output .= esc_html($company_info['email']) . '<br>';
                }
                if ($company_info['phone']) {
                    $output .= esc_html($company_info['phone']) . '<br>';
                }
                if ($company_info['website']) {
                    $output .= esc_html($company_info['website']);
                }
                $output .= '</div>';
                $output .= '</div>';
                break;
                
            case 'professional':
                // Professional style - structured layout
                $output .= '<div class="company-info-professional">';
                if ($show_label) {
                    $output .= '<h2 class="from-label">' . esc_html(\EasyInvoice\Controllers\SettingsController::getTextFrom()) . ':</h2>';
                }
                $output .= '<div class="address-content">';
                $output .= '<div class="company-name">' . esc_html($company_info['name']) . '</div>';
                if ($company_info['address']) {
                    $output .= nl2br(esc_html($company_info['address'])) . '<br>';
                }
                if ($company_info['email']) {
                    $output .= esc_html($company_info['email']) . '<br>';
                }
                if ($company_info['phone']) {
                    $output .= esc_html($company_info['phone']) . '<br>';
                }
                if ($company_info['website']) {
                    $output .= esc_html($company_info['website']);
                }
                $output .= '</div>';
                $output .= '</div>';
                break;
                
            case 'creative':
                // Creative style - artistic design
                $output .= '<div class="company-info-creative">';
                if ($show_label) {
                    $output .= '<h2 class="from-label">' . esc_html(\EasyInvoice\Controllers\SettingsController::getTextFrom()) . '</h2>';
                }
                $output .= '<div class="address-content">';
                $output .= '<div class="company-name">' . esc_html($company_info['name']) . '</div>';
                if ($company_info['address']) {
                    $output .= nl2br(esc_html($company_info['address'])) . '<br>';
                }
                if ($company_info['email']) {
                    $output .= esc_html($company_info['email']) . '<br>';
                }
                if ($company_info['phone']) {
                    $output .= esc_html($company_info['phone']) . '<br>';
                }
                if ($company_info['website']) {
                    $output .= esc_html($company_info['website']);
                }
                $output .= '</div>';
                $output .= '</div>';
                break;
                
            default:
                // Default style - standard layout
                $output .= '<div class="company-info-default">';
                if ($show_label) {
                    $output .= '<h2 class="from-label">' . esc_html(\EasyInvoice\Controllers\SettingsController::getTextFrom()) . ':</h2>';
                }
                $output .= '<div class="address-content">';
                $output .= '<div class="company-name">' . esc_html($company_info['name']) . '</div>';
                if ($company_info['address']) {
                    $output .= nl2br(esc_html($company_info['address'])) . '<br>';
                }
                if ($company_info['email']) {
                    $output .= esc_html($company_info['email']) . '<br>';
                }
                if ($company_info['phone']) {
                    $output .= esc_html($company_info['phone']) . '<br>';
                }
                if ($company_info['website']) {
                    $output .= esc_html($company_info['website']);
                }
                $output .= '</div>';
                $output .= '</div>';
                break;
        }
        
        return $output;
    }

    /**
     * What is still owed on an invoice: the total less completed payments.
     *
     * @param mixed $invoice Invoice model.
     * @return float
     */
    public static function amountDue($invoice): float {
        // Total less completed payments and credit notes; see InvoiceBalance
        // for the `easy_invoice_amount_due` filter.
        return \EasyInvoice\Services\InvoiceBalance::due($invoice);
    }

    /**
     * Label in front of a tax identifier, on both sides of the document.
     *
     * @return string
     */
    public static function taxIdLabel(): string {
        /**
         * Filter the label shown before tax identifiers on documents.
         *
         * @param string $label Label, "VAT" by default.
         */
        return (string) apply_filters('easy_invoice_tax_id_label', __('VAT', 'easy-invoice'));
    }

    /**
     * The recipient block of a document: who it is for and how to reach them.
     *
     * Business name first, the contact person underneath when the client
     * record has both, then address, email, phone and the client's VAT
     * number. Returns '' when there is nobody to address, so a design can
     * leave the block out instead of printing an empty "To".
     *
     * @param mixed $document Invoice or Quote model.
     * @return string HTML, or '' when the document has no recipient.
     */
    public static function generateClientBlock($document): string {
        if (!is_object($document)) {
            return '';
        }
        $name    = trim((string) (is_callable([$document, 'getCustomerName']) ? $document->getCustomerName() : ''));
        $address = trim((string) (is_callable([$document, 'getCustomerAddress']) ? $document->getCustomerAddress() : ''));
        $email   = trim((string) (is_callable([$document, 'getCustomerEmail']) ? $document->getCustomerEmail() : ''));
        $vat     = trim((string) (is_callable([$document, 'getCustomerVatNumber']) ? $document->getCustomerVatNumber() : ''));
        $contact = '';
        $phone   = '';

        $client_id = is_callable([$document, 'getClientId']) ? (int) $document->getClientId() : 0;
        if ($client_id > 0) {
            $client = (new \EasyInvoice\Repositories\ClientRepository())->find($client_id);
            if ($client) {
                $person = trim((string) $client->getFirstName() . ' ' . (string) $client->getLastName());
                if ('' !== $person && '' !== $name && 0 !== strcasecmp($person, $name)) {
                    $contact = $person;
                }
                $phone = trim((string) $client->getPhone());
                if ('' === $address) {
                    $address = trim((string) $client->getAddress());
                }
                if ('' === $email) {
                    $email = trim((string) $client->getEmail());
                }
            }
        }

        if ('' === $name . $address . $email . $phone . $vat) {
            return '';
        }

        $output = '';
        if ('' !== $name) {
            $output .= '<div class="client-name">' . esc_html($name) . '</div>';
        }
        if ('' !== $contact) {
            $output .= '<div class="client-contact">' . esc_html($contact) . '</div>';
        }
        if ('' !== $address) {
            $output .= '<div class="client-address">' . nl2br(esc_html($address)) . '</div>';
        }
        if ('' !== $email) {
            $output .= '<div class="client-email">' . esc_html($email) . '</div>';
        }
        if ('' !== $phone) {
            $output .= '<div class="client-phone">' . esc_html($phone) . '</div>';
        }
        if ('' !== $vat) {
            $output .= '<div class="client-tax-number">' . esc_html(self::taxIdLabel()) . ' ' . esc_html($vat) . '</div>';
        }

        /**
         * Filter the recipient block rendered by every document design.
         *
         * @param string $output   HTML lines.
         * @param mixed  $document Invoice or Quote model.
         */
        return (string) apply_filters('easy_invoice_client_block_html', $output, $document);
    }

    /**
     * Generate totals section for invoice/quote templates
     * 
     * @param mixed $document Invoice or Quote object
     * @param array $text_settings Text settings array
     * @param mixed $formatter Formatter object
     * @param string $type 'invoice' or 'quote'
     * @return string Generated HTML for totals section
     */
    public static function generateTotalsSection($document, array $text_settings, $formatter, string $type = 'invoice'): string {
        $output = '';
        
        // Subtotal
        $output .= '<div class="' . $type . '-total-row">';
        $output .= '<span class="' . $type . '-total-label">' . esc_html($text_settings['sub_total']) . '</span>';
        $output .= '<span class="' . $type . '-total-value">' . esc_html($formatter->format($document->getSubtotal())) . '</span>';
        $output .= '</div>';
        
        $discount_row = '';
        if ($document->getDiscountAmount() > 0) {
            $discount_row .= '<div class="' . $type . '-total-row">';
            $discount_row .= '<span class="' . $type . '-total-label">' . esc_html($text_settings['discount']);
            if ($document->getDiscountType() === 'percentage') {
                $discount_row .= ' (' . esc_html($document->getDiscountValue()) . '%)';
            }
            $discount_row .= '</span>';
            $discount_row .= '<span class="' . $type . '-total-value">-' . esc_html($formatter->format($document->getDiscountAmount())) . '</span>';
            $discount_row .= '</div>';
        }

        $tax_row = '';
        if ($document->getTaxAmount() > 0) {
            $tax_row .= '<div class="' . $type . '-total-row">';
            $tax_row .= '<span class="' . $type . '-total-label">' . esc_html(easy_invoice_get_tax_name());
            $tax_row .= ' (' . esc_html($document->getTaxRate()) . '%)</span>';
            $tax_row .= '<span class="' . $type . '-total-value">' . esc_html($formatter->format($document->getTaxAmount())) . '</span>';
            $tax_row .= '</div>';
        }

        // The rows read in the order the figures are computed: a discount taken
        // before tax sits above the tax line; one taken off the taxed total sits
        // below it. Shown the other way round, the arithmetic does not add up
        // on the page.
        // The model treats anything but 'before_tax' as after-tax.
        $method = (string) ($document->discount_calculation_method ?? '');
        $output .= ('before_tax' === $method) ? $discount_row . $tax_row : $tax_row . $discount_row;
        
        // Allow plugins to add content. Captured so it lands between the
        // rows and the grand total rather than printing ahead of the block.
        ob_start();
        if ($type === 'invoice') {
            do_action('easy_invoice_invoice_totals_after_tax', $document);
            do_action('easy_invoice_invoice_totals_after_discount', $document);
        } else {
            do_action('easy_invoice_quote_totals_after_tax', $document);
            do_action('easy_invoice_quote_totals_after_discount', $document);
        }
        $output .= (string) ob_get_clean();
        
        // Grand Total
        $output .= '<div class="' . $type . '-total-row ' . $type . '-grand-total">';
        $output .= '<span class="' . $type . '-total-label">' . esc_html($text_settings['total']) . '</span>';
        $output .= '<span class="' . $type . '-total-value">' . esc_html($formatter->format($document->getTotal())) . '</span>';
        $output .= '</div>';

        // Money already received: an invoice that is part paid must say what
        // is still owed, or the reader pays the total again. Skipped when an
        // addon has printed its own payment breakdown above.
        if ('invoice' === $type && false === strpos($output, 'partial-payments-breakdown') && is_callable([$document, 'getId'])) {
            $invoice_id = (int) $document->getId();
            $paid       = \EasyInvoice\Services\InvoiceBalance::paid($invoice_id);
            $credited   = \EasyInvoice\Services\InvoiceBalance::credited($invoice_id);
            /**
             * Filter whether the totals block lists payments received, credit
             * notes and the balance due.
             *
             * @param bool   $show     Default true when something has been paid or credited.
             * @param object $document Invoice model.
             */
            if (($paid > 0 || $credited > 0) && apply_filters('easy_invoice_totals_show_payments', true, $document)) {
                $balance = max(0, (float) $document->getTotal() - $paid - $credited);
                // A credit note corrects the invoice; the reader needs to see
                // it counted off before the balance.
                foreach (\EasyInvoice\Services\CreditNote::forInvoice($invoice_id) as $credit_id) {
                    $credit_amount = (float) get_post_meta($credit_id, '_easy_invoice_total', true);
                    if ($credit_amount <= 0) {
                        continue;
                    }
                    $credit_number = (string) get_post_meta($credit_id, '_easy_invoice_number', true);
                    $output .= '<div class="invoice-total-row invoice-credit-row">';
                    /* translators: %s: credit note number. */
                    $output .= '<span class="invoice-total-label">' . esc_html('' !== $credit_number ? sprintf(__('Credit note %s', 'easy-invoice'), $credit_number) : __('Credit note', 'easy-invoice')) . '</span>';
                    $output .= '<span class="invoice-total-value">-' . esc_html($formatter->format($credit_amount)) . '</span>';
                    $output .= '</div>';
                }
                if ($paid > 0) {
                    $output .= '<div class="invoice-total-row invoice-paid-row">';
                    $output .= '<span class="invoice-total-label">' . esc_html__('Paid', 'easy-invoice') . '</span>';
                    $output .= '<span class="invoice-total-value">-' . esc_html($formatter->format($paid)) . '</span>';
                    $output .= '</div>';
                }
                $output .= '<div class="invoice-total-row invoice-balance-due">';
                $output .= '<span class="invoice-total-label">' . esc_html__('Balance due', 'easy-invoice') . '</span>';
                $output .= '<span class="invoice-total-value">' . esc_html($formatter->format($balance)) . '</span>';
                $output .= '</div>';
            }
        }

        return $output;
    }
} 
```
