Skip to main content
El plugin Verifactu para WooCommerce de Finseed añade dos campos al checkout: una casilla de “factura completa” y un campo NIF/CIF. Esta guía está pensada para desarrolladores, agencias y equipos técnicos que necesitan reordenar, restilizar o renombrar esos campos sin romper la integración. Cubre los dos tipos de checkout que soporta el plugin, los hooks seguros para personalizar, qué prácticas rompen la integración y cómo diagnosticarlo desde el navegador.

Dos checkouts, dos vías de integración

WooCommerce tiene dos motores de checkout y Finseed soporta ambos. Comparten la misma forma canónica de datos en el pedido, pero la superficie de personalización es completamente distinta. Identifica primero cuál usa tu tienda antes de tocar nada.
  • Checkout Blocks (moderno). El checkout basado en bloques de Gutenberg, con validación de campos por JSON Schema y personalización desde el editor de bloques. Es el predeterminado en las versiones recientes de WooCommerce. Nuestros campos se registran a través de la Additional Checkout Fields API (WooCommerce 8.9 o posterior) bajo el namespace finseed.
  • Checkout Classic (heredado). El checkout del shortcode [woocommerce_checkout], basado en $_POST y personalizado mediante el filtro woocommerce_checkout_fields. Muchas tiendas antiguas y muchos themes o page builders siguen ancladas a este modo. Nuestros campos se registran con prioridades 34 y 35, entre billing_company (prioridad 30) y billing_country (prioridad 40).
Las instrucciones de la sección “Hooks y técnicas seguras” solo aplican al checkout clásico. Si estás en Blocks, personaliza desde el editor de Gutenberg y los ajustes nativos del bloque.

Qué añade el plugin y dónde

Los dos campos existen solo si has activado Solicitar NIF / CIF en el checkout en los ajustes del plugin. El ajuste viene desactivado, y hasta que lo actives ninguno de los dos motores registra nada. Con el ajuste activo, Finseed añade dos campos a la sección de facturación (billing) del checkout. El checkout clásico solo los registra si la tienda opera en EUR; en cualquier otra divisa no aparecen. El checkout Blocks los registra siempre, sea cual sea la divisa, pero fuera del euro no aplica el umbral ni exige el NIF/CIF: los dos campos quedan visibles y opcionales.
  • Casilla finseed_wants_full_invoice. Tipo checkbox. Es el consentimiento del comprador para recibir una factura completa (empresa o autónomo).
  • Campo finseed_tax_id. Tipo text. Recoge el NIF/CIF o número de IVA del comprador.
En el checkout clásico ambos campos van con prioridades 34 y 35, lo que produce el orden visual: nombre → apellidos → empresa → casilla de factura completa → NIF/CIF → país → dirección. En el checkout Blocks ambos campos se registran vía woocommerce_register_additional_checkout_field con location: 'order', por lo que aparecen en la sección de pedido del bloque y sus identificadores internos son finseed/wants_full_invoice y finseed/tax_id (con barra, no con guion bajo). El checkout clásico usa la forma con guion bajo porque envía los campos por $_POST con la convención de claves de WooCommerce, mientras que Blocks los envía en JSON a través de la Store API. Ambos motores persisten la misma información canónica en el pedido bajo los metadatos _finseed_wants_full_invoice (yes/no) y _finseed_tax_id.

Comportamiento por umbral

El umbral es el importe a partir del cual la factura completa con NIF/CIF es obligatoria. Es configurable en los ajustes del plugin y admite dos valores, ambos límites de la factura simplificada del RD 1619/2012: 400 € y 3.000 €. El valor por defecto es 3.000 €. En el checkout clásico, y en el checkout Blocks a partir de WooCommerce 10.1.0, el comportamiento de revelado depende de ese umbral:
  • Carrito ≤ umbral. La casilla está visible. El NIF/CIF está oculto hasta que el comprador marca la casilla. Al marcarla, aparece el campo NIF/CIF como opcional.
  • Carrito > umbral. La casilla desaparece (la factura completa es obligatoria de todas formas) y el NIF/CIF se muestra siempre con la etiqueta de obligatorio.
En el checkout Blocks de WooCommerce 8.9 a 10.0 no hay revelado. WooCommerce todavía no admite ahí las reglas que dependen del total del carrito, así que la casilla y el campo NIF/CIF están siempre visibles y el NIF/CIF conserva siempre la etiqueta de opcional. El requisito se aplica al enviar el pedido: si el total supera el umbral y no hay NIF/CIF, la Store API rechaza el envío con un mensaje de error. Con el checkout Blocks por debajo de WooCommerce 8.9, las ventas por encima del umbral no se pueden completar. La Additional Checkout Fields API no existe todavía, así que el plugin no registra ninguno de los dos campos, pero la comprobación del servidor sí sigue activa. Un pedido en euros por encima del umbral llega sin NIF/CIF, la Store API lo rechaza con un error 400 y el mensaje “El NIF / CIF es obligatorio en compras superiores a” seguido del umbral que tengas configurado, y el comprador no tiene ningún campo en la página donde escribirlo. Si tu tienda usa Blocks en una versión anterior a la 8.9, actualizar a 8.9 o posterior, o volver al checkout clásico, no es opcional. La lógica de revelado vive en el navegador, pero la validación final se hace en el servidor. Un comprador sin JavaScript no puede saltarse el requisito.

