El otro día en el WordPress Day Marbella 2015 nuestro compañero de ponencia Andy García nos propuso el reto de validar el campo NIF/CIF, que os enseñamos a añadir en la entrada ¿Cómo añadir un campo NIF o CIF a WooCommerce 2.1?, para hacer el código perfecto a su criterio.

Pues bien, dicho y hecho. Nos hemos puesto manos a la obra para satisfacer las necesidades de Andy y os proponemos una solución para validar cualquier tipo de número NIF, CIF o NIE.

Solución

Realmente la solución al problema planteado es simple gracias al gran código PHP publicado en Comprobar CIF, NIF o NIE con PHP por da-software. Lo único que hemos hecho es adaptar su fantástica función PHP a las necesidades de WooCommerce.

Aquí tienes el código PHP que tienes que añadir al archivo functions.php de tu tema hijo o padre para que se valide correctamente el campo NIF/CIF:

//Validando el campo NIF/CIF
function validando_campo() {
	$falso = true;

    if ( isset( $_POST['billing_nif'] ) ) {
		$nif = strtoupper( $_POST['billing_nif'] );

		for ( $i = 0; $i < 9; $i ++ ) {
			$num[$i] = substr( $nif, $i, 1 );
		}

		if ( !preg_match( '/((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)/', $nif ) ) { //No tiene formato válido
			$falso = true;
		}

		if ( preg_match( '/(^[0-9]{8}[A-Z]{1}$)/', $nif ) ) {
			if ( $num[8] == substr( 'TRWAGMYFPDXBNJZSQVHLCKE', substr( $nif, 0, 8 ) % 23, 1 ) ) { //NIF válido
				$falso = false;
			}
		}

		$suma = $num[2] + $num[4] + $num[6];
		for ( $i = 1; $i < 8; $i += 2 ) {
			$suma += substr( ( 2 * $num[$i] ), 0, 1 ) + substr( ( 2 * $num[$i] ), 1, 1 );
		}
		$n = 10 - substr( $suma, strlen( $suma ) - 1, 1 );

		if ( preg_match( '/^[KLM]{1}/', $nif ) ) { //NIF especial válido
			if ( $num[8] == chr( 64 + $n ) ) {
				$falso = false;
			}
		}

		if ( preg_match( '/^[ABCDEFGHJNPQRSUVW]{1}/', $nif ) ) {
			if ( $num[8] == chr( 64 + $n ) || $num[8] == substr( $n, strlen( $n ) - 1, 1 ) ) { //CIF válido
				$falso = false;
			}
		}

		if ( preg_match( '/^[T]{1}/', $nif ) ) {
			if ( $num[8] == preg_match( '/^[T]{1}[A-Z0-9]{8}$/', $nif ) ) { //NIE válido (T)
				$falso = false;
			}
		}
		if ( preg_match( '/^[XYZ]{1}/', $nif ) ) { //NIE válido (XYZ)
			if ( $num[8] == substr( 'TRWAGMYFPDXBNJZSQVHLCKE', substr( str_replace( array( 'X','Y','Z' ), array( '0','1','2' ), $nif ), 0, 8 ) % 23, 1 ) ) {
				$falso = false;
			}
		}
	}

	if ( $falso ) {
		wc_add_notice( __( 'Por favor, introduzca un NIF/CIF válido.' ), 'error' );
	}
}
add_action( 'woocommerce_checkout_process', 'validando_campo' );

Y para que no te vuelvas loco copiando y pegando código, aquí tienes todo el código PHP completo válido desde la versión 2.1 hasta la 2.3 de WooCommerce, ambas inclusive:

