Saltar al contenido principal
El plugin Verifactu para WooCommerce de Finseed añade campos al checkout para pedir factura con los datos fiscales del comprador: una casilla, un campo NIF/CIF y los campos que lo acompañan cuando hacen falta, como Empresa. 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 de la 34 a la 39, 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 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 estos campos al 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: la casilla y el NIF/CIF quedan visibles y opcionales. La casilla y el NIF/CIF aparecen según el importe del pedido (ver “Comportamiento por umbral”, más abajo). En los dos checkouts, también en Blocks con WooCommerce 8.9 a 10.0, un script del plugin muestra los campos que acompañan al NIF/CIF solo cuando el comprador marca la casilla o el pedido exige NIF/CIF (por encima del umbral, o en todos los pedidos con Siempre obligatorio). La lista recoge todos los campos y, para los que acompañan al NIF/CIF, cuándo aparece cada uno:
  • Casilla finseed_wants_full_invoice. Tipo checkbox, con la etiqueta “Quiero factura con mis datos fiscales”. Es la petición del comprador de una factura con sus datos fiscales.
  • Selector finseed_buyer_kind. Tipo select, con la etiqueta Compras como y las opciones Particular y Empresa. Aparece junto al NIF/CIF solo cuando la dirección de facturación está fuera de España.
  • Campo finseed_tax_id. Tipo text. Recoge el identificador fiscal del comprador. Su etiqueta depende del país de facturación y de la respuesta a Compras como: con una dirección española es “NIF / CIF / NIE”.
  • Campo finseed_company_name. Tipo text, con la etiqueta Empresa. El plugin trata al comprador como empresa en dos casos: con una dirección española, cuando el NIF/CIF escrito tiene forma de CIF o es un número de IVA de la UE bien formado con el prefijo de otro país (por ejemplo, DE815892943), y el comprador no ha elegido nada en Tipo de documento; fuera de España, cuando el comprador elige la opción Empresa de Compras como. Solo entonces aparece el campo Empresa, así que con una dirección española y un NIF personal no aparece. Debajo lleva el texto de ayuda “El nombre completo de la empresa, con S.L., S.A. u otra forma al final.” Mientras está visible, es obligatorio en cuanto el NIF/CIF tiene un valor. Si queda vacío, el checkout muestra el error “Indica el nombre de la empresa.” La primera vez que aparece, toma como valor inicial el del campo de empresa de WooCommerce (billing_company). Si la tienda no muestra ese campo, toma la empresa guardada en la cuenta del comprador. El comprador puede cambiar ese valor. En la factura, el nombre escrito en Empresa tiene prioridad sobre billing_company. Si el script del checkout clásico no se carga, el campo aparece para todos los compradores con la etiqueta “Empresa (solo si facturas a una empresa)”, y el servidor solo lo exige cuando trata al comprador como empresa.
  • Selectores finseed_document_type y finseed_issuing_country. Tipo select, con las etiquetas Tipo de documento (opciones Pasaporte y Documento de identidad de otro país) y País que expide el documento. Tipo de documento aparece con una dirección fuera de España cuando el comprador no elige la opción Empresa de Compras como. Con una dirección española aparece cuando el checkout rechaza el identificador con un mensaje que termina con “Si es un pasaporte o un documento de otro país, indica el tipo y el país.” País que expide el documento aparece cuando el comprador elige un tipo.
