A Bulgarian online shop sells to companies sooner than it expects. An office orders coffee, a workshop orders tools, a nursery orders paper, and each of them needs an invoice made out to the company. WooCommerce has no field for a company identifier, so shops usually solve it with an order note ("please invoice Primer EOOD, EIK ...") or a phone call. Both end the same way: a typo in a nine-digit number, an invoice issued again, and an accountant chasing details at the end of the month. This guide shows how to collect invoice details properly in the WooCommerce checkout, what can be checked automatically and where the details should end up.
#What to ask for, and when
A company invoice in Bulgaria is usually made out with these details:
- the company name, as registered;
- the EIK, the company's identifier in the Commercial Register, or its BULSTAT code;
- the VAT number, if the company is registered for VAT;
- the responsible person (МОЛ), which many accountants still put on invoices;
- the registered address, which is often not the delivery address.
Which of these your invoices must contain, and which are only customary, is a question for your accountant: the VAT Act sets the content of an invoice, and practice differs between VAT-registered and unregistered sellers. The checkout's job is simpler. It should collect what your accountant asks for, cleanly, and only from the customers who want an invoice.
That last part matters. Most orders come from people who do not need an invoice, and five extra fields in front of them cost sales. The usual pattern is a single checkbox, "I need an invoice", that opens the fields. A second choice, "Invoice to: a company / me as an individual", covers private customers who want an invoice in their own name: for them the billing name and address are enough, and the company fields stay closed.
#Block checkout and classic checkout are different worlds
Many older guides add invoice fields with the woocommerce_checkout_fields filter. That still works in the classic checkout (the [woocommerce_checkout] shortcode), but the block checkout, the default in new WooCommerce shops, ignores it. The block checkout has its own API for extra fields: woocommerce_register_additional_checkout_field(). Fields registered there look like the rest of the checkout, are saved on the order, appear in the order screen and the e-mails, and are validated on the server through the Store API.
Since WooCommerce 9.9 these fields can also be conditional: a JSON Schema rule says when a field is required and when it is hidden, and WooCommerce evaluates it in the browser and again on the server. Hidden fields are cleared, so a customer who opens the company fields and closes them again does not leave a stray EIK on the order. A shortened version of the registration:
add_action('woocommerce_init', function () {
$invoiceTicked = [
'type' => 'object',
'properties' => ['checkout' => ['properties' => ['additional_fields' => [
'type' => 'object',
'properties' => ['myshop/invoice' => ['const' => true]],
'required' => ['myshop/invoice'],
]]]],
];
woocommerce_register_additional_checkout_field([
'id' => 'myshop/invoice',
'label' => 'I need an invoice',
'location' => 'order',
'type' => 'checkbox',
]);
woocommerce_register_additional_checkout_field([
'id' => 'myshop/eik',
'label' => 'EIK / BULSTAT',
'location' => 'order',
'type' => 'text',
'required' => $invoiceTicked,
'hidden' => ['not' => $invoiceTicked],
]);
});
WooCommerce stores the values as order meta under _wc_other/myshop/eik, which other tools that understand additional checkout fields can read. If your shop still uses the classic checkout, you need both: the filter for the classic form and the registration for the block checkout, so that a theme switch does not silently lose the fields.
#Check the EIK while the customer types
An EIK has 9 digits, or 13 for a branch or a BULSTAT unit, and its last digit is a check digit calculated by the Registry Agency's algorithm. The 13-digit form has a second check digit at the end. That means most typing mistakes can be caught before the order is placed, without asking any outside service:
function clean_id(string $value): string
{
return strtoupper((string) preg_replace('/[\s.\-\/]+/u', '', $value));
}
function eik_is_valid(string $value): bool
{
$eik = clean_id($value);
if (! preg_match('/^(\d{9}|\d{13})$/', $eik)) {
return false;
}
$check = static function (array $digits, array $first, array $second): int {
$sum = fn (array $w) => array_sum(array_map(fn ($d, $x) => $d * $x, $digits, $w)) % 11;
$r = $sum($first);
if ($r === 10) {
$r = $sum($second);
}
return $r === 10 ? 0 : $r;
};
$d = array_map('intval', str_split($eik));
if ($check(array_slice($d, 0, 8), [1, 2, 3, 4, 5, 6, 7, 8], [3, 4, 5, 6, 7, 8, 9, 10]) !== $d[8]) {
return false;
}
return strlen($eik) === 9
|| $check(array_slice($d, 8, 4), [2, 7, 3, 5], [4, 9, 5, 7]) === $d[12];
}
We compared this function with the validator in our own plugin on 200,000 random numbers and they agreed on every one. The same rules, with EGN and IBAN, are explained step by step in our guide to Bulgarian validation in Laravel.
Two details make the check friendly. Clean the input first: customers paste numbers with spaces, dots and dashes from their own documents. And say what is wrong: "An EIK has 9 or 13 digits; this one has 8" helps, "Invalid value" does not. Check in the browser as the customer leaves the field, and again on the server when the order is placed, because the browser check can be skipped.
#The VAT number: format first, then VIES
A Bulgarian VAT number is "BG" followed by the company's 9-digit EIK, or by 10 digits for a person registered for VAT. Customers often leave out the "BG"; it is safe to add it to 9 or 10 bare digits. Numbers from other EU countries have their own published formats (DE and nine digits, NL with a "B" in the middle, and so on), and a format check catches obvious mistakes.
A correct format does not prove that the number is registered. For that the European Commission runs VIES, which asks the member state's own database. VIES has a simple REST interface, and only the VAT number is sent:
GET https://ec.europa.eu/taxation_customs/vies/rest-api/ms/BG/vat/123456786
Accept: application/json
{"isValid": false, "userError": "INVALID", "name": "", "address": "", ...}
A valid answer usually comes with the registered name and address, which is handy to keep on the order next to what the customer typed. Some member states do not share them and send three dashes instead. Three practical rules:
- Treat "unavailable" differently from "invalid". National databases go offline for maintenance, and VIES then answers with errors such as
MS_UNAVAILABLE. Refusing an order because a server in another country is down loses a sale; letting it through and marking it "not checked" lets someone look later. - Cache the answer. A day is reasonable. The customer may submit the checkout three times while fixing their address.
- Keep the answer with the order. Status, time of the check and the returned name are evidence of what you knew when you invoiced.
#Where the details must end up
Collecting the details is half the job. They are useful only where people look for them:
- on the order received page and in "My account", so the customer can see what they entered;
- in the order e-mail to the shop, with the VIES result;
- in a box on the order screen, with a way to copy them into the invoicing program and to correct a typo the customer reports;
- in a monthly list of orders that asked for an invoice, exported for the accountant.
For that export, a CSV in UTF-8 with semicolons opens directly in Excel set to Bulgarian. Write cells that start with "=", "+", "-" or "@" as text: a company name typed as a formula should never run as one on the accountant's computer.
#A plugin that does all of this
We built the steps above into Rekvizit, a WooCommerce plugin for Bulgarian shops. "I need an invoice" opens the company fields in the block checkout (as conditional additional checkout fields) and in the classic checkout. The EIK check digits and the VAT number format are checked while the customer types and again on the server, and a VIES check can be switched on, with your choice of what happens when VIES cannot answer. The details appear on the order, in the e-mails, in an order screen box with a copy button and in a monthly CSV for the accountant. It collects and checks the details; it does not issue or number invoices. It is tested with 69 unit tests and an end-to-end run on WordPress 7.1.3 and WooCommerce 11.2.1, with both order storage modes, and is listed as Coming soon.
#Frequently asked questions
Can I just ask for the EIK in the order notes?
You can, but nothing checks it, nothing stores it in a field of its own, and someone has to copy it out of free text for every order. A typo is found only when the invoice is rejected.
Does a valid check digit mean the company exists?
No. It only means the number could be an EIK. Most typing mistakes fail the check, but a made-up number can pass. VIES confirms VAT registration; the Commercial Register confirms the company.
What happens if VIES is down when a customer orders?
That is your choice. Letting the order through and marking the number as not checked keeps the sale; refusing it until VIES answers is stricter. Most small shops choose the first.
Is the VAT number required for every company?
No. Only companies registered for VAT have one, so the field should be optional. Ask your accountant whether your invoices need it when the customer has one.
Comments
Comments
Be the first to leave a comment.
Leave a comment