<?php
/**
 * CreateXml.php
 *
 * @since      1.0.0
 * @package    WcElectronInvoice\Xml
 * @author     alfiopiccione <alfio.piccione@gmail.com>
 * @license    http://opensource.org/licenses/gpl-2.0.php GPL v2
 *
 * Copyright (C) 2025 POP
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

namespace WcElectronInvoice\Xml;

use WcElectronInvoice\Admin\Settings\OptionPage;
use WcElectronInvoice\Integrations;
use WcElectronInvoice\PeppolApi;
use WcElectronInvoice\Plugin;
use WcElectronInvoice\Providers\DateTimeProvider;
use WcElectronInvoice\Providers\OrderQuery;
use WcElectronInvoice\Providers\ProductsQuery;
use WcElectronInvoice\Providers\TaxesQuery;
use WcElectronInvoice\Utils\TimeZone;
use WcElectronInvoice\WooCommerce\Fields\GeneralFields;
use WcElectronInvoice\WooCommerce\Fields\InvoiceFields;
use WcElectronInvoice\WooCommerce\Fields\TaxFields;
use WcElectronInvoice\WooCommerce\Providers\OrdersProvider;
use WcElectronInvoice\WooCommerce\Providers\TaxesProvider;
use function WcElectronInvoice\Functions\containsNumber;
use function WcElectronInvoice\Functions\disableInvoiceOnOrderTotalZero;
use function WcElectronInvoice\Functions\ensureHtaccessProtection;
use function WcElectronInvoice\Functions\getOrderInvoiceFileProgressiveNumber;
use function WcElectronInvoice\Functions\getN5RefNormative;
use function WcElectronInvoice\Functions\getNoTaxProductItemsTotals;
use function WcElectronInvoice\Functions\getPaymentMethodInvoiceCode;
use function WcElectronInvoice\Functions\getPostMeta;
use function WcElectronInvoice\Functions\getPostType;
use function WcElectronInvoice\Functions\getXmlFromOrder;
use function WcElectronInvoice\Functions\isWooCommerceActive;
use function WcElectronInvoice\Functions\isXml;
use function WcElectronInvoice\Functions\multiCurrencyConversion;
use function WcElectronInvoice\Functions\normalizePeppolText;
use function WcElectronInvoice\Functions\normalizeSdiText;
use function WcElectronInvoice\Functions\plainTextFromHtml;
use function WcElectronInvoice\Functions\popApi;
use function WcElectronInvoice\Functions\returnLocalFile;
use function WcElectronInvoice\Functions\setFormattedInvoiceNumber;

/**
 * Class CreateXml
 *
 * @since  1.0.0
 * @author alfiopiccione <alfio.piccione@gmail.com>
 */
final class CreateXml
{
    /**
     * List type
     *
     * @since 1.0.0
     */
    const LIST_TYPE = 'shop_order';

    /**
     * Code Type
     *
     * @since 1.0.0
     */
    const CODE_TYPE = 'INTERNO';

    /**
     * Shipping code
     *
     * @since 1.0.0
     */
    const CODE_SHIPPING = 'SHIPPING';

    /**
     * Shipping code
     *
     * @since 1.0.0
     */
    const CODE_STAMP_DUTY = 'BOLLO';

    /**
     * Fee code
     *
     * @since 1.0.0
     */
    const CODE_FEE = 'FEE';

    /**
     * Refund code
     *
     * @since 1.0.0
     */
    const CODE_REFUND = 'REFUND';

    /**
     * Extra Italian SDI code
     *
     * @since 1.0.0
     */
    const NO_IT_SDI_CODE = 'XXXXXXX';

    /**
     * Detect whether the current request is already the internal XML generation request
     * for the same order/provider pair. This prevents PMPro save hooks triggered during
     * XML generation from re-entering auto-save recursively.
     *
     * @param int|string $orderID
     * @param string     $provider
     *
     * @return bool
     */
    private static function isCurrentXmlGenerationRequestForOrder($orderID, string $provider): bool
    {
        $format            = \WcElectronInvoice\Functions\filterInput($_GET, 'format', FILTER_UNSAFE_RAW);
        $requestedOrder    = \WcElectronInvoice\Functions\filterInput($_GET, self::LIST_TYPE, FILTER_UNSAFE_RAW);
        $requestedProvider = sanitize_key((string)(\WcElectronInvoice\Functions\filterInput($_GET, 'provider',
            FILTER_UNSAFE_RAW) ?: 'woocommerce'));

        return 'xml' === $format
               && (string)$requestedOrder === (string)$orderID
               && $requestedProvider === sanitize_key($provider);
    }

    /**
     * Sent increment number
     *
     * @since 1.0.0
     *
     * @var int
     */
    private static $sentNumber = 1;

    /**
     * XML Element
     *
     * @since 1.0.0
     *
     * @var \SimpleXMLElement
     */
    private $xml;

    /**
     * Regex Web Service Code
     *
     * @since 1.0.0
     *
     * @var string
     */
    public $regexWEBSERV = "/^[a-zA-Z0-9]{7}$/i";

    /**
     * Regex PEC
     *
     * @since 1.0.0
     *
     * @var string
     */
    public $regexPEC = "/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*|\"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*\")@(?:\w*.?pec(?:.?\w+)*)$/i";

    /**
     * Regex Legal Mail
     *
     * @since 1.0.0
     *
     * @var string
     */
    public $regexLEGALMAIL = "/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*|\"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*\")@(?:\w*.?legalmail(?:.?\w+)*)$/i";

    /**
     * Regex Tax Code
     *
     * @since 1.0.0
     *
     * @var string
     */
    public $regexCF = "/^([A-Z]{6}[0-9LMNPQRSTUV]{2}[ABCDEHLMPRST]{1}[0-9LMNPQRSTUV]{2}[A-Za-z]{1}[0-9LMNPQRSTUV]{3}[A-Z]{1})$/i";

    /**
     * Regex VAT Code
     *
     * @since 1.0.0
     *
     * @var string
     */
    public $regexVAT = "/^(ATU[0-9]{8}|BE0[0-9]{9}|BG[0-9]{9,10}|CY[0-9]{8}L|CZ[0-9]{8,10}|DE[0-9]{9}|DK[0-9]{8}|EE[0-9]{9}|(EL|GR)[0-9]{9}|ES[0-9A-Z][0-9]{7}[0-9A-Z]|FI[0-9]{8}|FR[0-9A-Z]{2}[0-9]{9}|GB([0-9]{9}([0-9]{3})?|[A-Z]{2}[0-9]{13})|HU[0-9]{8}|IE[0-9][A-Z0-9][0-9]{5}[A-Z]{1,2}|IT[0-9]{11}|LT([0-9]{9}|[0-9]{12})|LU[0-9]{8}|LV[0-9]{11}|MT[0-9]{8}|NL[0-9]{9}B[0-9]{2}|PL[0-9]{10}|PT[0-9]{9}|RO[0-9]{2,10}|SE[0-9]{12}|SI[0-9]{8}|SK[0-9]{10})$/i";

    /**
     * @var string[]
     */
    public static array $allowedUBLZone = WC_EL_INV_UBL_COUNTRIES;

    /**
     * Resolve POP XML API route for the provided shop country.
     *
     * @param string $country
     *
     * @return string|null
     */
    public static function resolveXmlApiRouteForCountry(string $country): ?string
    {
        $country = strtoupper(trim($country));

        if ('DE' === $country && Integrations::isGermanyZugferdContext()) {
            $route = defined('XML_ZUGFERD_API_ROUTE') && XML_ZUGFERD_API_ROUTE ? XML_ZUGFERD_API_ROUTE : '';

            return apply_filters('wc_el_inv-zugferd_api_route', $route, $country);
        }

        switch ($country) {
            case 'IT':
                if (defined('XML_API_ROUTE') && XML_API_ROUTE) {
                    return XML_API_ROUTE;
                }
                break;
            case 'ES':
                if (defined('XML_FACTURAE_API_ROUTE') && XML_FACTURAE_API_ROUTE) {
                    return XML_FACTURAE_API_ROUTE;
                }
                break;
            case 'PL':
                if (defined('XML_KSEF_API_ROUTE') && XML_KSEF_API_ROUTE) {
                    return XML_KSEF_API_ROUTE;
                }
                break;
        }

        if (in_array($country, self::$allowedUBLZone, true) && defined('XML_UBL_API_ROUTE') && XML_UBL_API_ROUTE) {
            return XML_UBL_API_ROUTE;
        }

        return null;
    }

    /**
     * Resolve a dedicated integration route for background native actions that
     * do not use the XML-generation endpoints directly.
     *
     * @param string|null $action
     * @param string|null $method
     *
     * @return string|null
     */
    public static function resolveBackgroundIntegrationRoute(?string $action, ?string $method = null): ?string
    {
        $action = is_string($action) ? trim($action) : '';
        $method = is_string($method) ? trim($method) : '';

        if ('createAndSendZohoInvoice' === $action &&
            \WcElectronInvoice\Functions\isBackgroundExecutionMode($method, ['cronjob', 'ajax'])
        ) {
            return defined('WC_EL_INV_API_URL') && WC_EL_INV_API_URL
                ? WC_EL_INV_API_URL . 'integration/zoho/sync'
                : null;
        }

        return null;
    }

    private function isPeppolDocumentContext(): bool
    {
        return in_array(GeneralFields::getGeneralInvoiceOptionCountryState(), self::$allowedUBLZone, true);
    }

    private function normalizeDocumentBusinessText(
        $value,
        bool $removeBreaks = false,
        bool $preserveDashes = false
    ): string {
        if ($this->isPeppolDocumentContext()) {
            return normalizePeppolText($value, $removeBreaks);
        }

        return normalizeSdiText($value, $removeBreaks, $preserveDashes);
    }

    /**
     * /**
     * CreateXml constructor.
     *
     * @param \SimpleXMLElement $element
     *
     * @since 1.0.0
     *
     */
    public function __construct(\SimpleXMLElement $element)
    {
        $this->xml = $element;
    }

    /**
     * Add Processing Instruction
     *
     * @param $name
     * @param $value
     *
     * @since 1.0.0
     *
     */
    private function addProcessingInstruction($name, $value)
    {
        // Create a DomElement from this simpleXML object
        $domSxe = dom_import_simplexml($this->xml);

        // Create a handle to the owner doc of this xml
        $domParent = $domSxe->ownerDocument;

        // Find the topmost element of the domDocument
        $xpath        = new \DOMXPath($domParent);
        $firstElement = $xpath->evaluate('/*[1]')->item(0);

        // Add the processing instruction before the topmost element
        $pi = $domParent->createProcessingInstruction($name, $value);
        $domParent->insertBefore($pi, $firstElement);
    }

    /**
     * Version
     *
     * @param $ordersData
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function version($ordersData)
    {
        // Not set invoice type? FPR12 format default
        if (! property_exists($ordersData[0], 'invoice_type') ||
            empty($ordersData[0]->invoice_type) ||
            ! isset($ordersData[0]->invoice_type) ||
            ($ordersData[0]->invoice_type && '' === $ordersData[0]->invoice_type)
        ) {
            return 'FPR12';
        }

        switch ($ordersData[0]->invoice_type) {
            case 'freelance':
            case 'private':
            case 'company':
                return 'FPR12';
                break;
            default:
                break;
        }
    }

    /**
     * Date modified
     *
     * @param $order
     *
     * @return string
     * @since 1.0.0
     */
    private function dateLastModified($order)
    {
        $dateOrder = $order->date_modified;

        try {
            $timeZone = new TimeZone();
            $timeZone = new \DateTimeZone($timeZone->getTimeZone()->getName());
            $date     = new \DateTime($dateOrder);
            $date->setTimezone($timeZone);

            return $date->format('Y-m-d');
        } catch (\Exception $e) {
            echo esc_html__('Error DateTime in dateLastModified: ', WC_EL_INV_TEXTDOMAIN) . esc_html($e->getMessage());
        }
    }

    /**
     * Date invoice
     *
     * @param $order
     * @param $returnObj
     *
     * @return mixed|string|null
     * @throws \Exception
     */
    public static function dateInvoice($order, $returnObj = false)
    {
        if (! property_exists($order, 'id') &&
            ! property_exists($order, 'order_provider') &&
            ! property_exists($order, 'provider')
        ) {
            return '';
        }

        $provider    = $order->order_provider ?? $order->provider;
        $dateInvoice = null;

        if ('shop_order_refund' === $order->order_type) {
            $order = OrderQuery::instance()->getProviderOrder($order->id, $provider);

            // Provider eg. PMPro use Order for refund
            if ('woocommerce' !== $provider &&
                ! is_subclass_of($order, '\WcElectronInvoice\Providers\OrderRefund') &&
                method_exists($order, 'get_date_refunded')
            ) {
                // Typical usage for PMPro
                $dateInvoice = $order->get_date_refunded();
            } else {
                $dateInvoice = $order->get_date_created();
            }
        } elseif ('shop_order' === $order->order_type) {
            $order       = OrderQuery::instance()->getProviderOrder($order->id, $provider);
            $dateInvoice = $order->get_date_completed();

            $processingDate = $order->get_meta('_date_processing_set');
            $invoiceSent    = $order->get_invoice_sent();

            if (
                $processingDate &&
                // Invoice sent
                ('sent' === $invoiceSent ||
                 // Invoice not sent and status processing
                 ('no_sent' === $order->get_invoice_sent() &&
                  'processing' === $order->get_status())
                )
            ) {
                $dateInvoice = new DateTimeProvider($processingDate);
            }
        }

        /**
         * Filter invoice date
         */
        $dateInvoice = apply_filters('wc_el_inv-date-invoice', $dateInvoice);

        if ($returnObj) {
            return $dateInvoice;
        }

        if ($dateInvoice) {
            return $dateInvoice->date_i18n('Y-m-d');
        }

        return $dateInvoice;
    }

    /**
     * Date completed
     *
     * @param      $order
     * @param bool $parent
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function dateCompleted($order, $parent = false)
    {
        if (! property_exists($order, 'id') &&
            ! property_exists($order, 'order_provider') &&
            ! property_exists($order, 'provider')
        ) {
            return '';
        }

        $dateOrder = $order->date_created;
        $provider  = $order->order_provider ?? $order->provider;

        // Get parent order if current data is refund
        if ($parent && 'shop_order_refund' === $order->order_type) {
            $parentOrder = OrderQuery::instance()->getProviderOrder($order->parent_id, $provider);

            // Parent order created
            $dateOrder = $parentOrder->get_date_created();
            $dateOrder = $dateOrder->format('c');
        }

        try {
            $timeZone = new TimeZone();
            $timeZone = new \DateTimeZone($timeZone->getTimeZone()->getName());
            $date     = new \DateTime($dateOrder);
            $date->setTimezone($timeZone);

            return $date->format('Y-m-d');
        } catch (\Exception $e) {
            echo esc_html__('Error DateTime in dateCompleted: ', WC_EL_INV_TEXTDOMAIN) . esc_html($e->getMessage());
        }
    }

    /**
     * Format
     *
     * @param $ordersData
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function formatTransmission($ordersData)
    {
        // Not set invoice type? FPR12 format default
        if (! property_exists($ordersData[0], 'invoice_type') ||
            empty($ordersData[0]->invoice_type) ||
            ! isset($ordersData[0]->invoice_type) ||
            (isset($ordersData[0]->invoice_type) && '' === $ordersData[0]->invoice_type)
        ) {
            return 'FPR12';
        }

        // Set format
        return apply_filters('wc_el_inv-format_transmission_xml', 'FPR12', $ordersData[0]->invoice_type);
    }

    /**
     * Code or Pec
     *
     * @param $ordersData
     * @param $type
     *
     * @return null|string
     * @since 1.0.0
     *
     */
    private function codeOrPec($ordersData, $type)
    {
        if (! property_exists($ordersData[0], 'sdi_type')) {
            return '';
        }

        $country = $this->customerCountry($ordersData);

        // Get pec
        $pec         = preg_match($this->regexPEC, $ordersData[0]->sdi_type) ? $ordersData[0]->sdi_type : null;
        $legalMail   = preg_match($this->regexLEGALMAIL, $ordersData[0]->sdi_type) ? $ordersData[0]->sdi_type : null;
        $generalMail = false !== filter_var($ordersData[0]->sdi_type,
            FILTER_VALIDATE_EMAIL) ? $ordersData[0]->sdi_type : null;

        if (null === $pec && null === $legalMail && $generalMail) {
            $pec = $generalMail;
        }

        $pec = $pec ?: $legalMail;

        $invoiceType = $ordersData[0]->invoice_type;

        switch ($invoiceType) {
            case 'private':
                $code     = ! preg_match($this->regexWEBSERV, $ordersData[0]->sdi_type) ?
                    '0000000' : $ordersData[0]->sdi_type;
                $emailPec = '0000000' !== $code ? $pec : null;
                break;
            case 'freelance':
            case 'company':
                $code     = ! preg_match($this->regexWEBSERV,
                    $ordersData[0]->sdi_type) && $pec || '' === $ordersData[0]->sdi_type ?
                    '0000000' : $ordersData[0]->sdi_type;
                $emailPec = '0000000' === $code ? $pec : null;
                break;
            default:
                $emailPec = '';
                $code     = '0000000';
                break;
        }

        switch ($type) {
            case 'pec':
                return $emailPec;
            case 'code':
                return 'IT' === $country ? strtoupper($code) : self::NO_IT_SDI_CODE;
            default:
                return '';
        }
    }

