{ "woocommerce" :

Champs KvK et TVA dans le paiement WooCommerce

Réponse courte

WooCommerce n'a pas de champ de registre KvK ou TVA. Vous l'ajoutez vous-même : sur le paiement classique via le filtre woocommerce_billing_fields, validé dans woocommerce_after_checkout_validation et écrit dans les métadonnées de la commande. Nous ne proposons pas d'extension WooCommerce. Nous fournissons une API REST et un widget que vous appelez depuis ces hooks.

cURL
# contrôle côté serveur au paiement
curl ".../v1/lookup/68750110" \
  -H "Authorization: Bearer KEY"

{
  "name": "Acme B.V.",
  "isActive": true,
  "vat": {
    "number": "NL123456789B01",
    "valid": true
  }
}

Où les données d’entreprise doivent atterrir

WooCommerce n’a pas de champ de registre. Il a billing_company, qui est du texte libre. Dès que vous vendez en B2B aux Pays-Bas, il vous faut trois endroits où ranger les données.

EmplacementCe qui doit s’y trouverPourquoi
Champ de paiementNuméro KvK, saisi ou choisiSaisie et validation
Métadonnées de commandeNuméro KvK, dénomination sociale, numéro de TVAFacture et comptabilité
Métadonnées clientLes mêmes, par défautDeuxième commande sans ressaisie

Ce que les équipes négligent : une commande est un instantané. Si vous ne stockez que le numéro KvK et récupérez le nom plus tard, votre facture de 2024 ne correspondra plus à la réalité quand l’entreprise changera de nom en 2026. Écrivez le nom et l’adresse sur la commande, pas seulement la clé.

Paiement classique : champ, validation, stockage

Trois hooks, dans cet ordre. Mettez-les dans une extension propre au site plutôt que dans le functions.php d’un thème que vous remplacerez plus tard.

add_filter( 'woocommerce_billing_fields', function ( $fields ) {
    $fields['billing_kvk'] = [
        'label'    => 'Numéro KvK',
        'required' => false,
        'class'    => [ 'form-row-wide' ],
        'priority' => 35,
    ];
    return $fields;
} );

add_action( 'woocommerce_after_checkout_validation', function ( $data, $errors ) {
    $kvk = trim( $data['billing_kvk'] ?? '' );
    if ( $kvk === '' ) {
        return;
    }
    if ( ! preg_match( '/^\d{8}$/', $kvk ) ) {
        $errors->add( 'billing_kvk', 'Un numéro KvK compte 8 chiffres.' );
        return;
    }

    $res = wp_remote_get( "https://api.kvkbase.nl/v1/lookup/{$kvk}", [
        'timeout' => 3,
        'headers' => [ 'Authorization' => 'Bearer ' . KVKBASE_KEY ],
    ] );

    if ( is_wp_error( $res ) || wp_remote_retrieve_response_code( $res ) >= 500 ) {
        return; // API injoignable : laisser passer la commande, la signaler ensuite.
    }
    if ( wp_remote_retrieve_response_code( $res ) === 404 ) {
        $errors->add( 'billing_kvk', 'Ce numéro KvK ne figure pas au registre du commerce.' );
    }
}, 10, 2 );

add_action( 'woocommerce_checkout_create_order', function ( $order, $data ) {
    if ( ! empty( $data['billing_kvk'] ) ) {
        $order->update_meta_data( '_billing_kvk', wc_clean( $data['billing_kvk'] ) );
    }
}, 10, 2 );

Le return anticipé sur un 5xx ou une erreur de transport est voulu. Un paiement qui bloque parce qu’un tiers est lent vous coûte du chiffre d’affaires. Laissez passer la commande et ajoutez une note de commande ou un indicateur de statut pour un contrôle manuel.

Le paiement par blocs est une seconde implémentation

Maintenant que le paiement par blocs est celui par défaut, le formulaire repose sur React et les filtres classiques ne se déclenchent jamais. Il faut y déclarer le champ via l’API Additional Checkout Fields sur woocommerce_init, sous votre propre espace de noms. Deux choses à savoir :

  • La valeur est stockée sous une clé de métadonnées avec espace de noms, pas sous _billing_kvk. Tout rapport ou extension de facturation qui lit votre ancienne clé renvoie des colonnes vides.
  • La validation côté serveur s’accroche aux hooks de validation propres à cette API, pas à woocommerce_after_checkout_validation.

Si vous faites tourner les deux paiements côte à côte, écrivez dans la même clé de métadonnées personnalisée dans les deux chemins. Sinon, vos rapports doivent être construits en double.

Le widget comme aide à la saisie

Si vous voulez que les clients cherchent par nom d’entreprise plutôt que de taper un numéro, branchez le widget sur le champ. Une seule balise script, avec des sélecteurs CSS pour les champs qu’il peut remplir :

<script
  src="https://widget.kvkbase.nl/v1/widget.js"
  data-apikey="PUBLIC_KEY"
  data-target="#billing_kvk"
  data-autofill="true"
  data-name-target="#billing_company"
  data-city-target="#billing_city"
  data-postal-target="#billing_postcode"
  data-vat-target="#billing_vat"
></script>

Le widget remplit les champs dans le navigateur. C’est une aide à la saisie, pas une validation : tout ce qu’un navigateur remplit, un navigateur peut le modifier. Faites le contrôle qui compte côté serveur dans le hook ci-dessus, avec une clé qui ne figure pas dans le code source de votre page. La data-apikey est publique par définition.

Sur le paiement classique, cela fonctionne tel quel. Sur le paiement par blocs, le champ n’existe pas encore au chargement du script, donc un sélecteur simple ne trouve rien. Branchez le widget après l’affichage du champ, ou appelez directement l’API depuis votre propre composant de bloc.

Ce qui casse réellement en production

Le client colle un numéro d’établissement. Les factures et e-mails de confirmation néerlandais affichent souvent le vestigingsnummer à 12 chiffres au lieu du numéro KvK à 8 chiffres. Votre expression régulière l’attrape, mais le message d’erreur doit préciser quel numéro vous attendez, sinon le client abandonne.

Une vraie entreprise renvoie 404. Les entrées radiées disparaissent du registre. Traitez NOT_FOUND comme « n’est plus active » plutôt que comme « faute de frappe », et laissez un client existant poursuivre avec un indicateur de contrôle manuel.

VIES est injoignable. La validation de TVA s’appuie sur le service de l’État membre, qui tombe régulièrement. Vous recevez UPSTREAM_ERROR et pas de valid: true. Ne bloquez jamais le paiement là-dessus : appliquez la TVA, traitez la commande, et corrigez ensuite si l’autoliquidation s’avère applicable. Le fonctionnement est détaillé dans validation du numéro de TVA avec l’API VIES.

Un rattrapage en masse atteint la limite de débit. Enrichir les commandes existantes avec un foreach sur 3 000 clients épuise la limite par minute en quelques secondes. Utilisez POST /v1/lookup/batch et traitez par paquets depuis une action cron plutôt qu’une recherche par ligne.

Questions fréquentes

Avez-vous une extension WooCommerce ?
Non. Nous fournissons une API REST et un widget JavaScript. Vous construisez vous-même le côté WooCommerce, dans un thème enfant ou une petite extension propre au site, avec les hooks de paiement standard. En pratique, cela représente trente à cinquante lignes de PHP.
Le champ KvK doit-il être obligatoire ?
Seulement si vous devez séparer les commandes professionnelles des commandes de particuliers. Dans une boutique qui sert les deux, gardez-le facultatif et affichez-le quand le client renseigne un nom d'entreprise. Le rendre obligatoire pour tous vous coûte de la conversion sur les commandes de particuliers.
Le même code fonctionne-t-il dans le paiement par blocs ?
Non. Le paiement par blocs repose sur React et ignore woocommerce_billing_fields. Il faut y déclarer le champ via l'API Additional Checkout Fields, et la valeur est stockée sous une autre clé de métadonnées de commande que celle du paiement classique.
Puis-je déduire le numéro de TVA du numéro KvK ?
Pour une BV ou une NV, oui. Le numéro de TVA est NL plus le numéro KvK plus B01, et nous le vérifions auprès de VIES avant de le renvoyer. Cette déduction ne vaut pas pour les entreprises individuelles ni pour les sociétés de personnes : laissez alors le champ vide et demandez au client.

Mis à jour:

Un seul appel vous donne toute l'entreprise

Les données KvK, le numéro de TVA déduit et un contrôle VIES en direct, en une seule requête. L'offre gratuite comprend 50 recherches par mois, sans carte bancaire.

50
recherches gratuites par mois
1
requête au lieu de trois
0
carte bancaire, contrat ou appel commercial