Hooks y técnicas seguras para personalizar (checkout clásico)

Las técnicas de esta sección solo aplican al checkout clásico (shortcode). El checkout Blocks se personaliza desde el editor de Gutenberg y los ajustes del propio bloque.

Reordenar los campos

Para reubicar nuestros campos en el orden de facturación, engancha un filtro a woocommerce_checkout_fields con prioridad mayor que 10 (la nuestra) y reescribe la clave priority. WooCommerce ordena los campos por priority desde la versión 3.5.1; en 3.5.0 ignora ese valor y nuestros campos salen al final de la sección de facturación, así que en esa versión el único modo de moverlos es reordenar el array del filtro a mano. Por defecto, la casilla está en prioridad 34 y el NIF/CIF en 35, justo después de billing_company. Este ejemplo los coloca entre el nombre (prioridad 10) y los apellidos (prioridad 20):

Restilizar con CSS

Los campos exponen ganchos por ID y por clase. Úsalos para alinear con el diseño del theme:
  • #finseed_wants_full_invoice y #finseed_tax_id para los inputs.
  • .finseed-legacy-checkout__wants-full-invoice y .finseed-legacy-checkout__tax-id para las clases del contenedor <p class="form-row">.
Puedes apuntar también a las clases de fila de WooCommerce (.form-row, .form-row-wide) si tu hoja de estilos ya las trata.

Renombrar las etiquetas

El plugin rotula la casilla como “Quiero factura completa (empresa o autónomo)” y el campo fiscal como “NIF / CIF”. Solo la etiqueta de la casilla se puede cambiar con este filtro:
La etiqueta del NIF/CIF no admite este filtro. El script del cliente la reescribe siempre, tanto por encima como por debajo del umbral, al cargar la página y en cada refresco del checkout. Si le asignas un texto con woocommerce_checkout_fields, el script lo sustituye antes de que el comprador lo vea. Para controlar ese texto tendrías que intervenir el script del cliente, algo que no recomendamos.

Añadir contenido adyacente

Para insertar texto o componentes alrededor de la sección de facturación, usa los hooks de acción nativos de WooCommerce:
  • woocommerce_before_checkout_billing_form
  • woocommerce_after_checkout_billing_form
Por ejemplo, para mostrar una nota explicativa encima del bloque de facturación:

Qué rompe la integración y cómo detectarlo

La mayoría de incidencias en personalizaciones avanzadas caen en uno de estos patrones. Reconocerlos te ahorrará tiempo de soporte.

Page builders que reemplazan la plantilla del checkout

Elementor Pro, Divi, Beaver Builder o Bricks ofrecen widgets de checkout que sustituyen por completo la plantilla nativa. Cuando lo hacen, el pipeline de campos de WooCommerce no se ejecuta y nuestros campos no se renderizan. Solución. Configura el módulo de checkout del builder para usar los campos nativos de WooCommerce, no una lista de campos definida en el propio builder. Cada producto lo nombra de forma diferente, pero la opción suele estar etiquetada como “Use WooCommerce fields”, “Default fields” o similar.

Plugins de editor de campos del checkout

“Checkout Field Editor for WooCommerce”, “WooCommerce Checkout Manager”, “Flexible Checkout Fields” y similares mantienen una lista interna de campos permitidos. Cualquier campo que no esté en esa lista (como los nuestros) puede quedar eliminado del formulario. Solución. Añade finseed_wants_full_invoice y finseed_tax_id a la lista de campos permitidos del editor.

Themes que sobrescriben las plantillas de checkout

Algunos themes incluyen sobrescrituras de woocommerce/checkout/form-checkout.php u otras plantillas del checkout. Si la sobrescritura no itera dinámicamente los campos del filtro y los lista a mano, nuestros campos no aparecerán. Solución. Revisa el archivo form-checkout.php del theme dentro de wp-content/themes/<tu-theme>/woocommerce/checkout/. Asegúrate de que llama a woocommerce_checkout_billing() o que recorre los campos devueltos por el filtro woocommerce_checkout_fields. Una plantilla que codifica a mano la lista de campos rompe cualquier personalización dinámica, no solo la nuestra.

Contrato DOM del que depende el revelado condicional

El script de revelado y validación en el cliente espera una estructura DOM concreta. Si tu theme o un plugin la alteran, el revelado y la validación inline se degradan en silencio. La validación de servidor sigue funcionando, pero la experiencia del comprador empeora.
  • Contenedor <p class="form-row"> alrededor de cada campo. Lo usamos para mostrar u ocultar la fila y para marcar el estado de error.
  • Selector .order-total .woocommerce-Price-amount (o .order-total bdi). Lo leemos para conocer el total del carrito en vivo y aplicar el umbral.
  • IDs #billing_first_name, #billing_last_name, #billing_company, #billing_country. Los leemos para componer el nombre del comprador y el código de país que enviamos al validador de identificadores fiscales.
  • Evento updated_checkout disparado con jQuery sobre document.body. WooCommerce lo emite tras cada cambio en el carrito (cupón, envío, etc.) y lo reescuchamos para reaplicar el estado tras el re-renderizado del formulario.