//Arreglamos la dirección predeterminada
function campos_de_direccion( $campos ) {
    $campos['nif'] = array( 
        'label' => __( '<abbr title="Código de Identificación Fiscal" lang="es">CIF</abbr>/<abbr title="Número de Identificación Fiscal" lang="es">NIF</abbr>', 'woocommerce' ),
        'placeholder' => _x( 'Introduzca elCIF/NIF', 'placeholder', 'woocommerce' ),
        'required' => true,
        'class' =>array( 'form-row-last' ),
        'clear' => true,
	);

    $campos['company']['class'][0] = 'form-row-first';
    $campos['city']['class'][0] = 'form-row-first';
    $campos['state']['class'][0] = 'form-row-last update_totals_on_change';
    $campos['postcode']['class'][0] .= ' update_totals_on_change';

    //Reordenamos los campos
    $campos_nuevos['country'] = $campos['country'];
    $campos_nuevos['first_name'] = $campos['first_name'];
    $campos_nuevos['last_name'] = $campos['last_name'];
    $campos_nuevos['company'] = $campos['company'];
    $campos_nuevos['nif'] = $campos['nif'];
    $campos_nuevos['address_1'] = $campos['address_1'];
    $campos_nuevos['address_2'] = $campos['address_2'];
    $campos_nuevos['postcode'] = $campos['postcode'];
    $campos_nuevos['city'] = $campos['city'];
    $campos_nuevos['state'] = $campos['state'];
    if ( isset( $campos['email'] ) ) {
		$campos_nuevos['email'] = $campos['email'];
	}
    if ( isset( $campos['phone'] ) ) {
		$campos_nuevos['phone']['required'] = true;
		$campos_nuevos['phone'] = $campos['phone'];
    }
	
    return $campos_nuevos;
}
add_filter( 'woocommerce_default_address_fields' , 'campos_de_direccion' );

//Nueva función para hacer compatible el código con WooCommerce 2.1
function dame_campo_personalizado( $campo, $pedido ) {
    $valor = get_post_meta( $pedido, $campo, false );
	if ( isset( $valor[0] ) ){
		return $valor[0];
	}
	return NULL;
}

//Añadimos el NIF y el teléfono a la dirección de facturación y envío
function anade_campo_nif_direccion_facturacion( $campos, $pedido ) {
    $campos['nif'] = dame_campo_personalizado( '_billing_nif', $pedido->id );
    $campos['phone'] = dame_campo_personalizado( '_billing_phone', $pedido->id );
	
	return $campos;
}
add_filter( 'woocommerce_order_formatted_billing_address','anade_campo_nif_direccion_facturacion', 1, 2 );

function anade_campo_nif_direccion_envio( $campos, $pedido ) {
    $campos['nif'] = dame_campo_personalizado( '_shipping_nif', $pedido->id );
    $campos['phone'] = dame_campo_personalizado( '_shipping_phone', $pedido->id );
	
	return $campos;
}
add_filter( 'woocommerce_order_formatted_shipping_address','anade_campo_nif_direccion_envio', 1, 2 );

function formato_direccion_de_facturacion( $campos, $argumentos ) {
    $campos['{nif}'] = $argumentos['nif'];
    $campos['{nif_upper}'] =strtoupper( $argumentos['nif'] );
    $campos['{phone}'] = $argumentos['phone'];
    $campos['{phone_upper}'] =strtoupper( $argumentos['phone'] );
	
	return $campos;
}
add_filter( 'woocommerce_formatted_address_replacements','formato_direccion_de_facturacion', 1, 2 );

//Reordenamos los campos de la dirección predeterminada
function formato_direccion_localizacion( $campos ) {
    $campos['default'] = "{name}n{company}n{nif}n{address_1}n{address_2}n{city}n{state}n{postcode}n{country}n{phone}";
    $campos['ES'] = "{name}n{company}n{nif}n{address_1}n{address_2}n{postcode} {city}n{state}n{country}n{phone}";
	
	return $campos;
}
add_filter( 'woocommerce_localisation_address_formats','formato_direccion_localizacion' );

