# Créer une méthode d’expédition personnalisée avec WC_Shipping_Method

> Un transporteur régional facture au poids et à la zone, sans extension disponible sur le marché : voici comment coder une méthode d'expédition WooCommerce sur mesure.

- Auteur : Clément Hadrot
- Publié le : 2020-01-14
- Mis à jour le : 2020-01-14
- Catégorie : E-commerce
- URL : https://wpmoderne.dev.wordpress-developpement.fr/ecommerce/creer-methode-expedition-personnalisee-wc-shipping-method/

## L’essentiel

- Une classe PHP suffit à définir une méthode complète
- Le tarif se calcule au runtime, zone par zone
- Les réglages s'affichent nativement dans l'admin

Un client de transport routier facture ses livraisons selon une grille propre : poids réel, poids volumétrique et un forfait « zone rouge » pour certains départements mal desservis. Aucune extension du marché ne colle à cette grille, et le client refuse de payer un abonnement mensuel pour une fonctionnalité qu'il pourrait obtenir avec quelques dizaines de lignes de PHP. C'est le cas typique où WooCommerce donne accès à sa brique la plus bas niveau : la classe `WC_Shipping_Method`.

Contrairement à une extension tierce qui empile des réglages génériques, une méthode d'expédition maison colle exactement au métier du client. Ce tutoriel construit une méthode facturant au poids avec un supplément par zone, étape par étape, sans dépendance externe.

## Étape 1 : la structure minimale du plugin

Une méthode d'expédition personnalisée vit idéalement dans un petit plugin dédié plutôt que dans le thème, pour survivre à un changement de thème. Le fichier principal charge la classe une fois WooCommerce actif :

```
add_action( 'plugins_loaded', 'transporteur_maison_init' );

function transporteur_maison_init() {
    if ( ! class_exists( 'WC_Shipping_Method' ) ) {
        return;
    }
    require_once __DIR__ . '/class-wc-shipping-transporteur-maison.php';
}
```

Vérifier l'existence de `WC_Shipping_Method` évite une erreur fatale si WooCommerce est désactivé le temps d'une maintenance.

## Étape 2 : déclarer la méthode auprès de WooCommerce

> L'essentiel à retenir : Une classe PHP suffit à définir une méthode complète ; Le tarif se calcule au runtime, zone par zone ; Les réglages s'affichent nativement dans l'admin

WooCommerce découvre les méthodes de livraison disponibles via le filtre `woocommerce_shipping_methods`. Il faut y ajouter le nom de la classe :

```
add_filter( 'woocommerce_shipping_methods', 'transporteur_maison_register' );

function transporteur_maison_register( $methods ) {
    $methods['transporteur_maison'] = 'WC_Shipping_Transporteur_Maison';
    return $methods;
}
```

Une fois ce filtre en place, la méthode apparaît dans *Réglages > Livraison > Zones de livraison > Ajouter une méthode de livraison*, au même titre que la livraison à taux fixe fournie en cœur.

## Étape 3 : écrire la classe et ses réglages

La classe hérite de `WC_Shipping_Method` et définit son identifiant, son titre par défaut et ses champs de réglage dans le constructeur :

```
class WC_Shipping_Transporteur_Maison extends WC_Shipping_Method {

    public function __construct( $instance_id = 0 ) {
        $this->id                 = 'transporteur_maison';
        $this->instance_id        = absint( $instance_id );
        $this->method_title       = __( 'Transporteur maison', 'transporteur-maison' );
        $this->method_description = __( 'Tarif au poids avec supplément zone rouge.', 'transporteur-maison' );
        $this->supports           = array( 'shipping-zones', 'instance-settings' );

        $this->init();
    }

    public function init() {
        $this->init_form_fields();
        $this->init_settings();

        $this->title           = $this->get_option( 'title', 'Transporteur maison' );
        $this->tarif_kilo       = (float) $this->get_option( 'tarif_kilo', 0.35 );
        $this->supplement_zone  = (float) $this->get_option( 'supplement_zone', 4.5 );
        $this->departements_zone = array_map( 'trim', explode( ',', $this->get_option( 'departements_zone', '' ) ) );

        add_action( 'woocommerce_update_options_shipping_' . $this->id, array( $this, 'process_admin_options' ) );
    }

    public function init_form_fields() {
        $this->instance_form_fields = array(
            'title' => array(
                'title'   => __( 'Titre affiché', 'transporteur-maison' ),
                'type'    => 'text',
                'default' => 'Transporteur maison',
            ),
            'tarif_kilo' => array(
                'title'   => __( 'Tarif par kilo (€)', 'transporteur-maison' ),
                'type'    => 'number',
                'default' => '0.35',
            ),
            'supplement_zone' => array(
                'title'   => __( 'Supplément zone rouge (€)', 'transporteur-maison' ),
                'type'    => 'number',
                'default' => '4.50',
            ),
            'departements_zone' => array(
                'title'       => __( 'Départements zone rouge', 'transporteur-maison' ),
                'type'        => 'text',
                'description' => __( 'Codes séparés par des virgules, ex. 04,05,06', 'transporteur-maison' ),
                'default'     => '',
            ),
        );
    }
}
```

## Étape 4 : calculer le tarif au moment du panier

Toute la logique métier tient dans `calculate_shipping()`, appelée à chaque recalcul du panier ou de la commande. La méthode reçoit un tableau contenant, entre autres, le poids total et l'adresse de livraison :

```
public function calculate_shipping( $package = array() ) {
    $poids = 0;
    foreach ( $package['contents'] as $item ) {
        $poids += (float) $item['data']->get_weight() * $item['quantity'];
    }

    $cout = $poids * $this->tarif_kilo;

    $code_postal   = $package['destination']['postcode'];
    $departement   = substr( $code_postal, 0, 2 );

    if ( in_array( $departement, $this->departements_zone, true ) ) {
        $cout += $this->supplement_zone;
    }

    $this->add_rate(
        array(
            'id'    => $this->get_rate_id(),
            'label' => $this->title,
            'cost'  => round( $cout, 2 ),
        )
    );
}
```

Le poids est lu via `get_weight()` sur chaque objet produit, en cohérence avec l'unité configurée dans *WooCommerce > Réglages > Produits*. Un produit sans poids renseigné compte pour zéro : il vaut mieux forcer sa saisie côté catalogue plutôt que de le tolérer silencieusement.

## Étape 5 : tester dans un vrai tunnel de commande

- Ajouter la méthode à une zone existante puis vider le cache de session panier avant de tester.
- Vérifier le calcul avec un panier de plusieurs produits aux poids différents.
- Simuler une adresse dans un département de la zone rouge et une adresse hors zone.
- Contrôler l'arrondi affiché en caisse, notamment avec la TVA activée.

> Sur un projet de ce type, mieux vaut toujours prévoir un log temporaire dans `calculate_shipping()` via `error_log()` le temps des tests : le calcul de frais de port est justement le genre d'endroit où une erreur silencieuse coûte cher en support client.

## En résumé

Coder une méthode d'expédition sur mesure demande une seule classe, deux filtres (`woocommerce_shipping_methods` et l'action de sauvegarde des options) et une méthode `calculate_shipping()` qui reflète fidèlement la grille tarifaire du transporteur. Cette approche évite d'empiler des règles génériques mal adaptées et donne un contrôle total sur chaque paramètre de calcul, tout en s'intégrant nativement dans l'interface de gestion des zones de livraison.