Si alguno de estos elementos cambia de nombre, desaparece o se dispara por otro canal, el revelado condicional y la validación inline al perder el foco fallan en silencio. La validación en servidor sigue bloqueando el pedido si el NIF/CIF es incorrecto, así que el cumplimiento no se ve comprometido.

Diagnóstico rápido desde el navegador

Antes de pedir soporte, abre la consola del navegador en la página de checkout y comprueba lo siguiente:
Si devuelve undefined, nuestro script no se ha cargado. Comprueba primero que Solicitar NIF / CIF en el checkout está activado en los ajustes del plugin: con el ajuste apagado no se registra ningún campo ni se carga el script. Si está activado, confirma que el theme y los plugins activos no están bloqueando wp_enqueue_scripts en la página de checkout. Si devuelve un objeto, debe contener al menos restUrl, restNonce, thresholdCents, fieldWantsFullInvoice, fieldTaxId, labelTaxIdOptional y labelTaxIdRequired. Después, escribe un NIF inválido en el campo y quita el foco. En la pestaña Network deberías ver una petición POST a /wp-json/finseed/v1/validate-tax-id (en tiendas con enlaces permanentes personalizados) o a /?rest_route=/finseed/v1/validate-tax-id (en tiendas con la configuración “Sencillo” de enlaces permanentes). Si la petición no se dispara, el manejador de blur no se ha enlazado (suele indicar un bundle cacheado obsoleto en el servidor de WordPress o que el theme reemplaza el input antes de que lo enlacemos). Si la petición devuelve 401 o 403, el nonce REST no es válido, lo que suele indicar una caché de página agresiva que sirve nonces caducados a usuarios distintos. Cuando la validación inline funciona, el input gana el atributo aria-invalid="true" y la fila añade las clases woocommerce-invalid y woocommerce-invalid-required-field.

Cuando la posición estándar no encaja con tu diseño

Hoy no ofrecemos un shortcode independiente para colocar los campos en cualquier zona de la página. Los dos campos aparecen en la posición que indican las prioridades 34 y 35 del filtro woocommerce_checkout_fields. Para reubicarlos basta con cambiar esa prioridad como se muestra más arriba. Si tu diseño exige una posición que ninguna prioridad alcanza (por ejemplo, fuera del bloque de facturación), escríbenos. El campo renderizado debe terminar dentro del <form class="checkout"> para que su valor se envíe con el resto del pedido, así que la vía de escape correcta tiene restricciones que preferimos resolver contigo.

Tu personalización no rompe el cumplimiento

Sea cual sea el comportamiento del cliente, los hooks de servidor son la fuente de verdad. Cada motor de checkout usa los suyos:
  • Checkout clásico. La validación sobre woocommerce_after_checkout_validation aplica el requisito de NIF/CIF para compras por encima del umbral y consulta al validador de identificadores fiscales en cada envío. La persistencia sobre woocommerce_checkout_create_order escribe la información canónica en el pedido.
  • Checkout Blocks. Ninguno de esos dos hooks se ejecuta. El identificador se valida con el validate_callback que declara el campo al registrarse en la Additional Checkout Fields API. Los datos se guardan en woocommerce_store_api_checkout_update_order_from_request en todas las versiones. El umbral lo aplica una de dos vías, nunca las dos a la vez: hasta WooCommerce 10.0, una comprobación en esa misma acción que rechaza el envío; desde 10.1.0, la regla de obligatoriedad que declara el propio campo y que WooCommerce aplica al enviar.
Un frontend mal configurado puede degradar la experiencia del comprador, pero no puede producir un pedido sin la información fiscal requerida cuando la ley la exige. Está diseñado así a propósito.

Cuando algo no funciona

Antes de contactar con soporte, captura la siguiente información. Reduce el tiempo de diagnóstico a la mitad.
  • Logs de WooCommerce filtrados por finseed. Ve a WooCommerce > Estado > Registros y abre el log más reciente con esa fuente.
  • Consola y Network del navegador en la página de checkout. Captura el contenido de window.finseedLegacyCheckout y la petición al endpoint de validación (con enlaces permanentes personalizados aparece como /wp-json/finseed/v1/validate-tax-id; con la configuración “Sencillo” aparece como /?rest_route=/finseed/v1/validate-tax-id). Incluye cuerpo, respuesta, código de estado y cualquier error de consola.
  • Estado actual de los campos del checkout. Si tienes acceso WP-CLI, ejecuta wp option get woocommerce_checkout_fields. Si no, copia el HTML del <form class="checkout"> renderizado.
  • Theme activo y plugins de personalización del checkout activos. Nombre exacto y versión.
  • Una grabación de pantalla del fallo. Cualquier vídeo corto del problema vale más que una descripción escrita.
Con esos datos abre un ticket desde finseed.es/contacto y nuestro equipo podrá reproducirlo localmente.