    /**
     * Customer country
     *
     * @param $ordersData
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function customerCountry($ordersData)
    {
        if (! property_exists($ordersData[0], 'billing')) {
            return '';
        }

        return $ordersData[0]->billing['country'] ?? '';
    }

    /**
     * Customer vat
     *
     * @param $ordersData
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function customerVatNumber($ordersData)
    {
        if (! property_exists($ordersData[0], 'vat_number')) {
            return '';
        }

        return $ordersData[0]->vat_number ?? '';
    }

    /**
     * Customer Tax Code
     *
     * @param $ordersData
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function customerTaxCodeNumber($ordersData)
    {
        if (! property_exists($ordersData[0], 'tax_code')) {
            return '';
        }

        $euVatCountry = GeneralFields::getEuCountries();
        $country      = $this->customerCountry($ordersData);

        if (in_array($ordersData[0]->invoice_type, ['company', 'freelance'], true)) {
            // If VAT format
            if (preg_match($this->regexVAT, $country . $ordersData[0]->tax_code)) {
                $taxCode = $ordersData[0]->tax_code;

                return isset($ordersData[0]->tax_code) ? strtoupper($taxCode) : '';
            }
        }

        if ((! in_array($country, $euVatCountry, true) || $country !== 'IT') &&
            ! in_array($ordersData[0]->invoice_type, ['company', 'freelance'], true)
        ) {
            return '';
        }

        if ('private' === $ordersData[0]->invoice_type) {
            return isset($ordersData[0]->tax_code) &&
                   preg_match($this->regexCF, $ordersData[0]->tax_code) ?
                strtoupper($ordersData[0]->tax_code) : '';
        }

        // By default, it returns the default value without any checks: e.g. for association support
        return isset($ordersData[0]->tax_code) ? strtoupper($ordersData[0]->tax_code) : '';
    }

    /**
     * Customer company
     *
     * @param $ordersData
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function customerCompany($ordersData)
    {
        if (! property_exists($ordersData[0], 'billing')) {
            return '';
        }

        $customerCompany = $ordersData[0]->billing['company'] ?? '';
        $customerCompany = str_replace('`', "&rsquo;", $customerCompany);
        $customerCompany = str_replace('´', "&rsquo;", $customerCompany);

        if ('private' === $ordersData[0]->invoice_type) {
            return '';
        }

        return $this->normalizeDocumentBusinessText($customerCompany, true);
    }

    /**
     * Customer name
     *
     * @param $ordersData
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function customerName($ordersData)
    {
        if (! property_exists($ordersData[0], 'billing')) {
            return '';
        }

        $name = $ordersData[0]->billing['first_name'] ?? '';
        $name = str_replace('`', "&rsquo;", $name);
        $name = str_replace('´', "&rsquo;", $name);

        return $this->normalizeDocumentBusinessText($name, true);
    }

    /**
     * Customer last name
     *
     * @param $ordersData
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function customerLastName($ordersData)
    {
        if (! property_exists($ordersData[0], 'billing')) {
            return '';
        }

        $lastName = $ordersData[0]->billing['last_name'] ?? '';
        $lastName = str_replace('`', "&rsquo;", $lastName);
        $lastName = str_replace('´', "&rsquo;", $lastName);

        return $this->normalizeDocumentBusinessText($lastName, true);
    }

    /**
     * customerEmail
     *
     * @param $ordersData
     *
     * @return string|string[]|null
     */
    public function customerEmail($ordersData)
    {

        if (! property_exists($ordersData[0], 'billing')) {
            return '';
        }

        $email = $ordersData[0]->billing['email'] ?? '';

        return plainTextFromHtml($email, true);
    }

    /**
     * Customer address
     *
     * @param $ordersData
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function customerAddress($ordersData)
    {
        if (! property_exists($ordersData[0], 'billing')) {
            return '';
        }

        $address = $ordersData[0]->billing['address_1'] ?? '';

        return $this->normalizeDocumentBusinessText($address, true);
    }

    /**
     * Customer post code
     *
     * @param $ordersData
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function customerPostCode($ordersData)
    {
        if (! property_exists($ordersData[0], 'billing')) {
            return '';
        }

        $billingPostCode = $ordersData[0]->billing['postcode'] ?? '';
        $country         = $this->customerCountry($ordersData);

        $getPEPPOLPOPIntegrationData = PeppolApi::getPEPPOLPOPIntegrationData();
        $isPEPPOL                    = ! empty($getPEPPOLPOPIntegrationData->identifier_value);

        $postCode = (! $isPEPPOL && $country !== 'IT') ? '00000' : $billingPostCode;

        return apply_filters('wc_el_inv-customer_postcode', $postCode, $country, $ordersData);
    }

    /**
     * Customer city
     *
     * @param $ordersData
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function customerCity($ordersData)
    {
        if (! property_exists($ordersData[0], 'billing')) {
            return '';
        }

        return $this->normalizeDocumentBusinessText($ordersData[0]->billing['city'] ?? '', true);
    }

    /**
     * Customer state
     *
     * @param $ordersData
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function customerState($ordersData)
    {
        if (! property_exists($ordersData[0], 'billing')) {
            return '';
        }

        return plainTextFromHtml($ordersData[0]->billing['state'] ?? '', true);
    }

    /**
     * Payment Method
     *
     * @param $order
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function paymentMethod($order)
    {
        if (! property_exists($order, 'payment_method') &&
            ! property_exists($order, 'refunded') &&
            ! isset($order->refunded['refunded_payment_method'])
        ) {
            return '';
        }

        /**
         * Payment method for refunded:
         */
        if ('shop_order_refund' === $order->order_type &&
            isset($order->refunded['refunded_payment_method'])
        ) {
            $method = $order->refunded['refunded_payment_method'];
            if ('' === $order->refunded['refunded_payment_method']) {
                $method = 'MP01';
            }

            return $method;
        }

        $payment_method = null;
        if (property_exists($order, 'payment_method')) {
            $payment_method = false !== strpos($order->payment_method, 'stripe') ? 'stripe' : $payment_method;
            $payment_method = false !== strpos($order->payment_method, 'paypal') ? 'paypal' : $payment_method;
        }