//Arreglamos el formulario de envío
function formulario_de_envio( $campos ) {
    $campos['shipping_email'] =array( 
        'label' => __( 'EmailAddress', 'woocommerce' ),
        'required' => false,
        'class' =>array( 'form-row-first' ),
        'validate' =>array(  'email'  ),
	);
    $campos['shipping_phone'] =array( 
        'label' => __( 'Phone', 'woocommerce' ),
        'required' => true,
        'class' =>array( 'form-row-last' ),
        'clear' => true,
	);
    $campos['shipping_postcode'] =array( 
        'label' => __(  'Postcode /Zip', 'woocommerce'  ),
        'placeholder' => __(  'Postcode /Zip', 'woocommerce'  ),
        'required' => true,
        'class' =>array(  'form-row-wide', 'address-field'  ),
        'clear' => true,
        'custom_attributes' =>array( 
            'autocomplete' => 'no'
         )
	);
	 
	return $campos;
}
add_filter( 'woocommerce_shipping_fields' , 'formulario_de_envio' );

//Arreglamos el formulario de cobro
function formulario_de_cobro( $campos ) {
	$campos['billing_postcode'] =array( 
        'label' => __(  'Postcode /Zip', 'woocommerce'  ),
        'placeholder' => __(  'Postcode /Zip', 'woocommerce'  ),
        'required' => true,
        'class' => array(  'form-row-wide', 'address-field'  ),
        'clear' => true,
        'custom_attributes' => array( 
            'autocomplete' => 'no'
         )
	);
	
	return $campos;
}
add_filter( 'woocommerce_billing_fields' , 'formulario_de_cobro' );

//Añade el campo CIF/NIF a usuarios
function anade_campos_administracion_usuarios( $campos ) {
    $campos['billing']['fields']['billing_nif'] = array( 
            'label' => __( 'CIF/NIF', 'woocommerce' ),
            'description' => ''
	);

    $campos['shipping']['fields']['shipping_nif'] = array( 
            'label' => __( 'CIF/NIF', 'woocommerce' ),
            'description' => ''
	);
    $campos['shipping']['fields']['shipping_email'] = array( 
            'label' => __( 'Email', 'woocommerce' ),
            'description' => ''
	);
	$campos['shipping']['fields']['shipping_phone'] = array( 
            'label' => __( 'Telephone', 'woocommerce' ),
            'description' => ''
	);

    //Reordenamos los campos
    $campos_nuevos['billing']['title'] = $campos['billing']['title'];
    $campos_nuevos['billing']['fields']['billing_first_name'] = $campos['billing']['fields']['billing_first_name'];
    $campos_nuevos['billing']['fields']['billing_last_name'] = $campos['billing']['fields']['billing_last_name'];
    $campos_nuevos['billing']['fields']['billing_company'] = $campos['billing']['fields']['billing_company'];
    $campos_nuevos['billing']['fields']['billing_nif'] = $campos['billing']['fields']['billing_nif'];
    $campos_nuevos['billing']['fields']['billing_address_1'] = $campos['billing']['fields']['billing_address_1'];
    $campos_nuevos['billing']['fields']['billing_address_2'] = $campos['billing']['fields']['billing_address_2'];
    $campos_nuevos['billing']['fields']['billing_postcode'] = $campos['billing']['fields']['billing_postcode'];
    $campos_nuevos['billing']['fields']['billing_city'] = $campos['billing']['fields']['billing_city'];
    $campos_nuevos['billing']['fields']['billing_state'] = $campos['billing']['fields']['billing_state'];
    $campos_nuevos['billing']['fields']['billing_country'] = $campos['billing']['fields']['billing_country'];
    $campos_nuevos['billing']['fields']['billing_phone'] = $campos['billing']['fields']['billing_phone'];
    $campos_nuevos['billing']['fields']['billing_email'] = $campos['billing']['fields']['billing_email'];

    $campos_nuevos['shipping']['title'] = $campos['shipping']['title'];
    $campos_nuevos['shipping']['fields']['shipping_first_name'] = $campos['shipping']['fields']['shipping_first_name'];
    $campos_nuevos['shipping']['fields']['shipping_last_name'] = $campos['shipping']['fields']['shipping_last_name'];
    $campos_nuevos['shipping']['fields']['shipping_company'] = $campos['shipping']['fields']['shipping_company'];
    $campos_nuevos['shipping']['fields']['shipping_nif'] = $campos['shipping']['fields']['shipping_nif'];
    $campos_nuevos['shipping']['fields']['shipping_address_1'] = $campos['shipping']['fields']['shipping_address_1'];
    $campos_nuevos['shipping']['fields']['shipping_address_2'] = $campos['shipping']['fields']['shipping_address_2'];
    $campos_nuevos['shipping']['fields']['shipping_postcode'] = $campos['shipping']['fields']['shipping_postcode'];
    $campos_nuevos['shipping']['fields']['shipping_city'] = $campos['shipping']['fields']['shipping_city'];
    $campos_nuevos['shipping']['fields']['shipping_state'] = $campos['shipping']['fields']['shipping_state'];
    $campos_nuevos['shipping']['fields']['shipping_country'] = $campos['shipping']['fields']['shipping_country'];
    $campos_nuevos['shipping']['fields']['shipping_phone'] = $campos['shipping']['fields']['shipping_phone'];
    $campos_nuevos['shipping']['fields']['shipping_email'] = $campos['shipping']['fields']['shipping_email'];

    $campos_nuevos = apply_filters( 'wcbcf_customer_meta_fields', $campos_nuevos );
	
	return $campos_nuevos;
}
add_filter( 'woocommerce_customer_meta_fields', 'anade_campos_administracion_usuarios' );

