Skip to content

Developer notes

Overview · Installation · Configuration · Usage · FAQ

The add-on never calculates VAT. It switches on CS-Cart’s own tax exemption arithmetic for one calculation of one cart:

  1. calculate_cart_taxes_pre: the decision (Decision::forCart()) is stored in $cart['kv_vat']. When the reverse charge applies, the local $auth['tax_exempt'] of fn_calculate_cart_content() and $cart['user_data']['tax_exempt'] are set to Y (the original value is remembered).
  2. Core then drops added taxes and, for the unit price method, takes included taxes out of the product and shipping prices.
  3. calculate_taxes_post: for the subtotal method (where core keeps included taxes in the price for exempt customers) the add-on sets the per-product tax_summary.included and nets the chosen shipping rate, the same way core does for the unit price method.
  4. calculate_cart_post: the cart’s tax_exempt flag is restored, so nothing is saved on the customer, the profile or the order (orders.tax_exempt stays as it was).
  5. calculate_payment_taxes_pre/_post: the same for payment surcharge taxes.
  6. place_order: the decision is stored in ?:kv_vat_orders for the order and for every Multi-Vendor sub-order.
  • Countries: prefixes, normalisation, format patterns, check digits (AT, BE, DE, DK, EE, EL, FI, FR, HR, HU, IT, LU, NL, PL, PT, SE, SI, SK). Check digits only matter when VIES cannot be reached.
  • Vies: POST https://ec.europa.eu/taxation_customs/vies/rest-api/check-vat-number with the store VAT number as requester (requestIdentifier = consultation number). Errors (actionSucceed: false, errorWrappers[].error) other than INVALID_INPUT count as “not reachable”.
  • Hmrc: OAuth client credentials, GET /organisations/vat/check-vat-number/lookup/{vrn}[/{requester}], Accept: application/vnd.hmrc.2.0+json.
  • Checker: cache ?:kv_vat_checks (valid: setting, invalid: 1 h, outage: 10 min), per-request memo, log ?:kv_vat_log for every live lookup.
Table Contents
?:kv_vat_checks Last answer per (prefix, number) with expiry
?:kv_vat_log Live lookups: date, number, result, error, name, consultation number, context, user, order, company, duration
?:kv_vat_orders Per order: number, buyer and store country, result, outcome, reverse charge, review flag, VIES name/address, consultation number, request date, re-check data

The number itself is a core profile field (?:profile_fields.field_name = kv_vat_number, section C), stored by core for users and orders.

Dispatch Area Purpose
kv_eu_vat_validation.check (GET, JSON, Cache-Control: no-store) Storefront Live check: vat, country → outcome, tone, message, name, reverse_charge. 30 checks per session and hour.
kv_eu_vat_validation.manage Admin Log page
kv_eu_vat_validation.check / recheck / recheck_order / mark_reviewed / prune (POST) Admin Manual check, re-checks, review, log pruning

PHP: calculate_cart_taxes_pre, calculate_taxes_post, calculate_cart_post, calculate_payment_taxes_pre, calculate_payment_taxes_post, place_order, get_order_info (adds $order_info.kv_vat), get_orders (filter param kv_vat), delete_order, template_snippet_render_post (document lines), update_company (vendor numbers). All exist with the same arguments in 4.17.2 and 4.21.2.

Templates: storefront checkout:summary, checkout:checkout_totals, orders:order_total (pre), index:scripts, index:styles; admin orders:details_sidebar (pre), orders:totals_content, orders:advanced_search; classic invoices (storefront and admin mail templates) orders:invoice_customer_info, orders:invoice_company_info.

The lines are appended to the bill_to and company_address snippets automatically. For a custom layout, switch Print … on invoices off and use the document variable kv_vat:

{% if kv_vat.number %}VAT number: {{ kv_vat.number }}{% endif %}
{% if kv_vat.reverse_charge %}{{ kv_vat.note }} ({{ kv_vat.consultation }}){% endif %}
{% if kv_vat.seller_vat %}Our VAT number: {{ kv_vat.seller_vat }}{% endif %}

Fields: number, reverse_charge, note, consultation, status, seller_vat. E-mail templates can use order_info.kv_vat the same way where the order data is available.

The storefront prints only static configuration (field ID, endpoint, texts). The live check is an XHR to a non-cacheable dispatch; the reverse charge lines appear only on checkout and order pages, which are never cached. No ?:storage_data writes.

KV_EUVAT_VIES_URL and KV_EUVAT_HMRC_URL (in local_conf.php, localhost URLs only) point the clients at a local mock for tests.