En el checkout clásico los campos van con prioridades de la 34 a la 39, en este orden: casilla (34), Compras como (35), NIF/CIF (36), Empresa (37), Tipo de documento (38) y País que expide el documento (39). El orden visual resultante es: nombre → apellidos → empresa de WooCommerce (billing_company) → nuestros campos → país → dirección. En el checkout Blocks los 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. Sus identificadores internos son finseed/wants_full_invoice, finseed/buyer_kind, finseed/tax_id, finseed/company_name, finseed/document_type y finseed/issuing_country (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. Cuando el plugin trata al comprador como empresa, el nombre escrito en Empresa se guarda en _finseed_company_name, junto al NIF/CIF.

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 como obligatorio. Debajo del campo aparece el texto de ayuda “Necesario en compras superiores a 3.000 €”, con el umbral que se aplique a tu tienda.
En el checkout Blocks de WooCommerce 8.9 a 10.0 la casilla y el NIF/CIF no se ocultan. WooCommerce todavía no admite ahí las reglas que dependen del total del carrito, así que los dos están siempre visibles. Por encima del umbral, el script del plugin añade igualmente el texto de ayuda debajo del NIF/CIF. 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 sus 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 un mensaje que pide el identificador según el país de facturación (con una dirección española, “Indica tu NIF, CIF o NIE para continuar.”), 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. Si en los ajustes del plugin eliges Siempre obligatorio en Cuándo es obligatorio, el umbral deja de decidir cuándo se pide el NIF/CIF. En una tienda en euros la casilla no aparece, y el campo NIF/CIF se muestra en todos los pedidos como obligatorio, sea cual sea el importe. Sin NIF/CIF el pedido no se completa. Los botones de pago exprés de la página del producto o del carrito (Apple Pay, Google Pay u otros) no muestran el campo: si el botón crea el pedido a través del checkout de WooCommerce, el plugin lo rechaza, y si lo crea por su cuenta, el pedido llega sin NIF/CIF. Cada pedido que pasa por el checkout se guarda con _finseed_wants_full_invoice en yes junto al NIF/CIF del comprador, así que Finseed lo recibe como una petición de factura completa. Con el checkout Blocks por debajo de WooCommerce 8.9, este modo impide completar cualquier venta en euros: el plugin no puede registrar el campo, pero la comprobación del servidor sigue activa. 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, nuestros campos ocupan las prioridades de la 34 a la 39, justo después de billing_company. Muévelos juntos y en el mismo orden, para que Empresa siga justo debajo del NIF/CIF. Este ejemplo los coloca entre el país (prioridad 40) y la dirección (prioridad 50), con las prioridades de la 41 a la 46:

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, #finseed_buyer_kind, #finseed_tax_id, #finseed_company_name, #finseed_document_type y #finseed_issuing_country para los inputs y selectores.
  • .finseed-legacy-checkout__wants-full-invoice, .finseed-legacy-checkout__buyer-kind, .finseed-legacy-checkout__tax-id, .finseed-legacy-checkout__company-name, .finseed-legacy-checkout__document-type y .finseed-legacy-checkout__issuing-country para las clases del contenedor <p class="form-row">.
  • .finseed-field-help para el texto de ayuda que el script añade debajo del NIF/CIF y de Empresa.
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 con mis datos fiscales”. La etiqueta de la casilla se puede cambiar con este filtro:
Las etiquetas del NIF/CIF y de Empresa no admiten este filtro. El script del cliente las reescribe siempre, al cargar la página y en cada refresco del checkout. La del NIF/CIF cambia con el país de facturación y con la respuesta a Compras como. Si les 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, finseed_buyer_kind, finseed_tax_id, finseed_company_name, finseed_document_type y finseed_issuing_country a la lista de campos permitidos del editor. Si el editor quita finseed_company_name, el plugin deja de exigir Empresa: solo lo exige cuando el campo llega con el pedido, y el pedido de una empresa pasa sin ese nombre.

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_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. El script también lee #billing_company si existe, pero no depende de él. Para una empresa enviamos al validador el nombre escrito en Empresa, y #billing_company solo cuenta si Empresa está vacío. #billing_company también da el valor inicial de Empresa. Si #billing_company no existe, el revelado y la validación siguen funcionando.

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, fieldCompanyName y copy. 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 campos aparecen en la posición que indican las prioridades de la 34 a la 39 del filtro woocommerce_checkout_fields. Para reubicarlos basta con cambiar esas prioridades 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 (por encima del umbral, o en todos los pedidos con Siempre obligatorio), consulta al validador de identificadores fiscales en cada envío y rechaza el pedido de una empresa que escribe su NIF/CIF y deja Empresa vací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. En todas las versiones, el plugin actúa sobre woocommerce_store_api_checkout_update_order_from_request. Ahí rechaza el envío si falta el NIF/CIF cuando es obligatorio (por encima del umbral, o en todos los pedidos con Siempre obligatorio), si el identificador no es válido o si una empresa escribe su NIF/CIF y deja Empresa vacío. Esta última comprobación solo se hace si la petición a la Store API incluye finseed/company_name. Después guarda los datos en el pedido.
Un frontend mal configurado puede degradar la experiencia del comprador. Mientras nuestros campos lleguen con el envío (en el checkout clásico, mientras sigan en la lista de woocommerce_checkout_fields; en Blocks, mientras la petición a la Store API los incluya), el servidor rechaza todo envío del checkout que no cumple esas comprobaciones. 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.