Tax form
Your client’s tax details, validated as they type with the same rules Cord uses when invoicing.
The Fiscal Element captures the recipient’s tax details: who gets invoiced. It validates in the browser with the same code Cord’s server uses before saving, so what the form accepts is exactly what Cord accepts. An RFC with a mistyped check digit is caught as it’s typed, not days later when stamping rejects it.
Which fields it asks for and what it validates
| Country | Fields | Rules |
|---|---|---|
Mexico (MX) |
RFC, legal name, tax regime, CFDI use, tax postal code | RFC shape, date and check digit. The RFC type (12 characters, legal entity; 13, individual) filters the applicable regimes and uses. XAXX010101000 and XEXX010101000 (general public and foreigners) set regime 616 and use S01. A warning if a legal entity’s name includes the corporate suffix (“S.A. de C.V.”). 5-digit postal code. |
Spain (ES) |
NIF / CIF, legal name, postal code | NIF and NIE control letter, and CIF control digit. 5-digit postal code. |
United States (US) |
EIN, legal name, ZIP | Valid EIN. ZIP of 5 digits or 12345-6789. |
| Other country | Tax ID, name | Only presence is checked, with an unsupported_country warning. |
Regimes in the catalog: 601, 603, 605, 606, 612, 616, 620, 621 and 626. CFDI uses: G01, G03, I01, I02, I04, I08, S01 and CP01.
When valid, the result is the normalized recipient:
{
"country": "MX",
"tax_id": "EKU9003173C9",
"legal_name": "ESCUELA KEMPER URGATE",
"regimen_fiscal": "601",
"uso_cfdi": "G03",
"cp_fiscal": "42501"
}
Outside Mexico, regimen_fiscal and uso_cfdi come back null.
Web Component: <cord-fiscal-form>
Importing @flouviahq/elements registers <cord-fiscal-form> along with <cord-quote> and <cord-invoice>.
<script type="module" src="https://unpkg.com/@flouviahq/elements/dist/index.mjs"></script>
<form id="signup" method="post" action="/clients">
<cord-fiscal-form name="recipient" country="MX" lang="en"></cord-fiscal-form>
<button type="submit">Save</button>
</form>
It’s a form-associated element: inside a <form>, it contributes its value under its name attribute (the normalized recipient as JSON) and blocks submission while there are errors, just like an <input required>. The browser shows the first error next to the field that causes it. In Safari before 16.4, which lacks that API, the form works but doesn’t take part in submission: read value yourself.
| Attribute | Values |
|---|---|
country |
MX (default), ES, US or another ISO code. Changing it keeps what’s already typed. |
lang |
es, en, pt, fr or de; any other value falls back to en. Without it, your page’s lang. |
name |
The name under which it contributes its value to the form. |
| Property or method | What it does |
|---|---|
value |
The normalized recipient if valid; otherwise null. |
state |
The full state: values, touched fields, visible errors, available regimes and uses. |
setValues(data) |
Loads initial values, for example from a saved client. |
submit() |
Shows every error and returns the recipient if valid, or null. |
<script type="module">
const fiscal = document.querySelector('cord-fiscal-form');
fiscal.setValues({ tax_id: 'EKU9003173C9', legal_name: 'ESCUELA KEMPER URGATE', cp_fiscal: '42501' });
fiscal.addEventListener('fiscalvalid', (e) => {
console.log('Recipient ready:', e.detail);
});
fiscal.addEventListener('fiscalchange', (e) => {
console.log('Visible errors:', e.detail.visibleErrors);
});
</script>
| Event | detail |
|---|---|
fiscalchange |
The full state, on every change. |
fiscalvalid |
The normalized recipient, every time it becomes valid. |
Errors only show on fields the person has already touched, or on all of them after submit().
Styling
The form lives in a Shadow DOM. Customize it with parts and variables:
cord-fiscal-form::part(label) { font-weight: 500; }
cord-fiscal-form::part(input) { min-height: 48px; }
cord-fiscal-form::part(error) { font-size: 13px; }
cord-fiscal-form {
--cord-color-primary: #0a192f;
--cord-border-radius: 10px;
}
| Part | Element |
|---|---|
field |
Each field’s container. |
label |
The label. |
input |
The text field or the select. |
error |
A field’s error message. |
warning |
A field’s warning (for example, the corporate suffix in the legal name). |
Variables: --cord-font-family, --cord-color-text, --cord-color-text-secondary, --cord-color-input, --cord-color-primary, --cord-color-danger and --cord-border-radius.
React: <CordFiscalForm> and <CordBuilder fiscal>
import { CordFiscalForm } from '@flouviahq/elements/react';
export function TaxDetails({ onReady }: { onReady: (recipient: unknown) => void }) {
return (
<CordFiscalForm
country="MX"
locale="en"
title="Billing details"
initial={{ tax_id: 'EKU9003173C9' }}
onValid={onReady}
/>
);
}
| Prop | Detail |
|---|---|
country |
MX by default. |
initial |
Initial values. |
locale |
es, en, pt, fr or de. |
title |
Section heading. |
onValid |
Called with the normalized recipient every time it becomes valid. |
className, style |
For the container. |
Inside the Builder, fiscal adds the section and the recipient travels with the quote:
<CordProvider publishableKey="pk_test_...">
<CordBuilder fiscal />
</CordProvider>
The country comes from your organization’s tax configuration. If the recipient isn’t valid, the Builder doesn’t submit and flags the fields with errors. To control the state yourself, useFiscalForm() returns the same state as the component.
Vue: useCordFiscalForm
<script setup>
import { useCordFiscalForm } from '@flouviahq/elements/vue';
const { state, form } = useCordFiscalForm({ country: 'MX' });
function save() {
const recipient = form.submit();
if (recipient) console.log(recipient);
}
</script>
<template>
<label>
RFC
<input :value="state.values.tax_id" @input="form.set('tax_id', $event.target.value)" @blur="form.touch('tax_id')" />
</label>
<p v-for="e in state.visibleErrors" :key="e.field">{{ e.field }}: {{ e.code }}</p>
<button @click="save">Save</button>
</template>
state is a ShallowRef with the form’s state; form is the headless form (set, touch, submit). To render each error’s text, use fiscalText() from @flouviahq/elements/headless.
Validation codes
| Code | Meaning |
|---|---|
required |
The value is missing. |
too_long |
Over the maximum (20 characters for the ID, 300 for the name). |
invalid_tax_id |
The tax ID isn’t valid for the country. |
invalid_postal_code |
The postal code doesn’t match the country’s format. |
regimen_not_for_persona |
The regime doesn’t match the RFC type. |
uso_not_for_persona |
The CFDI use doesn’t match the RFC type. |
generic_requires_616_s01 |
A generic RFC requires regime 616 and use S01. |
legal_name_has_regime_suffix |
Warning: the legal name includes the corporate suffix. |
unsupported_country |
Warning: for that country only presence is checked. |
On the server side
-
POST /api/v1/cotizacionesaccepts the recipient incliente.fiscal. If it isn’t valid, it returns400withcode: "invalid_fiscal_data"and the list inissues:{ "error": "Los datos fiscales del cliente no son válidos.", "code": "invalid_fiscal_data", "issues": [{ "field": "tax_id", "code": "invalid_tax_id" }] } -
@flouviahq/nodeexportsvalidateFiscalReceptor(),validRfc(),validSpainTaxId()andvalidEin(), to validate on your server with the same code. -
The MCP server has the
validar_datos_fiscalestool.