        /**
         * Payment method for order:
         *
         * - Bacs
         * - Cheque
         * - PayPal Express Checkout
         * - Stripe
         * - Stripe SEPA
         * - ..... etc
         */
        if (property_exists($order, 'payment_method')) {
            if (! $payment_method) {
                $payment_method = $order->payment_method;
            }

            $code = getPaymentMethodInvoiceCode($payment_method);

            return apply_filters('wc_el_inv-default_payment_method_xml_invoice', $code, $payment_method);
        }
    }

    /**
     * Product Description
     *
     * @param      $item
     * @param null $type
     *
     * @return string|string[]|null
     * @since 1.0.0
     *
     */
    private function productDescription($item, $type = null)
    {
        // non-WooCommerce logic.
        if (empty($item['product_id'])) {
            return $this->normalizeDocumentBusinessText($item['name'], true);
        }

        $post = null;
        if ('woocommerce' === $item['provider']) {
            // WooCommerce logic.
            $post = get_post(intval($item['product_id']));
        }

        $productViewDesc = OptionPage::init()->getOptions('invoice_description_type');
        $productViewMeta = OptionPage::init()->getOptions('invoice_product_meta');

        $description = esc_html__('Unknown item', WC_EL_INV_TEXTDOMAIN);
        if (isset($item['description'])) {
            $description = $item['description'];
        } elseif (isset($item['name'])) {
            $description = $item['name'];
        }

        /**
         * Filter - description excerpt + title or only title
         */
        if ($post) {
            if ('on' === $productViewDesc && $post->post_excerpt ||
                true === apply_filters('wc_el_inv-product_excerpt_description_xml_invoice',
                    false) && $post->post_excerpt
            ) {
                $description = $post->post_title . ' ' . $post->post_excerpt;
            } else {
                $description = $post->post_title;
            }
        }

        /**
         * Filter - Product Meta data
         */
        if ('woocommerce' === $item['provider']) {
            if ('on' === $productViewMeta ||
                true === apply_filters('wc_el_inv-product_meta_description_xml_invoice', false)
            ) {

                /**
                 * Filter hidden meta list
                 */
                $hiddenMetaData = apply_filters('wc_el_inv-product_meta_hidden_list', array(
                    '_qty',
                    '_tax_class',
                    '_product_id',
                    '_variation_id',
                    '_line_subtotal',
                    '_line_subtotal_tax',
                    '_line_total',
                    '_line_tax',
                    'method_id',
                    'cost',
                    '_reduced_stock',
                    '_restock_refunded_items',
                ));

                $metaString = '';
                if (! empty($item['meta_data'])) {
                    foreach ($item['meta_data'] as $index => $meta) {
                        $sep = $index === count($item['meta_data']) - 1 ? '' : ', ';
                        // Remove pa_ from meta key
                        $key = str_replace(array('pa_'), '', $meta['key']);
                        // Remove hidden check '_'
                        $sub = substr($key, 0, 1);
                        if (in_array($key, $hiddenMetaData, true) || '_' === $sub) {
                            continue;
                        }
                        if (is_array($meta['value'])) {
                            continue;
                        }
                        $metaString = $metaString . "{$key}: {$meta['value']}{$sep}";
                    }

                    $description = "{$description} {$metaString}";
                }
            }
        }

        /**
         * Filter - description for item
         */
        $description = apply_filters('wc_el_inv-product_description_xml_invoice', $description, $item);

        if ('refund' === $type) {
            $description = sprintf('%s %s', esc_html__('Refund: ', WC_EL_INV_TEXTDOMAIN), "{$description}");
        }

        $description = mb_strimwidth($description, 0, 500, '');

        return $this->normalizeDocumentBusinessText($description, true);
    }

    /**
     * Document type
     *
     * @param $order
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function documentType($order)
    {
        if (! property_exists($order, 'order_type')) {
            return '';
        }

        $docType = '';

        if (isset($order->order_type)) {
            // Set doc type
            switch ($order->order_type) {
                case 'shop_order':
                    $docType = 'TD01';
                    break;
                case 'shop_order_refund':
                    $docType = 'TD04';
                    break;
                default:
                    $docType = '';
                    break;
            }
        }

        return $docType;
    }

    /**
     * Tax Rate
     *
     * @param        $item
     * @param string $get
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function taxRate($item, $get = 'rate')
    {
        // is always 'woocommerce'
        $provider    = isset($item['provider']) ? $item['provider'] : 'woocommerce';
        $taxProvider = TaxesQuery::instance()->getTaxProvider($provider);

        $taxRates  = array();
        $taxStatus = '';
        // Default product exist
        $productExist = true;

        if (! isset($item['product_id']) && ! isset($item['variation_id']) ||
            0 === (int)$item['product_id'] && 0 === (int)$item['variation_id']
        ) {
            $productExist = false;
            // Set base tax (standard)
            $taxRates = $taxProvider->get_base_tax_rates();
        }

        $taxEnabled = $taxProvider->is_tax_enabled();
        if ('yes' !== $taxEnabled || 0.00 === floatval($item['total_tax'])) {
            return $this->numberFormat(0.00, 0);
        }

        $id = isset($item['variation_id']) && 0 !== intval($item['variation_id']) ? intval($item['variation_id']) : intval($item['product_id']);

        $product = null;
        if ($id) {
            $product = ProductsQuery::instance()->getProduct($id, $provider);
        }

        if (is_subclass_of($product, 'WcElectronInvoice\Providers\ProductAbstract') || is_subclass_of($product,
                'WC_Abstract_Legacy_Product')) {

            $productExist = true;
            // Get rates by order tax class
            $orderTaxRates = isset($item['tax_class']) ? $taxProvider->get_rates($item['tax_class']) : null;
            // Get rates by product tax class
            $taxRates  = $orderTaxRates ?: $taxProvider->get_rates($product->get_tax_class());
            $taxStatus = $product->get_tax_status();

            // Double check get rates by billing country
            $order = OrderQuery::instance()->getProviderOrder($item['order_id'], $provider);
            $taxes = $taxProvider->get_rates_for_tax_class($product->get_tax_class());

            if (! empty($taxes)) {
                foreach ($taxes as $tax) {
                    // Get tax rate from taxes by country
                    if ($tax->tax_rate_country === $order->get_billing_country() &&
                        0 === (int)$tax->city_count) {
                        return $this->numberFormat($tax->tax_rate, 0);
                    }

                    // Get tax rate from taxes by country and city
                    $city = strtoupper($order->get_billing_city());
                    if ($tax->tax_rate_country === $order->get_billing_country() &&
                        0 < (int)$tax->city_count &&
                        in_array($city, $tax->city, true)
                    ) {
                        return $this->numberFormat($tax->tax_rate, 0);
                    }

                    // Empty taxRate, State, Country
                    if (empty($taxRates) &&
                        isset($tax->tax_rate) &&
                        '' === $tax->tax_rate_country &&
                        null === $tax->city &&
                        (! isset($tax->city)) &&
                        0 === (int)$tax->city_count
                    ) {
                        return $this->numberFormat($tax->tax_rate, 0);
                    }
                }
            }
        }

        // Get base tax rates
        if (empty($taxRates)) {
            $taxRates = $taxProvider->get_base_tax_rates();
        }

        if ($taxProvider instanceof TaxesProvider) {
            $taxRate = reset($taxRates);
        } else {
            $taxRate = $taxRates;
        }

        switch ($get) {
            case 'rate':
                if ('' === $taxStatus && ! $productExist) {
                    $rate = 0.00 !== floatval($item['total_tax']) ? floatval($taxRate[$get] ?? 0) : 0.00;
                } else {
                    $rate = $taxStatus === 'taxable' && 0.00 !== floatval($item['total_tax']) ? floatval($taxRate[$get] ?? 0) : 0.00;
                }
                // Fallback for non-WC providers: infer rate from item totals when provider lookup returns 0
                if (0.0 === $rate && 'woocommerce' !== $provider && 0.0 < floatval($item['total_tax'])) {
                    $subtotal = abs(floatval($item['subtotal'] ?? 0)) ?: abs(floatval($item['total'] ?? 0));
                    if ($subtotal > 0) {
                        $rate = round(floatval($item['total_tax']) / $subtotal * 100);
                    }
                }

                return $this->numberFormat($rate, 0);
            default:
                return '';
        }
    }

    /**
     * Vies valid or Extra UE TAX rate
     *
     * @param $ordersData
     * @param $defaultVat
     * @param $vies
     *
     * @return int
     */
    private function viesValidTaxRate($ordersData, $defaultVat, $vies)
    {
        // UE valid VIES rate or Extra UE
        if ($vies &&
            in_array($this->customerCountry($ordersData), GeneralFields::getEuCountries(), true) &&
            'IT' !== $this->customerCountry($ordersData)
        ) {
            return 0;
        } elseif (! in_array($this->customerCountry($ordersData), GeneralFields::getEuCountries(), true)) {
            return 0;
        }

        return $defaultVat;
    }

    /**
     * Shipping Rate
     *
     * @param $provider
     *
     * @return mixed|string+
     * @since 1.0.0
     */
    private function shippingRate($provider)
    {
        $taxProvider = TaxesQuery::instance()->getTaxProvider($provider);
        $taxRates    = $taxProvider->get_shipping_tax_rates();
        if (empty($taxRates)) {
            $taxRates = $taxProvider->get_base_tax_rates();
        }

        if (empty($taxRates)) {
            return '';
        }

        $taxRate = reset($taxRates);

        if ('no' === $taxRate['shipping']) {
            return '';
        }

        return $taxRate['rate'];
    }

    /**
     * Resolve tax rate for a shipping item, including plugins that do not persist tax_class.
     *
     * @param mixed  $taxesProvider
     * @param mixed  $item
     * @param string $country
     * @param string $city
     *
     * @return object|null
     */
    private function resolveShippingItemTaxRate($taxesProvider, $item, string $country, string $city)
    {
        $taxRate = $taxesProvider->get_shipping_item_tax_rate($item);
        if (! empty($taxRate) && is_object($taxRate)) {
            return $taxRate;
        }

        if (! is_object($item) || ! method_exists($item, 'get_tax_class')) {
            return null;
        }

        $taxes = $taxesProvider->get_rates_for_tax_class($item->get_tax_class());
        if (empty($taxes)) {
            return null;
        }

        $matchedTaxRate = null;
        $countryTaxRate = null;
        foreach ($taxes as $tax) {
            if (! is_object($tax)) {
                continue;
            }

            $matchedTaxRate = $tax;

            if ($tax->tax_rate_country === $country &&
                0 < (int)$tax->city_count &&
                isset($tax->city) &&
                is_array($tax->city) &&
                in_array($city, $tax->city, true)
            ) {
                return $tax;
            }

            if ($tax->tax_rate_country === $country &&
                0 === (int)$tax->city_count
            ) {
                $countryTaxRate = $tax;
            }
        }

        return $countryTaxRate ?: $matchedTaxRate;
    }

    /**
     * Extract the tax rate id for a shipping item without assuming array-like access on the id.
     *
     * @param mixed  $taxesProvider
     * @param mixed  $item
     * @param object $taxRate
     *
     * @return int|null
     */
    private function resolveShippingItemTaxRateId($taxesProvider, $item, $taxRate)
    {
        $taxRateId = $taxesProvider->get_shipping_item_tax_rate_id($item);
        if (null !== $taxRateId) {
            return (int)$taxRateId;
        }

        if (is_object($taxRate) && isset($taxRate->tax_rate_id) && is_numeric($taxRate->tax_rate_id)) {
            return (int)$taxRate->tax_rate_id;
        }

        return null;
    }

    /**
     * Invoice Number
     *
     * @param $order
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function invoiceNumber($order)
    {
        if ($order->provider instanceof OrdersProvider) {
            $provider = $order->provider->name();
        } else {
            $provider = ! empty($order->provider) ? $order->provider : 'woocommerce';
        }

        if (
            is_subclass_of($order, '\WcElectronInvoice\Providers\Order') ||
            is_subclass_of($order, '\WcElectronInvoice\Providers\OrderRefund')
        ) {
            $id   = $order->get_id();
            $type = $order->get_type();
        } else {
            $id   = $order->id;
            $type = $order->order_type;
        }

        $order = OrderQuery::instance()->getProviderOrder($id, $provider);

        if (! $order) {
            return '';
        }

        $options = OptionPage::init();
        if (is_subclass_of($order, 'WcElectronInvoice\Providers\OrderRefund') || 'shop_order_refund' === $type) {
            $orderParent    = OrderQuery::instance()->getProviderOrder($order->get_parent_id(), $provider);
            $numerationType = $orderParent->get_billing_choice_type() ?: 'invoice';
            if ('receipt' === $numerationType) {
                $numerationType = 'credit_note_receipt';
            } else {
                $numerationType = 'credit_note';
            }
        } else {
            // Numeration type
            $numerationType = $order->get_billing_choice_type() ?: 'invoice';
            $choiceType     = \WcElectronInvoice\Functions\filterInput($_GET, 'choice_type', FILTER_UNSAFE_RAW);

            if ($numerationType === 'invoice' && 'receipt' === $choiceType) {
                $numerationType = 'receipt';
            } elseif ('invoice' === $choiceType) {
                $numerationType = 'invoice';
            }
        }

        // Prefix
        $prefix = $options->getOptions("prefix_{$numerationType}_number");

        /**
         * Invoice prefix filter
         */
        $prefix = apply_filters("wc_el_inv-prefix_{$numerationType}", $prefix, $order);

        // Suffix
        $suffix           = $options->getOptions('suffix_invoice_number');
        $suffixYear       = $options->getOptions('suffix_year_invoice_number');
        $suffixYearFormat = $options->getOptions('suffix_year_format_invoice_number') ?: 'Y';
        if ('on' === $suffixYear) {
            if ('shop_order' === $type) {
                $created = $order->has_status('completed') ? $order->get_date_completed() : $order->get_date_created();
            } else {
                $created = $order->get_date_created();
            }
            $suffix = "/" . $created->format($suffixYearFormat);
        } else {
            $suffix = isset($suffix) && '' !== $suffix ? $suffix : '';
        }

        /**
         * Invoice suffix filter
         */
        $suffix = apply_filters('wc_el_inv-suffix_number', $suffix, $order);

        $formattedNumber = $order->get_meta("{$type}_formatted_number_{$order->get_id()}") ?? null;
        $resolvedNumber  = \WcElectronInvoice\Functions\getStoredInvoiceNumberValue($order, $numerationType);
        if ($resolvedNumber < 1) {
            $resolvedNumber = \WcElectronInvoice\Functions\recoverOrderInvoiceNumberFromFormatted($order,
                $numerationType);
            if ($resolvedNumber > 0) {
                if ('shop_order_refund' === $type) {
                    $order->update_meta("refund_number_invoice-{$order->get_id()}", $resolvedNumber);
                } else {
                    $order->update_meta('order_number_invoice', $resolvedNumber);
                }
                $order->save();
            }
        }

        // Patch formatted number (contain order number)
        if ($formattedNumber && $resolvedNumber > 0 && ! containsNumber($formattedNumber, $resolvedNumber)) {
            if ('shop_order_refund' === $type) {
                $order->update_meta("refund_number_invoice-{$order->get_id()}", $resolvedNumber);
            } else {
                $order->update_meta('order_number_invoice', $resolvedNumber);
            }
            setFormattedInvoiceNumber($order, $numerationType, true);
            // Save
            $order->save();
            $formattedNumber = $order->get_meta("{$type}_formatted_number_{$order->get_id()}") ?? null;
        }

        if ($formattedNumber) {
            $numberNormalize = apply_filters('wc_el_inv-normalize_formatted_number', $formattedNumber, $order);
            if ($resolvedNumber > 0 && ! containsNumber((string)$numberNormalize, $resolvedNumber)) {
                $number = \WcElectronInvoice\Functions\buildFormattedInvoiceNumberValue($order, $numerationType,
                    $resolvedNumber);
                $order->update_meta("{$type}_formatted_number_{$order->get_id()}", $number);
                $order->save();

                return apply_filters('wc_el_inv-create-xml-invoice-number', $number, $order, $prefix);
            }
            // Update formatted if number invoice is numeric value
            if (is_numeric($numberNormalize) && ($prefix || $numberNormalize)) {
                $number = "{$prefix}{$numberNormalize}{$suffix}";
                $order->update_meta("{$type}_formatted_number_{$order->get_id()}", $number);
                $order->save();
            }
        }

        $number = \WcElectronInvoice\Functions\buildFormattedInvoiceNumberValue($order, $numerationType,
            $resolvedNumber);
        if ('' === $number) {
            error_log("[WcElectronInvoice] Missing invoice number while building XML number for order #{$order->get_id()} ({$provider})");

            return '';
        }

        $order->update_meta("{$type}_formatted_number_{$order->get_id()}", $number);
        $order->save();

        return apply_filters('wc_el_inv-create-xml-invoice-number', $number, $order, $prefix);
    }

    /**
     * Progressive file number
     *
     * @param $ordersData
     *
     * @return string
     * @since 1.0.0
     *
     */
    public static function progressiveFileNumber($ordersData, bool $reserveNew = false)
    {
        if (empty($ordersData) || ! is_array($ordersData) || ! property_exists($ordersData[0], 'id')) {
            return '';
        }

        $provider = $ordersData[0]->provider ?? 'woocommerce';
        $order    = OrderQuery::instance()->getProviderOrder($ordersData[0]->id, $provider);

        return getOrderInvoiceFileProgressiveNumber($order, $reserveNew);
    }

    /**
     * Increment number
     *
     * @return int
     * @since 1.0.0
     *
     */
    private function increment(): int
    {
        $number = self::$sentNumber++;

        return str_pad($number, 5, '0', STR_PAD_LEFT);
    }

    /**
     * createXMLOnOrderCompleted
     *
     * @param $orderID
     * @param $from
     * @param $to
     *
     * @return void
     */
    public static function createXMLOnOrderCompleted($orderID, $from = null, $to = null)
    {
        self::createXMLOnProviderOrderCompleted($orderID, 'woocommerce', $from, $to);
    }

    /**
     * Save the generated XML when an order reaches the configured completed state.
     *
     * @param int|string  $orderID
     * @param string|null $provider
     * @param string|null $from
     * @param string|null $to
     *
     * @return void
     */
    public static function createXMLOnProviderOrderCompleted(
        $orderID,
        $provider = 'woocommerce',
        $from = null,
        $to = null
    ) {
        $level = \WcElectronInvoice\Wizard::popGetUserLevel();
        if (! \WcElectronInvoice\Functions\isUserPlanLevelGte($level, 'basic')) {
            return;
        }

        $enableSaving = OptionPage::init()->getOptions('invoice_saving_directory_xml_enable');
        $xmlDir       = OptionPage::init()->getOptions('invoice_saving_directory_xml');

        if ('on' !== $enableSaving) {
            return;
        }

        $provider = sanitize_key((string)$provider);
        if (! $provider) {
            $provider = 'woocommerce';
        }

        if (self::isCurrentXmlGenerationRequestForOrder($orderID, $provider)) {
            return;
        }

        $order = OrderQuery::instance()->getProviderOrder($orderID, $provider);
        if (! $order) {
            return;
        }

        // Check if refund
        $docTypeCode = "TD01";
        $docType     = "order";
        if ('shop_order_refund' === $order->get_type()) {
            $parentOrder = OrderQuery::instance()->getProviderOrder($order->get_parent_id(), $provider);
            if (! $parentOrder) {
                return;
            }
            $order       = $parentOrder;
            $docTypeCode = "TD04";
            $docType     = "refund";
        }

        // Check if order exist.
        if (! is_subclass_of($order, '\WcElectronInvoice\Providers\Order') &&
            ! is_subclass_of($order, '\WcElectronInvoice\Providers\OrderRefund')) {
            return;
        }

        // Disable invoice order whit total zero
        if (disableInvoiceOnOrderTotalZero($order)) {
            return;
        }

        $orderRefund = null;
        if (is_subclass_of($order, '\WcElectronInvoice\Providers\OrderRefund')) {
            // Refund order
            $orderRefund = $order;
        }

        /**
         * Filter order status for create xml
         * default: "completed"
         */
        $statusAllowed = apply_filters('wc_el_inv-invoice_create_xml_by_order_status', 'completed', $order);

        if ($from && $to) {
            if ($statusAllowed === $from || $statusAllowed !== $to) {
                return;
            }
        }

        if (($from !== $to && $statusAllowed === $to) || $statusAllowed === $order->get_status()) {

            if (! $xmlDir) {
                // Create default dir
                $uploadsWooPopDir = trailingslashit(wp_upload_dir()['basedir']) . 'pop-invoice';
                wp_mkdir_p($uploadsWooPopDir);
            }

            $key     = \WcElectronInvoice\Admin\Settings\OptionPage::getK();
            $type    = $order->get_meta('billing_choice_type');
            $url     = esc_url_raw(add_query_arg([
                self::LIST_TYPE => $orderID,
                'provider'      => $provider,
                'key'           => $key,
                'format'        => 'xml',
                'choice_type'   => $type,
            ],
                site_url('/' . \WcElectronInvoice\EndPoint\Endpoints::ENDPOINT)));
            $xml     = wp_remote_get($url, \WcElectronInvoice\Functions\getSessionCookieArgs('application/xml'));
            $bodyXml = wp_remote_retrieve_body($xml);

            // Fallback for get XML
            if (! $bodyXml) {
                if ('shop_order_refund' === $order->get_type()) {
                    // Get xml from order
                    $bodyXml = getXmlFromOrder($orderRefund);
                } else {
                    // Get xml from order
                    $bodyXml = getXmlFromOrder($order);
                }
            }

            // Is valid XML ?
            $isXml = isXml($bodyXml);

            // Not valid ?
            if (! $isXml->valid) {
                return;
            }

            // Xml file name
            $dataForFileName = array(
                (object)array(
                    'id'       => $orderID,
                    'provider' => $provider,
                ),
            );

            $VatNumber = preg_replace('/^' . preg_quote(GeneralFields::getGeneralInvoiceOptionCountryState(),
                    '/') . '/',
                '', GeneralFields::getGeneralInvoiceOptionVatNumber()
            );
            $fileName  = GeneralFields::getGeneralInvoiceOptionCountryState() . $VatNumber . '_' . self::progressiveFileNumber($dataForFileName);

            if (! $xmlDir) {
                $uploadDir = trailingslashit(wp_get_upload_dir()['basedir']) . 'pop-invoice';
            } else {
                $uploadDir = $xmlDir;
            }
            $file = $uploadDir . "/{$docTypeCode}_{$docType}_{$orderID}_" . $fileName . '.xml';

            /**
             * Filter file path
             */
            $file = apply_filters('wc_el_inv-invoice_create_xml_by_order_status_file',
                $file,
                $uploadDir,
                $docTypeCode,
                $docType,
                $orderID,
                $fileName,
                $provider
            );

            if (! $bodyXml) {
                $bodyXml = $isXml->xml_string;
            }

            if ('IT' === GeneralFields::getGeneralInvoiceOptionCountryState()) {
                // Stupid SimpleXMLElement!! replace "xmlns:p:FatturaElettronica" in "p:FatturaElettronica"
                $bodyXml = preg_replace('/xmlns:p:FatturaElettronica/', 'p:FatturaElettronica', $bodyXml);
            }

            // Add .htaccess file
            ensureHtaccessProtection($uploadDir);
            file_put_contents($file, $bodyXml);
        }
    }

    /**
     * Doc ID
     *
     * @param $order
     * @param $type
     *
     * @return int|string
     * @throws \Exception
     */
    private function docID($order, $type = null)
    {
        $id = $order->id;

        // If Refund get id of the parent.
        if ('shop_order_refund' === $order->order_type) {
            if ($order->provider instanceof OrdersProvider) {
                $provider = $order->provider->name();
            } else {
                $provider = ! empty($order->provider) ? $order->provider : 'woocommerce';
            }

            $order = OrderQuery::instance()->getProviderOrder($order->parent_id, $provider);

            // return invoice number
            $number = $this->invoiceNumber($order);

            // PMPro provider condition
            if ('connected_invoice' === $type && 'woocommerce' !== $provider) {
                return apply_filters('wc_el_inv-invoice_connected_number', $number, $order);
            }

            return $number;
        }

        return intval($id);
    }

    /**
     * Number Format
     *
     * @param int  $number
     * @param int  $decimals
     * @param bool $abs
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function numberFormat($number = 0, $decimals = null, $abs = true)
    {
        if (null === $number) {
            return null;
        }

        if (is_string($number)) {
            $number = floatval($number);
        }

        if ($abs) {
            $number = abs($number);
        }

        if (null === $decimals) {
            $decimals = apply_filters('wc_el_inv-price_num_decimals', get_option('woocommerce_price_num_decimals', 2));
        }

        // specific number format for xml
        return number_format($number, $decimals, '.', '');
    }

    /**
     * Percentage Discount
     *
     * @param $initial
     * @param $discounted
     *
     * @return float|int
     * @since 1.0.0
     *
     */
    private function percentageDiscount($initial, $discounted)
    {
        if ($discounted) {
            $percent = (($initial - $discounted) / $initial) * 100;

            if (0.00 === floatval($percent)) {
                return 100;
            } else {
                return $percent;
            }
        }
    }

    /**
     * Total
     *
     * @param $order
     * @param $summary
     *
     * @return mixed  The total order or total refunded
     * @since 1.0.0
     *
     */
    private function total($order, $summary = array())
    {
        if ('shop_order_refund' === $order->order_type) {

            $provider = ! empty($order->provider) ? $order->provider : 'woocommerce';
            $orderObj = OrderQuery::instance()->getProviderOrder($order->parent_id, $provider);

            $invoiceSent = $orderObj->get_invoice_sent($order->parent_id);

            $orderTotals = 0;
            if (! empty($summary)) {
                foreach ($summary as $index => $totals) {
                    foreach ($totals as $total) {
                        $orderTotals += ((float)$total['total'] + (float)$total['total_tax']);
                    }
                }

                if ('sent' === $invoiceSent) {
                    return $orderTotals;
                }
            }

            return $order->amount;
        } elseif ('shop_order' === $order->order_type) {
            return $order->total;
        }

        // Default totals
        return $order->total;
    }

    /**
     * Save xml files in temp dir
     *
     * @param $fileName
     *
     * @return string The file path
     * @since 1.1.0
     *
     */
    private function saveXmlFiles($fileName, $isXml, $xmlString = null)
    {
        $tempXmlDir = Plugin::getPluginDirPath('/') . "/tempXml/";
        if (! is_dir($tempXmlDir)) {
            wp_mkdir_p(trailingslashit($tempXmlDir));
            if (! is_writable($tempXmlDir)) {
                @chmod($tempXmlDir, 0755);
                if (! is_writable($tempXmlDir)) {
                    wp_die(esc_html__('The directory is not writable even after attempting to change permissions. Please check server settings.',
                            WC_EL_INV_TEXTDOMAIN) . ' DIR: ' . $tempXmlDir);
                }
            }
        }

        $file = Plugin::getPluginDirPath('/') . '/tempXml/' . $fileName . '.xml';

        ob_start();
        if (! $xmlString) {
            $xmlString = $isXml->xml_string;
        }

        if ('IT' === GeneralFields::getGeneralInvoiceOptionCountryState()) {
            // Stupid SimpleXMLElement!! replace "xmlns:p:FatturaElettronica" in "p:FatturaElettronica"
            $xmlString = preg_replace('/xmlns:p:FatturaElettronica/', 'p:FatturaElettronica', $xmlString);
        }

        file_put_contents($file, $xmlString);
        ob_get_clean();

        return $file;
    }

    /**
     * Delete Temp files
     *
     * @hooked wp_loaded
     *
     * @param bool $force
     *
     * @return bool
     * @since  1.1.0
     *
     */
    public static function deleteTempXmlFiles(bool $force = false): bool
    {
        $saveBulk = \WcElectronInvoice\Functions\filterInput($_GET, 'all', FILTER_UNSAFE_RAW);
        $remove   = \WcElectronInvoice\Functions\filterInput($_GET, 'removeTempFile', FILTER_UNSAFE_RAW);

        // Get xml file
        $tempXml = glob(
            Plugin::getPluginDirPath('/') . '/tempXml/*'
        );

        if (('true' === $saveBulk || 'true' === $remove) && ! empty($tempXml) ||
            $force && ! empty($tempXml)
        ) {
            foreach ($tempXml as $file) {
                $fileInfo = pathinfo($file);
                if ('index' === $fileInfo['filename'] && 'php' === $fileInfo['extension']) {
                    continue;
                }
                if ('invoice' === $fileInfo['filename']) {
                    continue;
                }
                if (file_exists($file) && is_file($file)) {
                    unlink($file);
                }
            }

            return true;
        }

        return false;
    }

    /**
     * Delete Temp Zip files
     *
     * @hooked wp_loaded
     *
     * @param bool $force
     *
     * @return bool
     * @since  1.1.0
     *
     */

    public static function deleteTempZipFiles(bool $force = false): bool
    {
        $saveBulk = \WcElectronInvoice\Functions\filterInput($_GET, 'all', FILTER_UNSAFE_RAW);
        $remove   = \WcElectronInvoice\Functions\filterInput($_GET, 'removeTempFile', FILTER_UNSAFE_RAW);

        // Get zip file
        $tempZip = glob(
            Plugin::getPluginDirPath('/') . '/tempZip/*'
        );

        if (('true' === $saveBulk || 'true' === $remove) && ! empty($tempZip) ||
            $force && ! empty($tempZip)
        ) {
            if (! empty($tempZip)) {
                foreach ($tempZip as $file) {
                    if (file_exists($file) && is_file($file)) {
                        $info = pathinfo($file);
                        if ('index' === $info['filename'] && 'php' === $info['extension']) {
                            continue;
                        }
                        unlink($file);
                    }
                }

                return true;
            }
        }

        return false;
    }

    /**
     * Create Zip
     *
     * @param array  $files
     * @param string $destination
     * @param bool   $overwrite
     *
     * @return bool
     * @since 1.1.0
     *
     */
    public static function createZip($files = array(), $destination = '', $overwrite = false)
    {
        // If the zip file already exists and overwrite is false, return false.
        if (file_exists($destination) && ! $overwrite) {
            return false;
        }

        // Vars
        $valid_files = array();
        // If files were passed in..
        if (is_array($files)) {
            // Cycle through each file.
            foreach ($files as $file) {
                // Make sure the file exists.
                if (file_exists($file)) {
                    $valid_files[] = $file;
                } else {
                    \WcElectronInvoice\Functions\log("POP [createZip] File not found: $file");
                }
            }
        }
        // If we have good files...
        if (count($valid_files)) {
            // Create the archive.
            $zip = new \ZipArchive();

            if ($zip->open($destination, $overwrite ? (\ZIPARCHIVE::OVERWRITE | \ZIPARCHIVE::CREATE) : \ZIPARCHIVE::CREATE) !== true) {
                \WcElectronInvoice\Functions\log("POP [createZip] Unable to open archive: $destination");

                return false;
            }

            // Add the files.
            $usedNames = [];

            foreach ($valid_files as $file) {
                $clearFile = basename($file);
                if (! isset($usedNames[$clearFile])) {
                    $usedNames[$clearFile] = 0;
                    $zipName               = $clearFile;
                } else {
                    $usedNames[$clearFile]++;
                    $fileInfo = pathinfo($clearFile);
                    $zipName  = $fileInfo['filename'] . '-' . $usedNames[$clearFile] . '.' . $fileInfo['extension'];
                }

                if (! $zip->addFile($file, $zipName)) {
                    \WcElectronInvoice\Functions\log("POP [createZip] Error while addFile: $file");
                }
            }
            // Close the zip -- done!
            $zip->close();

            // Check to make sure the file exists.
            return file_exists($destination);
        } else {
            \WcElectronInvoice\Functions\log("POP [createZip] No valid files found to include in the zip.");

            return false;
        }
    }

    /**
     * Filter Order data
     *
     * @param $ordersData
     *
     * @return mixed
     */
    public static function filterData($ordersData)
    {
        $orderID       = $ordersData['id'];
        $orderType     = $ordersData['order_type'];
        $orderRefunded = $ordersData['refunded'];
        $orderTotal    = $ordersData['total'];

        $provider = ! empty($ordersData['provider']) ? $ordersData['provider'] : 'woocommerce';
        $orderObj = OrderQuery::instance()->getProviderOrder($orderID, $provider);

        $sentInvoiceCheck = $orderObj->get_invoice_sent($orderID);

        if ('shop_order' === $orderType && 'no_sent' === $sentInvoiceCheck && ! empty($orderRefunded)) {
            $ordersData['total'] = $orderTotal - $orderRefunded['total_refunded'];
        }

        return $ordersData;
    }

    /**
     * Calc Unit price from total and total tax
     *
     * @param      $item
     * @param null $ordersData
     * @param null $vies
     *
     * @return float|int
     * @since 2.2.0
     */
    public function calcUnitPrice($item, $ordersData = null, $vies = null)
    {
        $total    = $item['total'] ?? 0;
        $subtotal = $item['subtotal'] ?? 0;
        $totalTax = $item['total_tax'] ?? 0;
        $quantity = $item['quantity'] ?? 1;

        $provider    = $item['provider'] ?? 'woocommerce';
        $taxProvider = TaxesQuery::instance()->getTaxProvider($provider);
        $decimals    = apply_filters('wc_el_inv-price_num_decimals', get_option('woocommerce_price_num_decimals', 2));

        // When working with 2 decimals I align the totals otherwise the sum
        // between total and total_tax creates discrepancies
        if (2 === (int)$decimals) {
            $total    = round($total, 2);
            $subtotal = round($subtotal, 2);
        }

        $unitTaxedPrice = 0;
        if (abs($subtotal) > abs($total) && $quantity > 0) {
            $unitTaxedPrice = (abs($subtotal) / abs($quantity));
        } else {
            if (abs($quantity) > 0) {
                // Gross price tax included
                $unitTaxedPrice = ((abs($total) + abs($totalTax)) / $quantity);
            }
        }

        // Vat
        $taxEnabled = $taxProvider->is_tax_enabled();
        $vat        = 0;
        if ('yes' === $taxEnabled && 0.00 !== floatval($totalTax)) {
            $vat = $this->numberFormat($this->taxRate($item), 2);
            if ($ordersData && $vies) {
                $vat = $this->viesValidTaxRate($ordersData, $vat, $vies);

                // Fix no woocommerce provider price no tax
                if (0.0 === floatval($vat) && 'woocommerce' !== $provider) {
                    $unitTaxedPrice = (abs($total) / $quantity);
                }
            }
        }

        if ($subtotal > $total) {
            $finalPrice = floatval($unitTaxedPrice);
        } else {
            // Net price excluding taxes
            $finalPrice = floatval($unitTaxedPrice) / (1 + (floatval($vat) / 100));
        }

        return $this->numberFormat($finalPrice, $decimals); // es: $unitTaxedPrice / 1,22 or 1.04
    }

    /**
     * Attachments Xml To Email
     *
     * @param        $attachments
     * @param        $emailID
     * @param        $order
     * @param string $type
     *
     * @return array|false|string
     * @since 3.0.0
     *
     */
    public function attachmentXmlToEmail($attachments, $emailID, $order, string $type = 'invoice')
    {
        $provider = 'woocommerce';

        // Send attachments via email ?
        $active          = OptionPage::init()->getOptions('invoice_via_email');
        $forceAttachment = in_array($emailID, array('custom_email_completed_order_xml_invoice'), true);

        if ('on' !== $active && ! $forceAttachment) {
            return $attachments;
        }

        $order = OrderQuery::instance()->getProviderOrder($order->get_id(), $provider);
        // check if all variables properly set
        if (! is_object($order) || ! isset($emailID)) {
            return $attachments;
        }

        // Skip User emails
        if ($order instanceof \WP_User) {
            return $attachments;
        }

        if (! is_subclass_of($order, '\WcElectronInvoice\Providers\Order')) {
            return $attachments;
        }

        $orderID = $order->get_id();

        if (! $orderID) {
            return $attachments;
        }

        // do not process low stock notifications, user emails etc!
        if (in_array($emailID, array(
                'no_stock',
                'low_stock',
                'backorder',
                'customer_new_account',
                'customer_reset_password',
            )) || getPostType($orderID) !== self::LIST_TYPE) {
            return $attachments;
        }

        if ('custom_email_completed_order_xml_invoice' === $emailID) {

            try {
                $key = OptionPage::getK();
                $url = esc_url_raw(add_query_arg([
                    self::LIST_TYPE => $orderID,
                    'provider'      => $provider,
                    'key'           => $key,
                    'format'        => 'xml',
                    'choice_type'   => $type,
                    'v'             => time(),
                ],
                    site_url('/' . \WcElectronInvoice\EndPoint\Endpoints::ENDPOINT)));
                // The request is finalized with an api call to api
                $xml     = wp_remote_get($url, \WcElectronInvoice\Functions\getSessionCookieArgs('application/xml'));
                $bodyXml = wp_remote_retrieve_body($xml);

                // Fallback for get XML
                if (! $bodyXml) {
                    // Get xml from order
                    // The request is finalized with an api call to api
                    $bodyXml = getXmlFromOrder($order);
                }

                // Is valid XML ?
                $isXml = isXml($bodyXml);

                // Not valid ?
                if (! $isXml->valid) {
                    return $attachments;
                }

                // File name
                $dataForFileName = array(
                    (object)array(
                        'id'       => $orderID,
                        'provider' => $provider,
                    ),
                );

                $VatNumber = preg_replace('/^' . preg_quote(GeneralFields::getGeneralInvoiceOptionCountryState(),
                        '/') . '/',
                    '', GeneralFields::getGeneralInvoiceOptionVatNumber()
                );
                $fileName  = GeneralFields::getGeneralInvoiceOptionCountryState() . $VatNumber . '_' . self::progressiveFileNumber($dataForFileName) . '.xml';

                // Return attachments
                return $this->buildAttachment($order, $fileName, $bodyXml);
            } catch (\Exception $e) {
                return '';
            }
        }

        return $attachments;
    }

    /**
     * Build Attachment
     *
     * @param $order
     * @param $fileName
     * @param $attachments
     *
     * @return array
     * @since 3.0.0
     *
     */
    public function buildAttachment($order, $fileName, $attachments)
    {
        if (! is_subclass_of($order, 'WcElectronInvoice\Providers\Order')) {
            return $attachments;
        }

        $xmlDir = Plugin::getPluginDirPath('/tempXml');
        if (! is_dir($xmlDir)) {
            wp_mkdir_p(trailingslashit($xmlDir));
        }
        if (! is_writable($xmlDir)) {
            @chmod($xmlDir, 0755);
            if (! is_writable($xmlDir)) {
                wp_die(esc_html__('The directory is not writable even after attempting to change permissions. Please check server settings.',
                        WC_EL_INV_TEXTDOMAIN) . ' DIR: ' . $xmlDir);
            }
        }

        // Get xml data & store in temp file
        $xmlData = $attachments;
        $xmlPath = trailingslashit($xmlDir) . 'invoice.xml';
        file_put_contents($xmlPath, $xmlData);

        // Initialize new attachments
        $attachments = array();

        // Copy temp file
        $tempFile = copy(
            $xmlPath,
            trailingslashit($xmlDir) . $fileName
        );

        if ($tempFile) {
            $attachments[] = trailingslashit($xmlDir) . $fileName;
        }

        return $attachments;
    }

    /**
     * viesCheckOrderMeta
     *
     * @param $order
     *
     * @return false
     */
    public static function viesCheckOrderMeta($order)
    {
        $provider = ! empty($order->provider) ? $order->provider : 'woocommerce';

        if (! is_subclass_of($order, 'WcElectronInvoice\Providers\Order')) {
            $orderObj = OrderQuery::instance()->getProviderOrder($order->id, $provider);
        } else {
            $orderObj = $order;
        }

        $orderViesCheck = false;
        // Order vies check meta data
        if (is_subclass_of($orderObj, 'WcElectronInvoice\Providers\Order')) {
            $orderViesCheck = $orderObj->get_meta('order_vies_check') ?: false;
        }

        return $orderViesCheck;
    }

    /**
     * Create
     *
     * @info  tool verifica xml: https://fex-app.com/servizi/verifica
     *
     * @param $ordersData
     *
     * @return string The file path
     * @since 1.0.0
     *
     */
    public function create($ordersData)
    {
        $requestedFormat = strtolower(trim((string)\WcElectronInvoice\Functions\filterInput($_GET, 'format', FILTER_UNSAFE_RAW)));
        $view       = \WcElectronInvoice\Functions\filterInput($_GET, 'view', FILTER_UNSAFE_RAW);
        $saveFile   = (bool)\WcElectronInvoice\Functions\filterInput($_GET, 'save', FILTER_UNSAFE_RAW);
        $saveBulk   = (bool)\WcElectronInvoice\Functions\filterInput($_GET, 'all', FILTER_UNSAFE_RAW);
        $choiceType = \WcElectronInvoice\Functions\filterInput($_GET, 'choice_type', FILTER_UNSAFE_RAW);

        // Block bulk download for Free e Basic
        $level = \WcElectronInvoice\Wizard::popGetUserLevel();
        if ($saveBulk && in_array($level, ['free', 'basic'], true)) {
            return __("Bulk XML download is not available for your plan.", WC_EL_INV_TEXTDOMAIN);
        }

        // GET variable
        $ordersData[0]->view        = $view ?: false;
        $ordersData[0]->save        = $saveFile ?: false;
        $ordersData[0]->save_bulk   = $saveBulk ?: false;
        $ordersData[0]->choice_type = $choiceType ?: 'invoice';

        // XML file name
        $VatNumber = preg_replace('/^' . preg_quote(GeneralFields::getGeneralInvoiceOptionCountryState(),
                '/') . '/',
            '', GeneralFields::getGeneralInvoiceOptionVatNumber()
        );
        $fileName  = GeneralFields::getGeneralInvoiceOptionCountryState() . $VatNumber . '_' . self::progressiveFileNumber($ordersData);

        $ordersData[0]->filename = $fileName;

        $style = Plugin::getPluginDirUrl('/assets/css/invoice/fatturaPA_v1.2.2.xsl');
        if ($style && $ordersData[0]->view) {
            $this->addProcessingInstruction(
                'xml-stylesheet',
                "type='text/xsl' href='{$style}'"
            );
        }

        if ('zugferd' === $requestedFormat || 'zugferd_zip' === $requestedFormat) {
            return $this->createFromCloud($ordersData, $requestedFormat);
        }

        return $this->createFromCloud($ordersData);
    }

    /**
     * createFromCloud
     *
     * @info API Request to api
     *
     * @param        $ordersData
     * @param string $returnFile
     *
     * @return string
     *
     */
    public function createFromCloud($ordersData, $returnFile = 'xml')
    {
        $paramArgs = \WcElectronInvoice\Functions\filterInput($_GET, 'param_args', FILTER_UNSAFE_RAW);
        $obj       = (object)$ordersData[0];

        if (! $paramArgs) {
            // Return local file if exist
            returnLocalFile(
                [
                    'id'             => $obj->id,
                    'order_provider' => $obj->order_provider,
                    'document_type'  => $obj->document_type ?? $obj->order_type ?? 'shop_order',
                    'parent_id'      => $obj->parent_id ?? 0,
                    'view'           => $obj->view ?? false,
                    'save'           => $obj->save ?? false,
                    'save_bulk'      => $obj->save_bulk ?? false,
                ], $returnFile);
        }

        // If receipt invoice_type is private
        if ('receipt' === $obj->choice_type) {
            $obj->invoice_type = 'private';
        }

        $docType = 'invoice';
        if ('shop_order_refund' === $obj->order_type) {
            $docType = 'credit_note';
        }

        if ('receipt' === $obj->choice_type) {
            $docType = 'receipt';
        }

        /**
         * BACS
         */
        $iban        = null;
        $bankName    = null;
        $accountName = null;
        $bacs        = get_option('woocommerce_bacs_accounts');
        if (! empty($bacs) && is_array($bacs)) {
            // First bacs account
            $bankName    = $bacs[0]['bank_name'];
            $accountName = $bacs[0]['account_name'];
            $iban        = $bacs[0]['iban'];
        }

        $nature             = apply_filters('wc_el_inv-nature_rc', TaxFields::getNature($obj), $obj);
        $refNorm            = apply_filters('wc_el_inv-ref_norm_rc', TaxFields::getRefN($obj), $obj);
        $provider           = ! empty($obj->provider) ? $obj->provider : 'woocommerce';
        $viesCheckOrderMeta = self::viesCheckOrderMeta($obj);
        $taxesProvider      = TaxesQuery::instance()->getTaxProvider($provider);

        /**
         * Get Vies data for check
         */
        $ordersProviderQuery       = \WcElectronInvoice\Providers\OrderQuery::instance();
        $providers                 = $ordersProviderQuery->get_providers();
        $premiumViesRuntimeEnabled = TaxFields::isPremiumViesReverseChargeEnabled();
        // Default vies check is active only when the premium runtime is available.
        $invoiceViesCheck = $premiumViesRuntimeEnabled
            ? (isWooCommerceActive() && count($providers) === 1 ? TaxFields::getViesCheck() : 'yes')
            : '';
        $vies             = false;
        if ('yes' === $invoiceViesCheck) {
            // Return vies meta data
            if ('yes' === $viesCheckOrderMeta || 1 === (int)$viesCheckOrderMeta || true === $viesCheckOrderMeta) {
                $vies = true;
            } else {
                $customerVatNumber = $this->customerVatNumber($ordersData);
                $customerCountry   = $this->customerCountry($ordersData);
                if ($customerVatNumber && $customerCountry) {
                    $vies = InvoiceFields::viesCheck($customerVatNumber, $customerCountry, true);
                }
            }
        }

        $transfereeCountryId = (string)$this->customerCountry($ordersData);

        // Prefix
        $prefix = OptionPage::init()->getOptions("prefix_{$docType}_number");

        // Suffix
        $suffix           = OptionPage::init()->getOptions('suffix_invoice_number');
        $suffixYear       = OptionPage::init()->getOptions('suffix_year_invoice_number');
        $suffixYearFormat = OptionPage::init()->getOptions('suffix_year_format_invoice_number') ?: 'Y';
        if ('on' === $suffixYear) {
            $suffix = date($suffixYearFormat);
        } else {
            $suffix = isset($suffix) && '' !== $suffix ? $suffix : '';
        }

        $level = \WcElectronInvoice\Wizard::popGetUserLevel();
        if ('credit_note' === $docType && ! \WcElectronInvoice\Functions\isCreditNotePlanEnabled($level)) {
            wp_die(esc_html__('Error: The free plan does not allow credit note creation.', WC_EL_INV_TEXTDOMAIN));
        }

        $shopCountry = GeneralFields::getGeneralInvoiceOptionCountryState();
        $isSdiCountryContext = Integrations::isSdiCountryContext($shopCountry);

        /**
         * Init params data
         */
        $params['license_key']        = OptionPage::init()->getOptions('license_key');
        $params['plugin_version']     = WC_EL_INV_VERSION;
        $params['site_title']         = get_bloginfo('name');
        $params['site_url']           = site_url();
        $params['user_agent_version'] = get_bloginfo('version');
        $params['user_agent']         = 'wordpress';
        $params['data']               = array(
            'id'                     => $obj->id,
            'parent_id'              => 'credit_note' === $docType ? $obj->parent_id : null,
            'order_provider'         => $obj->order_provider,
            'xml_style'              => Plugin::getPluginDirUrl('/assets/css/invoice/fatturaPA_v1.2.2.xsl'),
            'view'                   => $obj->view ?? false,
            'save'                   => $obj->save ?? false,
            'save_bulk'              => $obj->save_bulk ?? false,
            'filename'               => $obj->filename, // File name
            'type'                   => $docType, // Invoice, Receipt or Credit note
            'version'                => $this->version($ordersData),
            'sdi_type'               => (string) ($obj->sdi_type ?? ''),
            'customer_type'          => $obj->invoice_type,
            'nature'                 => apply_filters('wc_el_inv-nature', $nature),
            'ref_normative'          => apply_filters('wc_el_inv-ref_normative', $refNorm),
            'vies'                   => $vies,
            'vat_kind'               => apply_filters('wc_el_inv-vat_kind', null),
            // DatiTrasmissione
            'transmitter_data'       => array(
                'transmitter_id'      => array(
                    'country_id' => GeneralFields::getGeneralInvoiceOptionCountryState(),
                    'id_code'    => GeneralFields::getGeneralInvoiceTransmitterOptionVatNumber() ?: GeneralFields::getGeneralInvoiceOptionVatNumber(),
                ),
                'transmitter_format'  => $this->formatTransmission($ordersData),
                'sdi_code'            => $this->codeOrPec($ordersData, 'code'),
                'transmitter_contact' => array(
                    'phone' => GeneralFields::getGeneralInvoiceTransmitterOptionPhone() ?: GeneralFields::getGeneralInvoiceOptionPhoneNumber(),
                    'email' => GeneralFields::getGeneralInvoiceTransmitterOptionEmail() ?: GeneralFields::getGeneralInvoiceOptionEmailAddress(),
                ),
                'recipient_pec'       => $this->codeOrPec($ordersData, 'pec') ?? '',
            ),
            // CedentePrestatore
            'transfer_lender'        => array(
                'personal_data'    => array(
                    'tax_id_vat' => array(
                        'country_id' => GeneralFields::getGeneralInvoiceOptionCountryState(),
                        'id_code'    => GeneralFields::getGeneralInvoiceOptionVatNumber(),
                        'tax_regime' => GeneralFields::getGeneralInvoiceOptionTaxRegime(),
                    ),
                    //'company_name' => '',
                    //'first_name' => '',
                    //'last_name' => '',
                ),
                'place'            => array(
                    'address'     => $this->normalizeDocumentBusinessText(GeneralFields::getGeneralInvoiceOptionAddress(),
                        true),
                    'zip_code'    => GeneralFields::getGeneralInvoiceOptionPostCode(),
                    'city'        => $this->normalizeDocumentBusinessText(GeneralFields::getGeneralInvoiceOptionCity(),
                        true),
                    'province_id' => GeneralFields::getGeneralInvoiceOptionCountryProvince(),
                    'country_id'  => GeneralFields::getGeneralInvoiceOptionCountryState(),
                ),
                'rea_registration' => array(
                    'office'             => GeneralFields::getGeneralInvoiceOptionProvinceOfficeBusinessRegister() ?? '',
                    'number'             => GeneralFields::getGeneralInvoiceOptionReaNumber() ?? '',
                    'liquidation_status' => GeneralFields::getGeneralInvoiceOptionLiquidationStatus() ?? '',
                ),
                'contact'          => array(
                    'phone' => GeneralFields::getGeneralInvoiceOptionPhoneNumber() ?? '',
                    'email' => GeneralFields::getGeneralInvoiceOptionEmailAddress() ?? '',
                ),
            ),
            // CessionarioCommittente
            'transferee_client'      => array(
                'personal_data' => array(
                    'tax_id_vat'   => array(
                        'country_id' => $transfereeCountryId,
                        'id_code'    => $this->customerVatNumber($ordersData),
                    ),
                    'tax_id_code'  => $this->customerTaxCodeNumber($ordersData) ?? '',
                    'company_name' => $this->customerCompany($ordersData) ?? '',
                    'first_name'   => $this->customerName($ordersData) ?? '',
                    'last_name'    => $this->customerLastName($ordersData) ?? '',
                    'email'        => $this->customerEmail($ordersData) ?? '',
                ),
                'place'         => array(
                    'address'     => $this->customerAddress($ordersData),
                    'zip_code'    => $this->customerPostCode($ordersData),
                    'city'        => $this->customerCity($ordersData),
                    'province_id' => $this->customerState($ordersData),
                    'country_id'  => $transfereeCountryId,
                ),
            ),
            // FatturaElettronicaBody
            'invoice_body'           => array(
                // #2.1
                'general_data'          => array(
                    'doc_type'       => $this->documentType($obj),
                    'currency'       => apply_filters('wc_el_inv-xml_currency', 'EUR', $obj->currency),
                    'date'           => self::dateInvoice($obj),
                    'invoice_number' => $this->invoiceNumber($obj),
                    'invoice_prefix' => apply_filters("wc_el_inv-prefix_invoice", $prefix, $obj),
                    'invoice_suffix' => $suffix,
                ),
                // #2.1.1.7
                'provident_fund'        => array(//'amount_contribution_fund' => '',
                ),
                // #2.1.1.9
                'total_document_amount' => $this->numberFormat(
                    multiCurrencyConversion($this->total($obj), $obj->currency, 'EUR'),
                    2
                ),
            ),
            // DatiOrdineAcquisto
            'purchase_order_data'    => array(
                //'id'   => '',
                //'date' => '',
            ),
            // DatiFattureCollegate
            'connected_invoice_data' => array(
                //'id'   => '',
                //'date' => '',
            ),
            // DettaglioLinee
            'order_items'            => array(),
            // DatiPagamento
            'payment_data'           => array(
                'terms_payment'   => '',
                'payment_amount'  => '',
                'payment_details' => '',

                'beneficiary'           => '',
                'financial_institution' => '',
                'iban'                  => '',
            ),
        );

        if (! $isSdiCountryContext) {
            unset(
                $params['data']['version'],
                $params['data']['sdi_type'],
                $params['data']['transmitter_data'],
                $params['data']['vat_kind'],
                $params['data']['transfer_lender']['rea_registration'],
                $params['data']['xml_style']
            );
        }

        /**
         * CedentePrestatore: personal info
         */
        if (GeneralFields::getGeneralInvoiceOptionCompanyName()) {
            $params['data']['transfer_lender']['personal_data']['company_name'] = $this->normalizeDocumentBusinessText(GeneralFields::getGeneralInvoiceOptionCompanyName(),
                true);
        }
        if (! GeneralFields::getGeneralInvoiceOptionCompanyName()) {
            $params['data']['transfer_lender']['personal_data']['first_name'] = $this->normalizeDocumentBusinessText(GeneralFields::getGeneralInvoiceOptionName(),
                true);
            $params['data']['transfer_lender']['personal_data']['last_name']  = $this->normalizeDocumentBusinessText(GeneralFields::getGeneralInvoiceOptionSurname(),
                true);
        }

        /**
         * DatiFattureCollegate: Credit note -> Invoice connected
         */
        if ('shop_order_refund' === $obj->order_type) {
            $docID                                            = $this->docID($obj, 'connected_invoice');
            $params['data']['connected_invoice_data']['id']   = $docID;
            $params['data']['connected_invoice_data']['date'] = $this->dateCompleted($obj, true);
        } else {
            $docID                                         = $this->docID($obj);
            $params['data']['purchase_order_data']['id']   = "#{$docID}";
            $params['data']['purchase_order_data']['date'] = $this->dateCompleted($obj);
        }

        /**
         * Invoice Body > Provident fund (Rivalsa INPS)
         */
        if ('RF19' === GeneralFields::getGeneralInvoiceOptionTaxRegime() ||
            'RF02' === GeneralFields::getGeneralInvoiceOptionTaxRegime()
        ) {
            if (true === apply_filters('wc_el_inv-rivalsa_inps_enable', true, $obj)) {
                $addRivalsa = OptionPage::init()->getOptions('add_rivalsa_inps');
                if ('on' === $addRivalsa && 'invoice' === $docType) {
                    if (! empty($obj->items_fee)) {
                        $itemRefundFeeName = null;
                        foreach ($obj->items_fee as $itemFees) {
                            if (! empty($itemFees['refund_fee'])) {
                                $itemRefundFeeName = isset($itemFees['refund_fee']['name']) ? trim($itemFees['refund_fee']['name']) : '';
                            }
                        }
                        foreach ($obj->items_fee as $itemFees) {
                            if (! isset($itemFees['refund_fee']) && ! $itemRefundFeeName) {
                                $itemFeeName    = isset($itemFees['name']) ? trim($itemFees['name']) : '';
                                $rivalsaFeeName = apply_filters('wc_el_inv-rivalsa_inps_name', 'Rivalsa INPS');
                                if ($itemFeeName === $rivalsaFeeName) {
                                    $params['data']['invoice_body']['provident_fund']['amount_contribution_fund'] = $this->numberFormat($itemFees['total'],
                                        2);
                                }
                            }
                        }
                    }
                }
            }
        }

        /**
         * Order items (shop_order)
         */
        $noWCRefund = (! empty($obj->items) &&
                       empty($obj->current_refund_items) &&
                       'shop_order_refund' === $obj->order_type &&
                       apply_filters('wc_el_inv-provider_order_refund_use_order_items', 'woocommerce') !== $provider
        );
        if (! empty($obj->items) && 'shop_order' === $obj->order_type ||
            // (*) PMPro use order items for refund
            $noWCRefund
        ) {
            // ********************************************************************************************** //
            // Shop order items
            // ********************************************************************************************** //
            foreach ($obj->items as $item) {
                $id           = isset($item['variation_id']) && '0' !== $item['variation_id'] ? $item['variation_id'] : $item['product_id'];
                $itemTotalTax = $item['total_tax'];
                $gift         = getPostMeta('_active_gift_product', null, $id, true, 'product', $obj->provider);

                // product code
                $code = isset($id) && 0 !== (int)$id ? "{$id}" : $item['id'];

                // Total item refund
                if (0 === intval($item['quantity'])) {
                    continue;
                }

                // Remove product to zero total
                if (true === apply_filters('wc_el_inv-shop_items_remove_product_zero_total', false) &&
                    0.00 === floatval($item['total']) && 0.00 === floatval($item['subtotal'])
                ) {
                    continue;
                }

                // Set discount unit
                $discountUnit = $this->numberFormat((($item['subtotal'] - $item['total']) / abs($item['quantity'])));
                // Set Unit Price if have discount or not
                $PrezzoUnitario = multiCurrencyConversion($this->calcUnitPrice($item, $ordersData, $vies),
                    $obj->currency, 'EUR');

                if ('0' !== $obj->discount_total && $this->numberFormat($item['subtotal']) > $this->numberFormat($item['total'])) {
                    $discountType       = 'SC';
                    $discountPercentage = $this->numberFormat($this->percentageDiscount($item['subtotal'],
                        $item['total']), 2);
                    $discountUnit       = multiCurrencyConversion($discountUnit, $obj->currency, 'EUR');
                    $PrezzoTotale       = multiCurrencyConversion($item['total'], $obj->currency, 'EUR');
                } else {
                    $discountType       = null;
                    $discountPercentage = null;
                    $discountUnit       = null;
                    // Total
                    $total        = ($this->numberFormat($PrezzoUnitario) * abs($item['quantity']));
                    $PrezzoTotale = multiCurrencyConversion($total, $obj->currency, 'EUR');
                }

                /**
                 * 1. Company UE and valid VIES
                 * 2. Company and Private extra UE
                 * 3. All other cases (including EU companies with invalid VIES)
                 */
                // Regime forfettario / minimi
                if ('RF19' === GeneralFields::getGeneralInvoiceOptionTaxRegime() ||
                    'RF02' === GeneralFields::getGeneralInvoiceOptionTaxRegime()
                ) {
                    $itemRate = $this->numberFormat(0, 2);
                } elseif ('RF11' === GeneralFields::getGeneralInvoiceOptionTaxRegime()) {
                    // Agenzie viaggi e turismo (art.74-ter, DPR 633/72)
                    // Zero rate if total tax is zero
                    if ((0.00 === floatval($item['total_tax']))) {
                        $rate = 0;
                    } else {
                        $rate = $this->taxRate($item);
                    }
                    $itemRate = $this->numberFormat($rate, 2);
                } else {
                    // Reverse charge
                    if ($nature && true === $vies &&
                        in_array($this->customerCountry($ordersData), GeneralFields::getEuCountries(), true) &&
                        'IT' !== $this->customerCountry($ordersData)
                    ) {
                        $itemRate = $this->numberFormat(0, 2);
                    } elseif ($nature && ! in_array($this->customerCountry($ordersData),
                            GeneralFields::getEuCountries(), true)) {
                        $itemRate = $this->numberFormat(0, 2);
                    } else {
                        // Zero rate if total tax is zero
                        if ((0.00 === floatval($item['total_tax']))) {
                            $rate = 0;
                        } else {
                            $rate = $this->taxRate($item);
                        }
                        $itemRate = $this->numberFormat($rate, 2);
                    }
                }

                $PrezzoUnitario = apply_filters('wc_el_inv-shop_item_unit_price',
                    $PrezzoUnitario,
                    $obj->id, // order id
                    $obj->order_provider // provider
                );

                $PrezzoTotale = apply_filters('wc_el_inv-shop_item_total_price',
                    $PrezzoTotale,
                    $obj->id, // order id
                    $obj->order_provider // provider
                );

                // #2.2.1
                $params['data']['order_items'][] = array(
                    'item_code'        => array(
                        'type'  => apply_filters('wc_el_inv-CODE_TYPE', self::CODE_TYPE),
                        'value' => $code,
                    ),
                    'item_type'        => 'product',
                    'gift_product'     => $gift,
                    'description'      => $this->productDescription($item, $noWCRefund ? 'refund' : null),
                    'quantity'         => $this->numberFormat($item['quantity'], 2),
                    'unit'             => 'N.',
                    'discount_type'    => $discountType ?? '',
                    'discount_percent' => 0 < $discountPercentage ? $this->numberFormat($discountPercentage, 2) : '',
                    'discount_amount'  => $this->numberFormat($discountUnit, 2) ?? '',
                    'unit_price'       => $this->numberFormat($PrezzoUnitario, 2),
                    'total_price'      => $this->numberFormat($PrezzoTotale, 2),
                    'rate'             => $this->numberFormat($itemRate, 2),
                    'total_tax'        => abs($itemTotalTax),
                );
            }
            // Add order Fee
            if (! empty($obj->items_fee)) {
                $itemsFees    = $obj->items_fee;
                $itemFeeTotal = $itemFeeTotalTax = $refundFee = $rate = $itemFeeName = null;
                $addDutyLine  = false;
                // Set data fee
                foreach ($itemsFees as $key => $itemFees) {
                    if (! isset($itemFees['refund_fee'])) {
                        $itemFeeName = isset($itemFees['name']) ? trim($itemFees['name']) : '';
                        // Get tax by country
                        $country  = $this->customerCountry($ordersData);
                        $city     = strtoupper($this->customerCity($ordersData));
                        $taxClass = $itemFees['tax_class'] ?? null;

                        if ($taxClass) {
                            $taxes    = \WC_Tax::get_rates_for_tax_class($taxClass);
                            $taxRates = array();
                            foreach ($taxes as $tax) {
                                $taxRates = $tax;

                                // Get tax rate from taxes by country
                                if ($tax->tax_rate_country === $country &&
                                    0 === (int)$tax->city_count
                                ) {
                                    $taxRates = $tax;
                                    break;
                                }

                                // Get tax rate from taxes by country and city
                                if ($tax->tax_rate_country === $country &&
                                    0 < (int)$tax->city_count &&
                                    in_array($city, $tax->city, true)
                                ) {
                                    $taxRates = $tax;
                                    break;
                                }
                            }
                        }

                        if (empty($taxRates)) {
                            $taxRates = \WC_Tax::get_base_tax_rates();
                            if (! empty($taxRates)) {
                                $taxRate = reset($taxRates);
                                $rate    = is_array($taxRate) ? $taxRate['rate'] : $taxRate->tax_rate;
                            }
                        } elseif (is_object($taxRates)) {
                            $rate = $this->numberFormat($taxRates->tax_rate, 0);
                        } else {
                            if (! empty($taxRates)) {
                                $taxRate = reset($taxRates);
                                $rate    = is_array($taxRate) ? $taxRate['rate'] : $taxRate->tax_rate;
                            } else {
                                $rate = 0;
                            }
                        }

                        $itemFeeTotal    = isset($itemsFees[$key]['total']) ? $itemsFees[$key]['total'] : null;
                        $itemFeeTotalTax = isset($itemsFees[$key]['total_tax']) ? $itemsFees[$key]['total_tax'] : null;

                        // Zero rate if total tax is zero
                        if (0.00 === floatval($itemFeeTotalTax)) {
                            $rate = 0;
                        }
                    }
                    $refundFee = isset($itemFees['refund_fee']) ? $itemFees['refund_fee'] : null;

                    // Refund fee - recalculate fee total and total tax
                    if (! $refundFee && ! empty($refundFee)) {
                        $itemFeeTotal    = floatval($itemFeeTotal) - floatval($refundFee['total']);
                        $itemFeeTotalTax = floatval($itemFeeTotalTax) - floatval($refundFee['total_tax']);
                    }

                    // #2.2.1
                    $rivalsaFeeName   = apply_filters('wc_el_inv-rivalsa_inps_name', 'Rivalsa INPS');
                    $stampDutyFeeName = apply_filters('wc_el_inv-stamp_duty_name', 'Imposta di Bollo');
                    if ($stampDutyFeeName !== $itemFeeName && $rivalsaFeeName !== $itemFeeName) {
                        if (isset($itemFees['name']) && $itemFeeTotal) {
                            $itemFeeTotal = multiCurrencyConversion($itemFeeTotal, $obj->currency, 'EUR');
                            $itemRate     = '';
                            if (0.00 !== floatval($itemFeeTotal) || 0.00 !== floatval($itemFeeTotalTax)) {
                                /**
                                 * 1. Company UE and valid VIES
                                 * 2. Company and Private extra UE
                                 * 3. All other cases (including EU companies with invalid VIES)
                                 */
                                // Regime forfettario / minimi
                                if ('RF19' === GeneralFields::getGeneralInvoiceOptionTaxRegime() ||
                                    'RF02' === GeneralFields::getGeneralInvoiceOptionTaxRegime()
                                ) {
                                    $itemRate = $this->numberFormat(0, 2);
                                } elseif ('RF11' === GeneralFields::getGeneralInvoiceOptionTaxRegime()) {
                                    // Agenzie viaggi e turismo (art.74-ter, DPR 633/72)
                                    $itemRate = $this->numberFormat($rate, 2);
                                } else {
                                    // Reverse charge
                                    if ($nature && true === $vies &&
                                        in_array($this->customerCountry($ordersData),
                                            GeneralFields::getEuCountries(),
                                            true) &&
                                        'IT' !== $this->customerCountry($ordersData)
                                    ) {
                                        $itemRate = $this->numberFormat(0, 2);
                                    } elseif ($nature && ! in_array($this->customerCountry($ordersData),
                                            GeneralFields::getEuCountries(),
                                            true)) {
                                        $itemRate = $this->numberFormat(0, 2);
                                    } else {
                                        $itemRate = $this->numberFormat($rate, 2);
                                    }
                                }
                            }
                            $params['data']['order_items'][] = array(
                                'item_code'        => array(
                                    'type'  => apply_filters('wc_el_inv-CODE_TYPE', self::CODE_TYPE),
                                    'value' => apply_filters('wc_el_inv-CODE_FEE', self::CODE_FEE),
                                ),
                                'item_type'        => 'fee',
                                'description'      => $itemFeeName,
                                'quantity'         => $this->numberFormat('1', 2),
                                'unit'             => 'N.',
                                'discount_type'    => '',
                                'discount_percent' => '',
                                'discount_amount'  => '',
                                'unit_price'       => $this->numberFormat($itemFeeTotal, null, false),
                                'total_price'      => $this->numberFormat($itemFeeTotal, null, false),
                                'rate'             => $this->numberFormat($itemRate, 2),
                                'total_tax'        => $this->numberFormat($itemFeeTotalTax, null, false),
                            );
                        }
                    }

                    if (0.00 !== floatval($itemFeeTotal) || 0.00 !== floatval($itemFeeTotalTax)) {
                        // #2.2.1
                        $stampDutyFeeName = apply_filters('wc_el_inv-stamp_duty_name', 'Imposta di Bollo');
                        if ($stampDutyFeeName === $itemFeeName) {
                            // Virtual Duty charged to the customer
                            $addDuty            = OptionPage::init()->getOptions('add_stamp_duty');
                            $paidCustomerDuty   = OptionPage::init()->getOptions('stamp_duty_paid_customer');
                            $totalsNoTaxProduct = getNoTaxProductItemsTotals($obj->items);
                            if ('shop_order' === $obj->order_type &&
                                (
                                    (! $taxesProvider->is_tax_enabled() && ! $nature && ! $refNorm) ||
                                    ($totalsNoTaxProduct > 0.00) ||
                                    true === apply_filters('wc_el_inv-stamp_duty_by_customer_apply', false, $obj)
                                )
                            ) {
                                if ('on' === $addDuty && 'on' === $paidCustomerDuty) {
                                    if ((isset($obj->total_tax) && 0 === (int)$obj->total_tax) ||
                                        true === apply_filters('wc_el_inv-stamp_duty_by_customer_apply', false, $obj)
                                    ) {
                                        if (floatval($obj->total) > floatval('77.47') && ! $addDutyLine) {
                                            $addDutyLine = true;
                                            // #2.2.1
                                            $params['data']['order_items'][] = array(
                                                'item_code'        => array(
                                                    'type'  => apply_filters('wc_el_inv-CODE_TYPE', self::CODE_TYPE),
                                                    'value' => apply_filters('wc_el_inv-CODE_STAMP_DUTY',
                                                        self::CODE_STAMP_DUTY),
                                                ),
                                                'item_type'        => 'stamp_duty_fee',
                                                'description'      => __('Virtual stamp duty charged to the customer',
                                                    WC_EL_INV_TEXTDOMAIN),
                                                'quantity'         => $this->numberFormat('1', 2),
                                                'unit'             => 'N.',
                                                'discount_type'    => '',
                                                'discount_percent' => '',
                                                'discount_amount'  => '',
                                                'unit_price'       => $this->numberFormat(2),
                                                'total_price'      => $this->numberFormat(2),
                                                'rate'             => $this->numberFormat(0, 2),
                                                'total_tax'        => abs($obj->total_tax),
                                            );
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            // ********************************************************************************************** //
            // Not expected in the xml file, we manage it as a product!
            // Shipping rate tax
            // Shipping line
            $shippingTitle = '';
            if ('disabled' !== GeneralFields::getGeneralShippingLocation()) {
                if ($this->numberFormat(0) !== $this->numberFormat($obj->shipping_total)) {
                    // Check if is refund total shipping
                    $provider            = ! empty($obj->provider) ? $obj->provider : 'woocommerce';
                    $orderForShipping    = OrderQuery::instance()->getProviderOrder($obj->id, $provider);
                    $totalShipping       = floatval($orderForShipping->get_shipping_total());
                    $totalShippingTax    = floatval($orderForShipping->get_shipping_tax());
                    $shippingTotalRefund = false;
                    $refunded            = $refundedTax = 0;
                    $taxRates            = array();
                    foreach ($orderForShipping->get_items('shipping') as $itemID => $item) {
                        $shippingData  = $item->get_data();
                        $shippingTitle = $shippingData['name'];
                        // Get tax by country
                        $country  = $this->customerCountry($ordersData);
                        $city     = strtoupper($this->customerCity($ordersData));
                        $taxRates = $this->resolveShippingItemTaxRate($taxesProvider, $item, $country, $city);

                        if (! empty($taxRates) && is_object($taxRates)) {
                            $taxRateId = $this->resolveShippingItemTaxRateId($taxesProvider, $item, $taxRates);
                            // Refund total
                            $refunded = floatval($orderForShipping->get_total_refunded_for_item($itemID,
                                'shipping'));
                            // Refund tax
                            if (1 === intval($taxRates->tax_rate_shipping) && null !== $taxRateId) {
                                $refundedTax = floatval($orderForShipping->get_tax_refunded_for_item($itemID,
                                    $taxRateId,
                                    'shipping')
                                );
                            }
                        }

                        if ($refunded === $totalShipping && $refundedTax === $totalShippingTax) {
                            $shippingTotalRefund = true;
                            // Reset shipping total data
                            $obj->shipping_total = $this->numberFormat(0);
                        } else {

                            if (isset($obj->items_shipping[1]) && is_array($obj->items_shipping[1])) {
                                $dataShippingPartialRefund = $obj->items_shipping[1]['refund_shipping'];
                                $totalShipping             = $orderForShipping->get_shipping_total() - $dataShippingPartialRefund['total'];
                                $totalShippingTax          = $orderForShipping->get_shipping_tax() - $dataShippingPartialRefund['total_tax'];
                            }
                        }
                    }

                    if (! $shippingTotalRefund) {
                        $rate = 0;
                        // Rate based local tax
                        if (! empty($taxRates) &&
                            0.00 !== floatval($totalShippingTax)
                        ) {
                            if (is_object($taxRates)) {
                                $rate = $this->numberFormat($taxRates->tax_rate, 0);
                            } elseif (is_array($taxRates) && is_object($taxRates[0])) {
                                $rate = $this->numberFormat($taxRates[0]->tax_rate, 0);
                            }
                        } elseif (0.00 === floatval($totalShippingTax)) {
                            // Zero rate if total tax is zero
                            $rate = 0;
                        } else {
                            $rate = $this->shippingRate($provider);
                        }

                        /**
                         * Shipping description
                         */
                        $shippingDescForInvoice = $shippingTitle ?: 'Spedizione';
                        $shippingDescForInvoice = apply_filters('wc_el_inv-shipping_description_for_invoice',
                            'Spedizione' . ' ' . $shippingDescForInvoice);

                        $totalShipping = multiCurrencyConversion($totalShipping, $obj->currency, 'EUR');

                        /**
                         * 1. Company UE and valid VIES
                         * 2. Company and Private extra UE
                         * 3. All other cases (including EU companies with invalid VIES)
                         */
                        // Regime forfettario / minimi
                        if ('RF19' === GeneralFields::getGeneralInvoiceOptionTaxRegime() ||
                            'RF02' === GeneralFields::getGeneralInvoiceOptionTaxRegime()
                        ) {
                            $itemRate = $this->numberFormat(0, 2);
                        } elseif ('RF11' === GeneralFields::getGeneralInvoiceOptionTaxRegime()) {
                            // Agenzie viaggi e turismo (art.74-ter, DPR 633/72)
                            $itemRate = $this->numberFormat($rate, 2);
                        } else {
                            // Reverse charge
                            if ($nature && true === $vies &&
                                in_array($this->customerCountry($ordersData), GeneralFields::getEuCountries(),
                                    true) &&
                                'IT' !== $this->customerCountry($ordersData)
                            ) {
                                $itemRate = $this->numberFormat(0, 2);
                            } elseif ($nature && ! in_array($this->customerCountry($ordersData),
                                    GeneralFields::getEuCountries(), true)) {
                                $itemRate = $this->numberFormat(0, 2);
                            } else {
                                $itemRate = $this->numberFormat($rate, 2);
                            }
                        }

                        // #2.2.1
                        $params['data']['order_items'][] = array(
                            'item_code'        => array(
                                'type'  => apply_filters('wc_el_inv-CODE_TYPE', self::CODE_TYPE),
                                'value' => apply_filters('wc_el_inv-CODE_SHIPPING', self::CODE_SHIPPING),
                            ),
                            'item_type'        => 'shipping',
                            'description'      => $this->normalizeDocumentBusinessText($shippingDescForInvoice, true),
                            'quantity'         => $this->numberFormat('1', 2),
                            'unit'             => 'N.',
                            'discount_type'    => '',
                            'discount_percent' => '',
                            'discount_amount'  => '',
                            'unit_price'       => $this->numberFormat($totalShipping),
                            'total_price'      => $this->numberFormat($totalShipping),
                            'rate'             => $this->numberFormat($itemRate, 2),
                            'total_tax'        => abs($totalShippingTax),
                        );
                    }
                }
            }

            // ********************************************************************************************** //
            // Refund order
            // ********************************************************************************************** //
        } elseif (! empty($obj->current_refund_items) && 'shop_order_refund' === $obj->order_type) {
            // Set refund item meta data for product
            $refundItemMeta = array();
            if (! empty($obj->items)) {
                foreach ($obj->items as $item) {
                    $id                  = isset($item['variation_id']) && '0' !== $item['variation_id'] ? $item['variation_id'] : $item['product_id'];
                    $refundItemMeta[$id] = $item['meta_data'];
                }
            }

            // Refund items
            foreach ($obj->current_refund_items as $item) {
                // product code
                $productID = isset($item['product_id']) ? $item['product_id'] : null;
                $id        = isset($item['variation_id']) && '0' !== $item['variation_id'] ? $item['variation_id'] : $productID;
                $code      = isset($id) ? "{$id}" : '';
                $provider  = ! empty($obj->provider) ? $obj->provider : 'woocommerce';
                $product   = ProductsQuery::instance()->getProduct($id, $provider);
                // Get tax by country
                $country = $this->customerCountry($ordersData);

                // Refund product
                if ($product) {
                    // Zero rate if total tax is zero
                    if ((0.00 === floatval($item['total_tax']))) {
                        $rate = 0;
                    } else {
                        $rate = $this->taxRate($item);
                    }

                    // Set item meta data
                    $item['meta_data'] = $refundItemMeta[$id];

                    // Unit price
                    $unitPrice    = multiCurrencyConversion($this->calcUnitPrice($item, $ordersData, $vies),
                        $obj->currency, 'EUR');
                    $PrezzoTotale = multiCurrencyConversion(abs($item['total']), $obj->currency, 'EUR');

                    // Set rate for product_refund
                    if ('RF19' === GeneralFields::getGeneralInvoiceOptionTaxRegime() ||
                        'RF02' === GeneralFields::getGeneralInvoiceOptionTaxRegime()
                    ) {
                        $itemRate = $this->numberFormat(0, 2);
                    } elseif ('RF11' === GeneralFields::getGeneralInvoiceOptionTaxRegime()) {
                        // Agenzie viaggi e turismo (art.74-ter, DPR 633/72)
                        $itemRate = $this->numberFormat($rate, 2);
                    } else {
                        // Reverse charge
                        if ($nature && true === $vies &&
                            in_array($this->customerCountry($ordersData), GeneralFields::getEuCountries(),
                                true) &&
                            'IT' !== $this->customerCountry($ordersData)
                        ) {
                            $itemRate = $this->numberFormat(0, 2);
                        } elseif ($nature && ! in_array($this->customerCountry($ordersData),
                                GeneralFields::getEuCountries(),
                                true)) {
                            $itemRate = $this->numberFormat(0, 2);
                        } else {
                            $itemRate = $this->numberFormat($rate, 2);
                        }
                    }

                    // #2.2.1
                    $params['data']['order_items'][] = array(
                        'item_code'        => array(
                            'type'  => apply_filters('wc_el_inv-CODE_TYPE', self::CODE_TYPE),
                            'value' => $code,
                        ),
                        'item_type'        => 'product_refund',
                        'description'      => $this->productDescription($item, 'refund'),
                        'quantity'         => $this->numberFormat(abs($item['quantity']), 2),
                        'unit'             => 'N.',
                        'discount_type'    => '',
                        'discount_percent' => '',
                        'discount_amount'  => '',
                        'unit_price'       => $this->numberFormat($unitPrice),
                        'total_price'      => $this->numberFormat($PrezzoTotale),
                        'rate'             => $this->numberFormat($itemRate, 2),
                        'total_tax'        => abs($item['total_tax']),
                    );
                }

                // Refund Fee
                if (isset($item['refund_type']) && 'fee' === $item['refund_type']) {
                    $itemFeeName = $item['name'];
                    $total       = isset($item['total']) ? $item['total'] : 0;
                    $totalTax    = isset($item['total_tax']) ? $item['total_tax'] : 0;

                    $itemsFees = $obj->items_fee;
                    foreach ($itemsFees as $key => $itemFees) {
                        // Get fee data
                        if (! isset($itemFees['refund_fee'])) {
                            $totalTax = $itemFees['total_tax'] ?? null;
                        }

                        // Get tax class
                        $taxClass = $itemFees['tax_class'] ?? null;
                        $taxRates = array();
                        if ($taxClass) {
                            $taxes = $taxesProvider->get_rates_for_tax_class($taxClass);
                            $city  = strtoupper($this->customerCity($ordersData));
                            foreach ($taxes as $tax) {
                                $taxRates = $tax;

                                // Get tax rate from taxes by country
                                if ($tax->tax_rate_country === $country &&
                                    0 === (int)$tax->city_count
                                ) {
                                    $taxRates = $tax;
                                    break;
                                }

                                // Get tax rate from taxes by country and city
                                if ($tax->tax_rate_country === $country &&
                                    0 < (int)$tax->city_count &&
                                    in_array($city, $tax->city, true)
                                ) {
                                    $taxRates = $tax;
                                    break;
                                }
                            }
                        }

                        if (empty($taxRates)) {
                            $taxRates = $taxesProvider->get_rates_for_tax_class($taxClass);
                            if (! empty($taxRates)) {
                                $taxRate = reset($taxRates);
                                $rate    = is_array($taxRate) ? $taxRate['rate'] : $taxRate->tax_rate;
                            }
                        } elseif (is_object($taxRates)) {
                            $rate = $this->numberFormat($taxRates->tax_rate, 0);
                        } else {
                            if (is_array($taxRates)) {
                                $taxRate = reset($taxRates);
                                $rate    = is_array($taxRate) ? $taxRate['rate'] : $taxRate->tax_rate;
                            } else {
                                $rate = 0;
                            }
                        }

                        // Rate based local tax
                        if (! empty($taxRates) &&
                            is_object($taxRates) &&
                            (0.00 !== floatval($totalTax) || 0.00 !== abs($totalTax))
                        ) {
                            $rate = $this->numberFormat($taxRates->tax_rate, 0);
                        } else {
                            $rate = $item['tax_rate'];
                        }
                        if (0.00 === floatval($totalTax) || 0.00 === abs($totalTax)) {
                            // Zero rate if total tax is zero
                            $rate = 0;
                        }

                        // Set item fee data
                        if (isset($itemFees['name']) && $itemFeeName === $itemFees['name'] && $item['total']) {

                            $PrezzoUnitario = multiCurrencyConversion($item['total'], $obj->currency, 'EUR');
                            $PrezzoTotale   = multiCurrencyConversion($item['total'], $obj->currency, 'EUR');

                            // Set rate for fee_refund
                            if ('RF19' === GeneralFields::getGeneralInvoiceOptionTaxRegime() ||
                                'RF02' === GeneralFields::getGeneralInvoiceOptionTaxRegime()
                            ) {
                                $itemRate = $this->numberFormat(0, 2);
                            } elseif ('RF11' === GeneralFields::getGeneralInvoiceOptionTaxRegime()) {
                                $itemRate = $this->numberFormat($rate, 2);
                            } else {
                                // Reverse charge
                                if ($nature && true === $vies &&
                                    in_array($this->customerCountry($ordersData),
                                        GeneralFields::getEuCountries(),
                                        true) &&
                                    'IT' !== $this->customerCountry($ordersData)
                                ) {
                                    $itemRate = $this->numberFormat(0, 2);
                                } elseif ($nature && ! in_array($this->customerCountry($ordersData),
                                        GeneralFields::getEuCountries(),
                                        true)) {
                                    $itemRate = $this->numberFormat(0, 2);
                                } else {
                                    $itemRate = $this->numberFormat($rate, 2);
                                }
                            }

                            // #2.2.1
                            $params['data']['order_items'][] = array(
                                'item_code'        => array(
                                    'type'  => apply_filters('wc_el_inv-CODE_TYPE', self::CODE_TYPE),
                                    'value' => apply_filters('wc_el_inv-CODE_FEE', self::CODE_FEE),
                                ),
                                'item_type'        => 'fee_refund',
                                'description'      => esc_html__('Refund: ', WC_EL_INV_TEXTDOMAIN) . $item['name'],
                                'quantity'         => $this->numberFormat('1', 2),
                                'unit'             => 'N.',
                                'discount_type'    => '',
                                'discount_percent' => '',
                                'discount_amount'  => '',
                                'unit_price'       => $this->numberFormat($PrezzoUnitario),
                                'total_price'      => $this->numberFormat($PrezzoTotale),
                                'rate'             => $this->numberFormat($itemRate, 2),
                                'total_tax'        => abs($totalTax),
                            );
                        }
                    }
                }

                // Refund Shipping
                if (isset($item['method_id']) && isset($item['refund_type']) && 'shipping' === $item['refund_type']) {
                    $total    = $item['total'] ?? 0;
                    $totalTax = $item['total_tax'] ?? 0;

                    // Recalculate totals
                    if (isset($obj->items_shipping[1]) && is_array($obj->items_shipping[1])) {
                        $refundShipping = $obj->items_shipping[1];
                        $total          = $refundShipping['refund_shipping']['total'];
                        $totalTax       = $refundShipping['refund_shipping']['total_tax'];
                        if (0 === abs($totalTax)) {
                            $totalTax = $item['total_tax'] - $refundShipping['refund_shipping']['total_tax'];
                        }
                    }

                    // Shipping tax
                    $taxRates         = array();
                    $shippingTitle    = '';
                    $orderForShipping = OrderQuery::instance()->getProviderOrder($obj->id, $provider);

                    foreach ($orderForShipping->get_items('shipping') as $itemID => $shipItem) {
                        $shippingData  = $shipItem->get_data();
                        $shippingTitle = $shippingData['name'];
                        // Get tax by country
                        $country  = $this->customerCountry($ordersData);
                        $city     = strtoupper($this->customerCity($ordersData));
                        $taxRates = $this->resolveShippingItemTaxRate($taxesProvider, $shipItem, $country, $city);
                    }

                    // Rate based local tax
                    if (! empty($taxRates) &&
                        is_object($taxRates) &&
                        0.00 !== floatval($totalTax)
                    ) {
                        $shippingRate = $this->numberFormat($taxRates->tax_rate, 0);
                    } elseif (0.00 === floatval($totalTax) || 0.00 === abs($totalTax)) {
                        // Zero rate if total tax is zero
                        $shippingRate = 0;
                    } else {
                        $shippingRate = $this->shippingRate($provider);
                    }

                    /**
                     * Shipping description
                     */
                    $shippingDescForInvoice = $shippingTitle ?: 'Spedizione';
                    $shippingDescForInvoice = apply_filters('wc_el_inv-shipping_description_for_invoice',
                        'Spedizione' . ' ' . $shippingDescForInvoice);

                    $PrezzoUnitario = multiCurrencyConversion($total, $obj->currency, 'EUR');
                    $PrezzoTotale   = multiCurrencyConversion($total, $obj->currency, 'EUR');

                    // set rate for shipping_refund
                    if ('RF19' === GeneralFields::getGeneralInvoiceOptionTaxRegime() ||
                        'RF02' === GeneralFields::getGeneralInvoiceOptionTaxRegime()
                    ) {
                        $itemRate = $this->numberFormat(0, 2);
                    } elseif ('RF11' === GeneralFields::getGeneralInvoiceOptionTaxRegime()) {
                        $itemRate = $this->numberFormat($shippingRate, 2);
                    } else {
                        // Reverse charge
                        if ($nature && true === $vies &&
                            in_array($this->customerCountry($ordersData), GeneralFields::getEuCountries(),
                                true) &&
                            'IT' !== $this->customerCountry($ordersData)
                        ) {
                            $itemRate = $this->numberFormat(0, 2);
                        } elseif ($nature && ! in_array($this->customerCountry($ordersData),
                                GeneralFields::getEuCountries(),
                                true)) {
                            $itemRate = $this->numberFormat(0, 2);
                        } else {
                            $itemRate = $this->numberFormat($shippingRate, 2);
                        }
                    }

                    // #2.2.1
                    $params['data']['order_items'][] = array(
                        'item_code'        => array(
                            'type'  => apply_filters('wc_el_inv-CODE_TYPE', self::CODE_TYPE),
                            'value' => apply_filters('wc_el_inv-CODE_SHIPPING', self::CODE_SHIPPING),
                        ),
                        'item_type'        => 'shipping_refund',
                        'description'      => $this->normalizeDocumentBusinessText(
                            esc_html__('Refund: ', WC_EL_INV_TEXTDOMAIN) . $shippingDescForInvoice,
                            true
                        ),
                        'quantity'         => $this->numberFormat('1', 2),
                        'unit'             => 'N.',
                        'discount_type'    => '',
                        'discount_percent' => '',
                        'discount_amount'  => '',
                        'unit_price'       => $this->numberFormat($PrezzoUnitario),
                        'total_price'      => $this->numberFormat($PrezzoTotale),
                        'rate'             => $this->numberFormat($itemRate, 2),
                        'total_tax'        => abs($totalTax),
                    );
                }
            }
        }
        // No items refunded (only WC provider)
        if ('woocommerce' === $provider && 'shop_order_refund' === $obj->order_type &&
            empty($obj->current_refund_items) &&
            floatval(abs($obj->amount)) === floatval(abs($obj->refunded['total_refunded']))
        ) {
            $PrezzoUnitario = multiCurrencyConversion($obj->amount, $obj->currency, 'EUR');
            $PrezzoTotale   = multiCurrencyConversion(abs($obj->total), $obj->currency, 'EUR');

            // #2.2.1
            $params['data']['order_items'][] = array(
                'item_code'        => array(
                    'type'  => apply_filters('wc_el_inv-CODE_TYPE', self::CODE_TYPE),
                    'value' => apply_filters('wc_el_inv-CODE_SHIPPING', self::CODE_REFUND),
                ),
                'item_type'        => 'total_refunded',
                'description'      => sprintf('%s %s', esc_html__('Refund: ', WC_EL_INV_TEXTDOMAIN), "{$obj->reason}"),
                'quantity'         => $this->numberFormat('1', 2),
                'unit'             => 'N.',
                'discount_type'    => '',
                'discount_percent' => '',
                'discount_amount'  => '',
                'unit_price'       => $this->numberFormat($PrezzoUnitario),
                'total_price'      => $this->numberFormat($PrezzoTotale),
                'rate'             => '',
                'total_tax'        => '',
            );
        }

        // Used for ImportoPagamento
        $summaryRate = array();
        if (! empty($params['data']['order_items'])) {
            foreach ($params['data']['order_items'] as $item) {
                $lineItem                       = (object)$item;
                $summaryRate[$lineItem->rate][] = [
                    'total'     => $this->numberFormat((float)$lineItem->total_price - (float)$lineItem->discount_amount),
                    'total_tax' => (float)$lineItem->total_tax,
                ];
            }
        }

        /**
         * payment_data
         */
        $params['data']['payment_data']['terms_payment'] = 'TP02';
        $payMethod                                       = $this->paymentMethod($obj);
        if ('MP05' === $payMethod) {
            if ($accountName) {
                $params['data']['payment_data']['beneficiary'] = $accountName;
            }
        }
        $params['data']['payment_data']['payment_details'] = $payMethod;

        // Total Payments
        if ('shop_order_refund' === $obj->order_type &&
            ! empty($obj->refunded) &&
            '0' !== $obj->refunded['total_refunded']
        ) {
            // Current total refunded
            $totalRefundedAmount                              = $obj->amount;
            $ImportoPagamento                                 = multiCurrencyConversion($totalRefundedAmount,
                $obj->currency, 'EUR');
            $params['data']['payment_data']['payment_amount'] = $this->numberFormat($ImportoPagamento, 2);
        } else {
            $ImportoPagamento                                 = multiCurrencyConversion($this->total($obj,
                $summaryRate), $obj->currency, 'EUR');
            $params['data']['payment_data']['payment_amount'] = $this->numberFormat($ImportoPagamento, 2);
        }

        if ('MP05' === $payMethod) {
            if ($bankName) {
                $params['data']['payment_data']['financial_institution'] = $bankName;
            }
            if ($iban) {
                $params['data']['payment_data']['iban'] = $iban;
            }
        }

        /**
         * API POST request
         */
        $getXmlState         = \WcElectronInvoice\Functions\filterInput($_GET, 'xml_state', FILTER_UNSAFE_RAW);
        $invoiceCountryState = GeneralFields::getGeneralInvoiceOptionCountryState();

        if ($getXmlState) {
            $invoiceCountryState = $getXmlState;
        }

        $languageOverride = \WcElectronInvoice\Functions\resolveDocumentLanguageOverride($transfereeCountryId);

        /**
         * Params lists for override data
         */
        $params['data']['overrides'] = [
            'bollo_force_apply' => apply_filters('wc_el_inv-stamp_duty_force_apply', false),
            'language'          => apply_filters('wc_el_inv-language_force_apply', $languageOverride,
                $transfereeCountryId, $obj, $ordersData),
            'client_lang'       => \WcElectronInvoice\Functions\resolveWpLocaleLanguageCode(),
        ];

        $action                  = \WcElectronInvoice\Functions\filterInput($_POST, 'action', FILTER_UNSAFE_RAW);
        $method                  = \WcElectronInvoice\Functions\filterInput($_POST, 'method', FILTER_UNSAFE_RAW);
        $backgroundSyncApiRoute  = self::resolveBackgroundIntegrationRoute($action, $method);

        $debugData = $params['data'] ?? [];
        \WcElectronInvoice\Functions\log(sprintf(
            '[createFromCloud] order #%s (%s) → sdi_type=%s | customer_type=%s | first_name=%s | last_name=%s | company=%s | country=%s | vat=%s | tax_code=%s | items=%d',
            $debugData['id'] ?? '?',
            $debugData['type'] ?? '?',
            $debugData['sdi_type'] ?? 'NULL',
            $debugData['customer_type'] ?? '?',
            $debugData['transferee_client']['personal_data']['first_name'] ?? '',
            $debugData['transferee_client']['personal_data']['last_name'] ?? '',
            $debugData['transferee_client']['personal_data']['company_name'] ?? '',
            $debugData['transferee_client']['place']['country_id'] ?? '',
            $debugData['transferee_client']['personal_data']['tax_id_vat']['id_code'] ?? '',
            $debugData['transferee_client']['personal_data']['tax_id_code'] ?? '',
            count($debugData['order_items'] ?? [])
        ));

        if ('xml' === $returnFile && $backgroundSyncApiRoute) {
            return popApi($backgroundSyncApiRoute, ['method' => 'POST'], $params, 'string');
        }

        if ('xml' === $returnFile || 'zugferd' === $returnFile || 'zugferd_zip' === $returnFile) {
            /**
             * Request for XML or ZUGFeRD bundle (single hybrid PDF or full ZIP).
             */

            $apiRoute = self::resolveXmlApiRouteForCountry($invoiceCountryState);

            $result = false;
            if ($apiRoute) {
                // zugferd_zip reuses the same API call as zugferd; files are saved to disk
                // by popApi() and we build the ZIP from the cached paths afterwards.
                $apiReturnFile = 'zugferd_zip' === $returnFile ? 'zugferd' : $returnFile;
                $result        = popApi($apiRoute, ['method' => 'POST'], $params, $apiReturnFile);
            } elseif ('zugferd' === $returnFile || 'zugferd_zip' === $returnFile) {
                \WcElectronInvoice\Functions\log(
                    "createFromCloud > Missing ZUGFeRD API route for country {$invoiceCountryState}\n"
                );
                echo esc_html__('ZUGFeRD API route not configured.', WC_EL_INV_TEXTDOMAIN);
                die();
            }

            if ('zugferd' === $returnFile) {
                // Single ZUGFeRD hybrid PDF download.
                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                echo trim((string)$result);
                die();
            }

            if ('zugferd_zip' === $returnFile) {
                // Files were saved to disk by popApi() above; read all three from cache
                // and serve as a ZIP. No additional API call — no extra credits consumed.
                $providerOrder = \WcElectronInvoice\Providers\OrderQuery::instance()
                    ->getProviderOrder($obj->id, $obj->order_provider ?? 'woocommerce');
                if ($providerOrder) {
                    $cachedPaths = \WcElectronInvoice\Functions\getCachedDocumentAttachmentPaths($providerOrder);
                    if (count($cachedPaths) >= 3) {
                        $tempZipDir = Plugin::getPluginDirPath('/') . 'tempZip/';
                        if (! is_dir($tempZipDir)) {
                            wp_mkdir_p(trailingslashit($tempZipDir));
                        }
                        $zipFileName = sanitize_file_name(($obj->filename ?? ('zugferd-bundle-' . $obj->id)) . '-bundle.zip');
                        $zipPath     = $tempZipDir . $zipFileName;
                        if (self::createZip(array_values($cachedPaths), $zipPath, true)) {
                            header('Content-Type: application/zip');
                            header('Content-Disposition: attachment; filename="' . $zipFileName . '"');
                            header('Content-Length: ' . filesize($zipPath));
                            readfile($zipPath);
                            @unlink($zipPath);
                            exit;
                        }
                    }
                }
                die();
            }

            // Is valid XML ?
            $isXml = isXml($result);

            // No valid XML?
            // Check integration response (api return JSON not XML)
            if (! $isXml->valid &&
                // Ajax or Cron
                (defined('DOING_AJAX') && DOING_AJAX || defined('DOING_CRON') && DOING_CRON)
            ) {
                $integrationRes = json_decode($result);
                if (is_object($integrationRes) && 'SDI_via_POP' === $integrationRes->integration) {
                    $order = OrderQuery::instance()->getProviderOrder($obj->id, $obj->order_provider);

                    if (! $integrationRes->success) {
                        if (! empty($integrationRes->response->data->violations ?? [])) {
                            $detail     = $integrationRes->response->data->detail ?? '';
                            $violations = (array)($integrationRes->response->data->violations ?? []);

                            if (! empty($violations)) {
                                $messages = array_map(function ($violation) use ($detail) {
                                    // forza ogni $violation a oggetto per evitare warning
                                    $violation = (object)$violation;

                                    return sprintf(
                                        "%s:\n%s\n%s\n%s\n%s",
                                        $detail ?? '',
                                        $violation->message ?? '',
                                        $violation->propertyPath ?? '',
                                        $violation->propertyContent ?? '',
                                        $violation->code ?? '',
                                    );
                                }, $violations);

                                $messageViolations = implode("\n", $messages);
                                $order->update_meta('sdi_pop-invoice_validation_errors', $messageViolations);
                            }
                        }

                        return json_encode($integrationRes);
                    }
                    if (! empty($integrationRes->data)) {
                        $order->delete_meta('sdi_pop-invoice_validation_errors');

                        return $result;
                    }
                } elseif (is_object($integrationRes) && 'Peppol_via_POP' === $integrationRes->integration) {
                    return json_encode($integrationRes);
                } elseif (is_object($integrationRes) && 'KSeF_via_POP' === $integrationRes->integration) {
                    return json_encode($integrationRes);
                } elseif (is_object($integrationRes) && ! empty($integrationRes->connector)) {
                    // n8n connector response — echo so ob_get_clean() captures it
                    echo json_encode($integrationRes);
                    return;
                } elseif (is_object($integrationRes) && property_exists($integrationRes, 'pop_webhook')) {
                    // generic sendWebhook response — echo so ob_get_clean() captures it
                    echo json_encode($integrationRes);
                    return;
                }
            }

            if (isset($params['data']['save_bulk']) && true === $params['data']['save_bulk']) {
                // Not valid ?
                if (! $isXml->valid) {
                    $overquotaPayload = \WcElectronInvoice\Functions\extractPopApiOverquotaUiPayload($isXml->xml_string);
                    if (is_array($overquotaPayload)) {
                        @header('Content-Type: text/html; charset=' . get_option('blog_charset'));
                        echo \WcElectronInvoice\Functions\buildPopApiOverquotaHtmlFallback($overquotaPayload);
                        die;
                    }
                    $documentErrorPayload = \WcElectronInvoice\Functions\extractPopApiDocumentErrorHtmlPayload($isXml->xml_string);
                    if (is_array($documentErrorPayload)) {
                        @header('Content-Type: text/html; charset=' . get_option('blog_charset'));
                        echo \WcElectronInvoice\Functions\buildPopApiDocumentErrorHtmlFallback($documentErrorPayload);
                        die;
                    }
                    $isXml->error_message = esc_html__('Request error: xml not valid', WC_EL_INV_TEXTDOMAIN);
                    // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                    echo print_r($isXml, true);
                    die;
                }

                // Save files in temp dir
                // return the file path
                return $this->saveXmlFiles($params['data']['filename'], $isXml, $result);
            } else {

                $action = \WcElectronInvoice\Functions\filterInput($_POST, 'action', FILTER_UNSAFE_RAW);
                if ('processFileBatch' === $action && defined('DOING_AJAX') && DOING_AJAX) {
                    return $this->saveXmlFiles($params['data']['filename'], $isXml, $result);
                }

                // Not valid ?
                if (! $isXml->valid) {
                    $overquotaPayload = \WcElectronInvoice\Functions\extractPopApiOverquotaUiPayload($isXml->xml_string);
                    if (is_array($overquotaPayload)) {
                        @header('Content-Type: text/html; charset=' . get_option('blog_charset'));
                        echo \WcElectronInvoice\Functions\buildPopApiOverquotaHtmlFallback($overquotaPayload);
                        die;
                    }
                    $documentErrorPayload = \WcElectronInvoice\Functions\extractPopApiDocumentErrorHtmlPayload($isXml->xml_string);
                    if (is_array($documentErrorPayload)) {
                        @header('Content-Type: text/html; charset=' . get_option('blog_charset'));
                        echo \WcElectronInvoice\Functions\buildPopApiDocumentErrorHtmlFallback($documentErrorPayload);
                        die;
                    }
                    $isXml->error_message = esc_html__('Request error: xml not valid', WC_EL_INV_TEXTDOMAIN);
                    // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                    echo print_r($isXml, true);
                    die;
                }

                // Single xml
                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                echo trim($result);
            }
        } elseif ('pdf' === $returnFile) {
            /**
             * Request for PDF
             */
            // Customer country
            $country = $this->customerCountry($ordersData) !== null
                ? strtoupper($this->customerCountry($ordersData))
                : null;

            $countryToLocale = Plugin::$countryToLocale;
            $popLocale       = $countryToLocale[$country] ?? 'en_GB';
            if ($popLocale) {
                add_filter('plugin_locale', function ($locale, $domain) use ($popLocale) {
                    if ('el-inv' === $domain) {
                        return $popLocale;
                    }

                    return $locale;
                }, PHP_INT_MAX, 2);
                Plugin::load_plugin_textdomain();
            }

            $options     = \WcElectronInvoice\Admin\Settings\OptionPage::init();
            $logoUrl     = $options->getOptions('invoice_pdf_logo_url');
            $invoiceHtml = $options->getOptions('invoice_html');

            $docTypeTitle = '';
            switch ($obj->order_type) {
                case 'shop_order':
                    $docTypeTitle = esc_html__('Invoice', WC_EL_INV_TEXTDOMAIN);
                    if ('receipt' === $obj->choice_type) {
                        $docTypeTitle = esc_html__('Receipt', WC_EL_INV_TEXTDOMAIN);
                    }
                    break;
                case 'shop_order_refund':
                    $docTypeTitle = esc_html__('Credit note', WC_EL_INV_TEXTDOMAIN);
                    break;
                default:
                    break;
            }

            $docTypeTitle = apply_filters('wc_el_inv-pdf_docType_title', $docTypeTitle, $obj);

            /**
             * Action hook for Extra data
             */
            ob_start();
            do_action('wc_el_inv-invoice_pdf_head_general_extra_data');
            $extraData = ob_get_clean();

            // @TODO semplificare il payload per il PDF usando i dati presenti per la fatturazione XML
            //       qui quelli veramente necessari sono:
            //       - invoice_html, doc_type_title, logo_url, head[], footer_text,
            //       (*) nature_rc, ref_norm_rc li possiamo prendere da "data" ref_n5_normative (opzionale per N5) lo possiamo passare su "data"
            //       da eliminare [billing, shipping]
            //       **************************************************************************************************
            //       ### >>>>> dopo le modifiche allineare i dati ne template dentro pop-cloud-api per il PDF <<<<< ###
            //       **************************************************************************************************

            /**
             * Extra params for PDF
             */
            $params['data']['pdf']['invoice_html']     = $invoiceHtml;
            $params['data']['pdf']['doc_type_title']   = $docTypeTitle;
            $params['data']['pdf']['logo_url']         = $logoUrl ?: false;
            $params['data']['pdf']['head']             = array(
                'store_info_extra_data' => $extraData,
                'store_info_address'    => GeneralFields::getGeneralInvoiceOptionAddress() . ' ' .
                                           GeneralFields::getGeneralInvoiceOptionCity() . ' ' .
                                           GeneralFields::getGeneralInvoiceOptionPostCode() . ' ' .
                                           GeneralFields::getGeneralInvoiceOptionCountryState() . ' ' .
                                           GeneralFields::getGeneralInvoiceOptionCountryProvince(),
            );
            $params['data']['pdf']['billing']          = $obj->billing;
            $params['data']['pdf']['shipping']         = $obj->shipping ?: array();
            $params['data']['pdf']['nature_rc']        = $nature;
            $params['data']['pdf']['ref_norm_rc']      = $refNorm;
            $params['data']['pdf']['ref_n5_normative'] = getN5RefNormative();
            $params['data']['pdf']['total_tax']        = $obj->total_tax; // Order total tax
            $params['data']['pdf']['footer_text']      = $options->getOptions('invoice_pdf_footer', true);

            if (defined('PDF_API_ROUTE') && PDF_API_ROUTE) {
                $result = popApi(PDF_API_ROUTE, ['method' => 'POST'], $params, $returnFile);
                // Single PDF
                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                echo trim($result);
                // die for return PDF file
                die();
            }
        }
    }
}