//Añadimos el NIF a la dirección de facturación y envío
function anade_campo_nif_usuario_direccion_facturacion( $campos, $usuario ) {
    $campos['nif'] = get_user_meta( $usuario, 'billing_nif', true );
    $campos['phone'] = get_user_meta( $usuario, 'billing_phone', true );return $campos;
}
add_filter( 'woocommerce_user_column_billing_address','anade_campo_nif_usuario_direccion_facturacion', 1, 2 );

function anade_campo_nif_usuario_direccion_envio( $campos, $usuario ) {
    $campos['nif'] = get_user_meta( $usuario, 'shipping_nif', true );
    $campos['phone'] = get_user_meta( $usuario, 'shipping_phone', true );return $campos;
}
add_filter( 'woocommerce_user_column_shipping_address','anade_campo_nif_usuario_direccion_envio', 1, 2 );

//Añade el campo NIF a Editar mi dirección
function anade_campo_nif_editar_direccion( $campos, $usuario, $nombre ) {
    $campos['nif'] = get_user_meta( $usuario, $nombre . '_nif', true );
    $campos['phone'] = get_user_meta( $usuario, $nombre . '_phone', true );

    //Ordena los campos
    $campos_nuevos['first_name'] = $campos['first_name'];
    $campos_nuevos['last_name'] = $campos['last_name'];
    $campos_nuevos['company'] = $campos['company'];
    $campos_nuevos['nif'] = $campos['nif'];
    $campos_nuevos['address_1'] = $campos['address_1'];
    $campos_nuevos['address_2'] = $campos['address_2'];
    $campos_nuevos['postcode'] = $campos['postcode'];
    $campos_nuevos['city'] = $campos['city'];
    $campos_nuevos['state'] = $campos['state'];
    $campos_nuevos['country'] = $campos['country'];
    $campos_nuevos['phone'] = $campos['phone'];
	
	return $campos_nuevos;
}
add_filter( 'woocommerce_my_account_my_address_formatted_address', 'anade_campo_nif_editar_direccion', 10, 3 );

//Añade el campo NIF a Detalles del pedido
function anade_campo_nif_editar_direccion_pedido( $campos ) {
    $campos['nif'] = array( 
        'label' => __( 'CIF/NIF', 'woocommerce' ),
        'show'  => false
     );
    $campos['phone'] = array( 
        'label' => __( 'Telephone', 'woocommerce' ),
        'show'  => false
     );

    //Ordena los campos
    $campos_nuevos['first_name'] = $campos['first_name'];
    $campos_nuevos['last_name'] = $campos['last_name'];
    $campos_nuevos['company'] = $campos['company'];
    $campos_nuevos['nif'] = $campos['nif'];
    $campos_nuevos['address_1'] = $campos['address_1'];
    $campos_nuevos['address_2'] = $campos['address_2'];
    $campos_nuevos['postcode'] = $campos['postcode'];
    $campos_nuevos['city'] = $campos['city'];
    $campos_nuevos['state'] = $campos['state'];
    $campos_nuevos['country'] = $campos['country'];
    $campos_nuevos['phone'] = $campos['phone'];

    return $campos_nuevos;
}
add_filter( 'woocommerce_admin_billing_fields', 'anade_campo_nif_editar_direccion_pedido' );
add_filter( 'woocommerce_admin_shipping_fields', 'anade_campo_nif_editar_direccion_pedido' );

