ITWoocommerceWordpress

Woocommerce: dati di fatturazione facoltativi

In un precedente articolo ho affrontato il tema di come inserire campi per Codice Fiscale e partita IVA su Woocommerce. Con questo nuovo articolo affronto lo stesso argomento ma con la possibilità di rendere questi campi non obbligatori.

Non solo, per semplificare ulteriormente il form di checkout, la visibilità di questi campi avverrà solo se l’utente marca un apposito flag di richiesta fattura.

L’approccio che ho seguito è stato quello di nascondere i campi tramite CSS e farli apparire con un piccolo script jQuery. Probabilmente ci sono metodi migliori, ma mi serviva una cosa semplice da implementare e senza fronzoli.

Ho poi aggiunto qualche controllo aggiuntivo, ad esempio:

  • Se l’utente è Italiano e non ha inserito il nome di un’azienda, si presume sia un cliente privato. In questo caso viene validato il codice fiscale che dev’essere coerente: usando il codice di Manuel Marangoni possiamo controllare che il Codice Fiscale non sia vuoto, abbia la giusta lunghezza (16 caratteri) e che il codice di controllo (l’ultimo carattere della stringa);
  • Se l’utente mette il “flag” per chiedere la fattura, i campi di indirizzo diventano obbligatori. Diventa obbligatorio inserire anche il Codice Fiscale e/o la Partita IVA;
  • Il controllo viene fatto su billing_country, billing_address_1 e billing_city. Ho evitato di usare billing_state e billing_postcode perché in alcuni stati esteri non esistono e di conseguenza potrebbero essere vuoti

Ma vediamo più nel dettaglio il codice. Per prima cosa creiamo i campi necessari e facciamo in modo che vengano memorizzati tra i metadati associati all’ordine:


add_filter( 'woocommerce_checkout_fields' , 'custom_additional_fields' );
function custom_additional_fields( $fields ) {
	// Richiesta fattura
	$fields['billing']['fattura'] = array(
	'label'     => __('Richiedo la fattura', 'woocommerce'),
	'type'		=> 'checkbox',
	'placeholder'   => _x('Fattura', 'placeholder', 'woocommerce'),
	'required'  => false,
	'class'     => array('form-row-last'),
	'clear'     => true,
	'priority'  => 5
	);
	// Codice Fiscale
	$fields['billing']['codice_fiscale'] = array(
		'label'     => __('Codice Fiscale', 'woocommerce'),
		'placeholder'   => _x('Codice Fiscale', 'placeholder', 'woocommerce'),
		'required'  => false,
		'class'     => array('form-row-last'),
		'clear'     => true,
		'priority'  => 180
	);
	// Partita IVA
	$fields['billing']['partita_iva'] = array(
		'label'     => __('Partita IVA', 'woocommerce'),
		'placeholder'   => _x('Partita IVA', 'placeholder', 'woocommerce'),
		'required'  => false,
		'class'     => array('form-row-last'),
		'clear'     => true,
		'priority'  => 190
	);

	return $fields;
}

add_action( 'woocommerce_checkout_update_order_meta', 'custom_order_meta' );    
function custom_order_meta( $order_id ) {
 if ( ! empty( $_POST['fattura'] ) ) {
 update_post_meta( $order_id, 'fattura', strtoupper( sanitize_text_field( $_POST['fattura'] ) ) );
 }
 if ( ! empty( $_POST['codice_fiscale'] ) ) {
 update_post_meta( $order_id, 'codice_fiscale', strtoupper( sanitize_text_field( $_POST['codice_fiscale'] ) ) );
 }
 if ( ! empty( $_POST['partita_iva'] ) ) {
 update_post_meta( $order_id, 'partita_iva', sanitize_text_field( $_POST['partita_iva'] ) );
 }
}

Ora dobbiamo fare in modo che questi campi vengano inseriti nel backend, in modo che siano visibili nei dettagli dell’ordine, quando sono visualizzati su Woocommerce:


