# easy-invoice/2.4.1/includes/Helpers/TaxCategory.php

Easy Invoice – Invoice Generator, PDF Quotes &amp; Payments, version 2.4.1. 165 lines.

- Page: https://pluginprobe.com/plugins/easy-invoice/2.4.1/code/includes/Helpers/TaxCategory.php
- Raw: https://pluginprobe.com/plugins/easy-invoice/2.4.1/raw/includes/Helpers/TaxCategory.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.1/code/includes/Helpers/TaxCategory.php#L10-L20`.

```php
<?php
/**
 * EN 16931 tax category vocabulary.
 *
 * @package Easy_Invoice
 * @subpackage Helpers
 */

namespace EasyInvoice\Helpers;

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * The tax categories a compliant invoice can carry.
 *
 * Why this exists
 * ---------------
 * The plugin's tax model has been a single rate plus a per-line "taxable"
 * boolean. That is enough to print a number on a page and nothing more. Every
 * structured e-invoicing standard in the markets this product sells into —
 * Factur-X and UBL in France, XRechnung in Germany, Peppol BIS in Belgium —
 * derives from EN 16931, which requires each line to carry a *category code*
 * from UNCL5305, not a yes/no flag. "Zero-rated", "exempt" and "reverse charge"
 * are three different things that all produce 0.00 in the tax column and mean
 * entirely different things to a tax authority.
 *
 * Codes are deliberately the standard's own, so they can be written straight
 * into an XML document later without a translation table.
 */
class TaxCategory {

    /** Standard rate. */
    const STANDARD = 'S';

    /** Zero rated goods. */
    const ZERO = 'Z';

    /** Exempt from tax. */
    const EXEMPT = 'E';

    /** VAT reverse charge — the customer accounts for the tax. */
    const REVERSE_CHARGE = 'AE';

    /** Intra-community supply within the EEA. */
    const INTRA_COMMUNITY = 'K';

    /** Free export item, tax not charged. */
    const EXPORT = 'G';

    /** Services outside the scope of tax. */
    const OUT_OF_SCOPE = 'O';

    /**
     * Every supported code with a human label.
     *
     * @return array<string,string>
     */
    public static function all(): array {
        return [
            self::STANDARD        => __( 'Standard rate', 'easy-invoice' ),
            self::ZERO            => __( 'Zero rated', 'easy-invoice' ),
            self::EXEMPT          => __( 'Exempt from tax', 'easy-invoice' ),
            self::REVERSE_CHARGE  => __( 'Reverse charge', 'easy-invoice' ),
            self::INTRA_COMMUNITY => __( 'Intra-community supply', 'easy-invoice' ),
            self::EXPORT          => __( 'Export, tax not charged', 'easy-invoice' ),
            self::OUT_OF_SCOPE    => __( 'Outside the scope of tax', 'easy-invoice' ),
        ];
    }

    /**
     * Is this a code we recognise?
     *
     * @param string $code Category code.
     * @return bool
     */
    public static function isValid( string $code ): bool {
        return array_key_exists( $code, self::all() );
    }

    /**
     * Human label for a code, falling back to the code itself.
     *
     * @param string $code Category code.
     * @return string
     */
    public static function label( string $code ): string {
        $all = self::all();
        return $all[ $code ] ?? $code;
    }

    /**
     * Does this category charge tax at all?
     *
     * Everything except the standard rate resolves to a zero tax amount, but for
     * different reasons — which is precisely why the reason has to be recorded
     * separately from the number.
     *
     * @param string $code Category code.
     * @return bool
     */
    public static function isTaxable( string $code ): bool {
        return self::STANDARD === $code;
    }

    /**
     * Does the standard require a stated reason alongside this category?
     *
     * EN 16931 rule BR-E-10 and its siblings: an exempt, reverse-charge,
     * intra-community, export or out-of-scope line must carry an exemption
     * reason. An invoice that zero-rates tax without saying why is rejected.
     *
     * @param string $code Category code.
     * @return bool
     */
    public static function requiresReason( string $code ): bool {
        return in_array(
            $code,
            [ self::EXEMPT, self::REVERSE_CHARGE, self::INTRA_COMMUNITY, self::EXPORT, self::OUT_OF_SCOPE ],
            true
        );
    }

    /**
     * The wording that has to appear on the document for a given category.
     *
     * These are the customary legal phrases. A merchant can override any of them
     * through `easy_invoice_tax_exemption_reason`, because the exact wording is
     * sometimes prescribed locally.
     *
     * @param string $code Category code.
     * @return string Empty when the category needs no statement.
     */
    public static function defaultReason( string $code ): string {
        switch ( $code ) {
            case self::REVERSE_CHARGE:
                $reason = __( 'Reverse charge — VAT to be accounted for by the recipient.', 'easy-invoice' );
                break;
            case self::INTRA_COMMUNITY:
                $reason = __( 'Intra-community supply — exempt under Article 138 of Directive 2006/112/EC.', 'easy-invoice' );
                break;
            case self::EXPORT:
                $reason = __( 'Export outside the EU — zero rated.', 'easy-invoice' );
                break;
            case self::EXEMPT:
                $reason = __( 'Exempt from VAT.', 'easy-invoice' );
                break;
            case self::OUT_OF_SCOPE:
                $reason = __( 'Outside the scope of VAT.', 'easy-invoice' );
                break;
            default:
                $reason = '';
        }

        /**
         * Filter the statement printed for a tax category.
         *
         * @param string $reason Default wording.
         * @param string $code   Category code.
         */
        return (string) apply_filters( 'easy_invoice_tax_exemption_reason', $reason, $code );
    }
}

```