function carga_hoja_de_estilo_editar_direccion_pedido() {
    echo '</pre>
<style type="text/css"><!-- #order_data .order_data_column ._billing_company_field, #order_data .order_data_column ._shipping_company_field { float: left; margin: 9px 0 0; padding: 0; width: 48%; } #order_data .order_data_column ._billing_nif_field, #order_data .order_data_column ._shipping_nif_field { float: right; margin: 9px 0 0; padding: 0; width: 48%; } --></style>
<pre>';
}
add_action( 'woocommerce_admin_order_data_after_billing_address', 'carga_hoja_de_estilo_editar_direccion_pedido' );

//Validando el campo NIF/CIF
function validando_campo() {
	$falso = true;

    if ( isset( $_POST['billing_nif'] ) ) {
		$nif = strtoupper( $_POST['billing_nif'] );

		for ( $i = 0; $i < 9; $i ++ ) {
			$num[$i] = substr( $nif, $i, 1 );
		}

		if ( !preg_match( '/((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)/', $nif ) ) { //No tiene formato válido
			$falso = true;
		}

		if ( preg_match( '/(^[0-9]{8}[A-Z]{1}$)/', $nif ) ) {
			if ( $num[8] == substr( 'TRWAGMYFPDXBNJZSQVHLCKE', substr( $nif, 0, 8 ) % 23, 1 ) ) { //NIF válido
				$falso = false;
			}
		}

		$suma = $num[2] + $num[4] + $num[6];
		for ( $i = 1; $i < 8; $i += 2 ) {
			$suma += substr( ( 2 * $num[$i] ), 0, 1 ) + substr( ( 2 * $num[$i] ), 1, 1 );
		}
		$n = 10 - substr( $suma, strlen( $suma ) - 1, 1 );

		if ( preg_match( '/^[KLM]{1}/', $nif ) ) { //NIF especial válido
			if ( $num[8] == chr( 64 + $n ) ) {
				$falso = false;
			}
		}

		if ( preg_match( '/^[ABCDEFGHJNPQRSUVW]{1}/', $nif ) ) {
			if ( $num[8] == chr( 64 + $n ) || $num[8] == substr( $n, strlen( $n ) - 1, 1 ) ) { //CIF válido
				$falso = false;
			}
		}

		if ( preg_match( '/^[T]{1}/', $nif ) ) {
			if ( $num[8] == preg_match( '/^[T]{1}[A-Z0-9]{8}$/', $nif ) ) { //NIE válido (T)
				$falso = false;
			}
		}
		if ( preg_match( '/^[XYZ]{1}/', $nif ) ) { //NIE válido (XYZ)
			if ( $num[8] == substr( 'TRWAGMYFPDXBNJZSQVHLCKE', substr( str_replace( array( 'X','Y','Z' ), array( '0','1','2' ), $nif ), 0, 8 ) % 23, 1 ) ) {
				$falso = false;
			}
		}
	}

	if ( $falso ) {
		wc_add_notice( __( 'Por favor, introduzca un NIF/CIF válido.' ), 'error' );
	}
}
add_action( 'woocommerce_checkout_process', 'validando_campo' );

Conclusión

Esperamos que el bueno de Andy de por satisfecha la necesidad planteada a APG y que a vosotros os resulte útil esta función en vuestras tiendas virtuales.

Tanto si lo has usado como si has conseguido mejorarlo, quedamos a la espera de vuestros comentarios y aportaciones.

Actualización: la actualización para WooCommerce 2.4 está disponible en ¿Cómo añadir un campo NIF o CIF con validación a WooCommerce 2.4?.