add_action( 'woocommerce_admin_order_data_after_billing_address', 'custom_order_meta_admin', 10, 1 );
function custom_order_meta_admin($order){

	if (get_post_meta( $order->id, 'fattura', true )) {
		$fattura = "Sì";
	} else {
		$fattura = "No";
	}

	echo '

Dati di fatturazione

'; echo '
'; echo '

'.__('Richiesta Fattura').': ' . $fattura . '

'; echo '

'.__('Codice Fiscale').': ' . get_post_meta( $order->id, 'codice_fiscale', true ) . '

'; echo '

'.__('Partita IVA').': ' . get_post_meta( $order->id, 'partita_iva', true ) . '

'; echo '
'; }

Analogamente facciamo in modo che i nuovi campi vengano inseriti nelle mail che invia Woocommerce. Questo esempio è basato sul template standard, va modificato per aderire ad eventuali template personalizzati per l’invio di email:


add_filter('woocommerce_email_order_meta', 'custom_order_meta_keys', 30, 3);
function custom_order_meta_keys( $order, $sent_to_admin, $plain_text ) {

	$fattura = get_post_meta( $order->get_order_number(), 'fattura', true );

	if( empty( $fattura ) ) // se non è stata richiesta fattura esco senza aggiungere nulla
		return;

	$codice_fiscale = get_post_meta( $order->get_order_number(), 'codice_fiscale', true );
	$partita_iva = get_post_meta( $order->get_order_number(), 'partita_iva', true );

	if ( $plain_text === false ) { // versione HTML
		echo '
		

Dati di fatturazione

Richiesta fattura? Sì
Codice Fiscale: ' . $codice_fiscale . '
Partita IVA: ' . $partita_iva . '
'; } else { // versione plain text echo "DATI FATTURAZIONE\n Richiesta fattura: Sì Codice Fiscale: $codice_fiscale Partita IVA: $partita_iva"; } }

Fatto questo possiamo aggiungere una funzione che memorizza i dati inseriti nel profilo utente, così che il nostro cliente non debba digitarli ogni volta:

add_action( 'woocommerce_checkout_update_user_meta', 'custom_checkout_update_user_meta', 10, 2 );
function custom_checkout_update_user_meta( $customer_id, $posted ) {
	if (isset($posted['partita_iva'])) {
		$partita_iva = sanitize_text_field( $posted['partita_iva'] );
		update_user_meta( $customer_id, 'partita_iva', $partita_iva);
	}
	if (isset($posted['codice_fiscale'])) {
		$codice_fiscale = sanitize_text_field( $posted['codice_fiscale'] );
		update_user_meta( $customer_id, 'codice_fiscale', $codice_fiscale);
	}
	if (isset($posted['fattura'])) {
		$codice_fiscale = sanitize_text_field( $posted['fattura'] );
		update_user_meta( $customer_id, 'fattura', $codice_fiscale);
	}

}

Adesso andiamo a gestire il form di checkout. Utilizzando una combinazione di CSS e jQuery faccio in modo che i vari campi di fatturazione appaiano solo se l’utente seleziona la checkbox per la richiesta della fattura. Questo renderà il nostro form più pulito per i clienti che non necessitano fattura:

add_action( 'woocommerce_after_checkout_form', 'custom_add_jscript_checkout');
function custom_add_jscript_checkout() {
echo <<<EOT
 <style type="text/css">
 #billing_phone_field,
 #billing_country_field,
 #billing_company_field,
 #billing_address_1_field,
 #billing_address_2_field,
 #billing_postcode_field,
 #billing_city_field,
 #billing_state_field,
 #codice_fiscale_field,
 #partita_iva_field { display : none!important; }
 </style>
 
 <script type="text/javascript">
 jQuery(function(t) {
  var checkbox = "#fattura"
  // campi da mostrare/nascondere
  var fields = ["#billing_country_field",
                "#billing_company_field",
                "#billing_address_1_field",
                "#billing_address_2_field",
                "#billing_postcode_field",
                "#billing_city_field",
                "#billing_state_field",
                "#codice_fiscale_field",
                "#partita_iva_field"];
                                 
  function check(){
     if( t(checkbox).is(":checked") ) {
      fields.forEach(function (item) {
        t(item).attr("style", "display: block!important");
      });
     } else {
        fields.forEach(function (item) {
         t(item).attr("style", "display: none!important");
      });
      }
      // workaround per far renderizzare correttamente il selettore di country
      t("#billing_country").trigger("change");
      }
                                 
      // al cambio della checkbox mostro o nascondo i campi
      t(checkbox).change(function() {
        check();
      })
      check(); // eseguo al caricamento della pagina
   })
 </script>
 EOT;
 } 

Per concludere possiamo aggiungere una semplice validazione dei campi e del codice fiscale: in base alle scelte dell’utente definiamo alcune politiche di obbligatorietà. Ad esempio se l’utente ha inserito una azienda (ragione sociale) ma non ha compilato la partita IVA riceve un errore. Allo stesso modo se compila la partita IVA ma non il nome della società riceve un errore.

Se si tratta di un utente privato, usando il codice di Manuel Marangoni possiamo controllare che il Codice Fiscale non sia vuoto, abbia la giusta lunghezza (16 caratteri) e che il codice di controllo (l’ultimo carattere della stringa).

 add_action('woocommerce_checkout_process', 'required_cf_checkout_field_process');
 function required_cf_checkout_field_process() {
                 if ($_POST['fattura'] == true) {
                                 if ($_POST['codice_fiscale'] == null || $_POST['partita_iva'])
                                                 wc_add_notice( __( '<b>Per ricevere la fattura devi inserire una Partita IVA e/o un Codice Fiscale valido.</b>' ), 'error' );
                                 if ($_POST['billing_country'] == null || $_POST['billing_address_1'] == null || $_POST['billing_city'] == null )
                                                 wc_add_notice( __( '<b>Per ricevere la fattura devi inserire un indirizzo valido.</b>'), 'error' );
                                 if (($_POST['billing_country'] == 'IT' || $_POST['billing_country'] == 'Italia') && $_POST['billing_company'] == '' ) // verifico se è un utente italiano e privato (ovvero senza billing_company)
                                                 if (!validateCodiceFiscale($_POST['codice_fiscale'])) // in questo caso valido il codice fiscale
                                                                 wc_add_notice( __( '<b>Se sei residente in Italia e desideri la fattura come utente privato, devi inserire un Codice Fiscale valido. In alternativa rimuovi la spunta dalla richiesta fattura oppure compila il nome della Società (Ragione Sociale).</b>' . $_POST['billing_company'] ), 'error' );
                 }
 }
  
 // code by Manuel Marangoni - http://www.manuelmarangoni.it 
 /** controllo del codice fiscale **/
 function validateCodiceFiscale($cf){
                 
                 if($cf=='')
                                 return false;
                 
                 if(strlen($cf)!= 16)
                                 return false;
                                 
                 $cf=strtoupper($cf);
                 if(!preg_match("/[A-Z0-9]+$/", $cf))
                                 return false;
  
                 $s = 0;
                 
                 for($i=1; $i<=13; $i+=2){
                                 $c=$cf[$i];
                                 if('0'<=$c and $c<='9')
                                                 $s+=ord($c)-ord('0');
                                 else
                                                 $s+=ord($c)-ord('A');
                 }
                 
                 for($i=0; $i<=14; $i+=2){
                                 $c=$cf[$i];
                                 switch($c){
                                                 case '0':  $s += 1;  break;
                                                 case '1':  $s += 0;  break;
                                                 case '2':  $s += 5;  break;
                                                 case '3':  $s += 7;  break;
                                                 case '4':  $s += 9;  break;
                                                 case '5':  $s += 13;  break;
                                                 case '6':  $s += 15;  break;
                                                 case '7':  $s += 17;  break;
                                                 case '8':  $s += 19;  break;
                                                 case '9':  $s += 21;  break;
                                                 case 'A':  $s += 1;  break;
                                                 case 'B':  $s += 0;  break;
                                                 case 'C':  $s += 5;  break;
                                                 case 'D':  $s += 7;  break;
                                                 case 'E':  $s += 9;  break;
                                                 case 'F':  $s += 13;  break;
                                                 case 'G':  $s += 15;  break;
                                                 case 'H':  $s += 17;  break;
                                                 case 'I':  $s += 19;  break;
                                                 case 'J':  $s += 21;  break;
                                                 case 'K':  $s += 2;  break;
                                                 case 'L':  $s += 4;  break;
                                                 case 'M':  $s += 18;  break;
                                                 case 'N':  $s += 20;  break;
                                                 case 'O':  $s += 11;  break;
                                                 case 'P':  $s += 3;  break;
                                                 case 'Q':  $s += 6;  break;
                                                 case 'R':  $s += 8;  break;
                                                 case 'S':  $s += 12;  break;
                                                 case 'T':  $s += 14;  break;
                                                 case 'U':  $s += 16;  break;
                                                 case 'V':  $s += 10;  break;
                                                 case 'W':  $s += 22;  break;
                                                 case 'X':  $s += 25;  break;
                                                 case 'Y':  $s += 24;  break;
                                                 case 'Z':  $s += 23;  break;
                                 }
                 }
                 
                 if( chr($s%26+ord('A'))!=$cf[15] )
                                 return false;
                                 
                 return true;
 } 

Trovate il codice completo su GitHub.

Nota importante: il codice che ho indicato va considerato sperimentale, sconsiglio di usarlo in ambienti di produzione senza averlo testato prima in ambienti staging e sopratutto fate sempre un backup prima di qualunque modifica!

L’ho testato con Woocommerce 5.0, versioni diverse potrebbero avere strutture dati diverse e quindi questo approccio potrebbe non essere corretto.