Personnaliser l’expérience du tunnel de commande WooCommerce

Découvrons comment ajouter des champs personnalisés lors de la dernière étape de commande WooCommerce. Nous verrons aussi comment modifier le prix du panier selon la valeur de ces champs.

Durant le développement d’une boutique e-commerce sur-mesure, on fait souvent appel à des extensions tierces pour personnaliser l’expérience d’achat du client.

Mais selon le besoin, il est parfois difficile de trouver chaussure à son pied.

Prenons ici le temps de comprendre comment il est possible de personnaliser la page Commande (Checkout) de WooCommerce pour ainsi obtenir un tunnel tout à fait unique. Avec les bons outils en main, on se rendra compte que ce n’est peut-être pas si compliqué !

Information

Le code complet de ce tutoriel a été regroupé dans un plugin WordPress librement téléchargeable ici.

Objectif du tutoriel

Avant de détailler ce que l’on va mettre en place, rien ne vaut une image pour illustrer…

Démonstration du plugin (cliquez ici pour voir une vidéo plus complète)

Nous créons une boutique en ligne pour acheteurs pressés. Sur la page de commande, nous allons ajouter une zone — avant les détails de facturation — où le client pourra :

  1. cocher une checkbox pour indiquer qu’il est pressé 🍋
  2. utiliser un menu déroulant pour choisir son niveau de pressage 🧃

Selon l’urgence de sa demande, un frais supplémentaire viendra s’ajouter à son panier. Ce surplus équivaut à :

  • 2 fois la valeur du panier pour un envoi ultra-rapide dans l’heure qui suit
  • 1.5 fois pour un envoi dans les 6 heures
  • 1.25 fois pour un envoi dans les 12 heures
  • 1.1 fois pour un envoi avant le lendemain

Ce petit exercice va alors nous permettre d’ajouter des nouveaux champs sur le checkout de WooCommerce, de les valider lors de l’achat et d’ajouter d’éventuels frais au panier selon la valeur de ces champs.

Une fois la commande placée, on sauvegardera les valeurs de nos 2 champs (par défaut totalement inconnus aux yeux de WooCommerce) dans des métadonnées de la commande.

Un checkout WooCommerce aux petits oignons !

Ajouter des champs personnalisés sur le formulaire de commande

La première étape va être d’ajouter les nouveaux champs que nous souhaitons voir apparaître dans le formulaire de commande.

<?php function display_custom_shipping_methods() {
	?>
	<fieldset class="extra-fields">
		<legend><?php _e( 'Emergency', 'wc_ccf' ); ?></legend>

		<p>
			<label for="msk-urgent-order">
				<input type="checkbox" name="msk-urgent-order" id="msk-urgent-order" value="on" class="msk-custom-field" />
				<span><?php esc_html_e( 'This is an emergency, send me my order ASAP!', 'wc_ccf' ); ?></span>
			</label>
		</p>

		<p>
			<label for="msk-urgency-level">
				<span><?php esc_html_e( 'When do you want this shipped?', 'wc_ccf' ); ?></span>
				<select name="msk-urgency-level" id="msk-urgency-level" class="msk-custom-field">
					<option value="_null"><?php esc_attr_e( '— Select an option', 'wc_ccf' ); ?></option>
					<option value="next_hour"><?php esc_attr_e( 'In the next hour', 'wc_ccf' ); ?></option>
					<option value="next_6hours"><?php esc_attr_e( 'In the next 6 hours', 'wc_ccf' ); ?></option>
					<option value="next_12hours"><?php esc_attr_e( 'In the next 12 hours', 'wc_ccf' ); ?></option>
					<option value="tomorrow"><?php esc_attr_e( 'By tomorrow', 'wc_ccf' ); ?></option>
				</select>
			</label>
		</p>
	</fieldset>

	<script>
		// When one of our custom field value changes, tell WC to update the checkout data (AJAX request to the back-end).
		jQuery(document).ready(function($) {
			$('form.checkout').on('change', '.msk-custom-field', function() {
				$('body').trigger('update_checkout');
			});
		});
	</script>
	<?php
}
add_action( 'woocommerce_checkout_billing', __NAMESPACE__ . '\\display_custom_shipping_methods', 10 );

On va profiter du hook woocommerce_checkout_billing de WooCommerce pour injecter des nouveaux champs dans le formulaire de commande. Ce hook nous permettra de les afficher avant les détails de facturation demandés au client.

A savoir

Regardez le template /templates/checkout/form-checkout.php de WooCommerce pour découvrir les différents hooks à disposition pour insérer vos propres champs supplémentaires. A l’heure où j’écris, WooCommerce propose pas moins de 8 actions placées à divers endroits du formulaire, vous devriez trouver ce qu’il vous faut pour placer vos nouveaux champs où bon vous semble !

Ici, rien de folichon : on ajoute notre propre code HTML pour faire apparaître de nouveaux champs (dans notre cas, une case à cocher et un menu déroulant).

Attention

Pensez bien à indiquer des attributs name uniques sur vos champs pour éviter les conflits.

En fin de fonction, un code JavaScript va indiquer à WooCommerce de « relancer sa tambouille » de calcul de prix de panier à chaque fois qu’une interaction a été faite sur un de nos deux champs. On va pouvoir ainsi garder un panier toujours à jour selon le choix d’urgence choisi par l’acheteur.

Le fait de déclencher l’événement update_checkout sur le body de la page permet à WooCommerce de comprendre qu’on a modifié des informations et qu’il est temps de re-calculer le panier pour délivrer un prix adéquat au client.

S’assurer de la validité des valeurs de nos nouveaux champs

Nos champs sont créés, leurs valeurs sont envoyées à WooCommerce lorsque l’on envoie la commande. Il est pourtant judicieux d’ajouter une couche de validation et vérifier la validité de nos nouveaux champs pour potentiellement empêcher l’envoi de la commande et afficher un message d’erreur.

<?php function validate_all_checkout_fields() {
	$errors = [];

	if ( isset( $_POST['msk-urgent-order'] ) && $_POST['msk-urgent-order'] === 'on' ) {
		if ( ! isset( $_POST['msk-urgency-level'] ) || $_POST['msk-urgency-level'] === '_null' ) {
			$errors[] = __( 'Please tell us the <strong>level of emergency</strong>.', 'wc_ccf' );
		}

		elseif ( isset( $_POST['msk-urgency-level'] ) && ! in_array( $_POST['msk-urgency-level'], [ 'next_hour', 'next_6hours', 'next_12hours', 'tomorrow' ], true ) ) {
			$errors[] = __( 'This <strong>level of emergency</strong> is invalid.', 'wc_ccf' );
		}
	}

	/**
	 * If we have errors, 
	 */
	if ( ! empty( $errors ) ) {
		foreach ( $errors as $error ) {
			wc_add_notice( $error, 'error' );
		}
	}
}
add_action( 'woocommerce_checkout_process', __NAMESPACE__ . '\\validate_all_checkout_fields' );

C’est le hook woocommerce_checkout_process de WooCommerce qui nous permet d’effectuer toute logiques nécessaires pour considérer une demande de commande comme valide ou invalide.

Dans notre cas, on va :

  1. lignes 4 à 7 : s’assurer que le menu déroulant du niveau d’urgence possède bien une valeur autre que la consigne.
  2. lignes 9 à 11 : vérifier que le niveau d’urgence envoyé correspond bien à un niveau d’urgence valide parmi nos 4 connus. On interdit ainsi aux petits malins d’envoyer une valeur inconnue.

Dans son processus de validation, WooCommerce va s’intéresser aux notices d’erreur. Si aucune notice n’est présente, il considère les paramètres de la commande comme valides et pourra ainsi continuer sa moulinette (création de la commande, envoi des e-mails, redirection de l’utilisateur sur la page de remerciement, etc.).

On peut donc invalider la demande de commande en ajoutant une notice d’erreur avec wc_add_notice( $message, 'error' ). Le message s’affichera en haut de page pour que l’utilisateur corrige son ou ses erreur(s).

Modifier le prix du panier dynamiquement en fonction de ces champs

Comme décrit en intro, on souhaite appliquer un frais supplémentaire sur le panier selon le degré d’urgence choisi par l’acheteur. C’est le hook woocommerce_cart_calculate_fees qui nous permettra d’appliquer des frais dynamiquement sur son panier.

<?php function add_emergency_fee( $cart_object ) {
	if ( is_admin() && ! defined( 'DOING_AJAX' ) || ! is_checkout() ) {
		return;
	}

	// Only trigger this logic once.
	if ( did_action( 'woocommerce_cart_calculate_fees' ) >= 2 ) {
		return;
	}	

	$form_data = get_wc_posted_data();

	// Do not calculate anything if we do not have our emergency field checked or no emergency level is provided.
	if ( ! isset( $form_data['msk-urgent-order'], $form_data['msk-urgency-level'] ) || $form_data['msk-urgent-order'] !== 'on' ) {
		return;
	}

	// Store a mutiplier/coefficient to calculate the emergency fee.
	$multipliers = [
		'next_hour'    => 2,
		'next_6hours'  => 1.5,
		'next_12hours' => 1.25,
		'tomorrow'     => 1.1,
	];

	if ( ! array_key_exists( $form_data['msk-urgency-level'], $multipliers ) ) {
		return;
	}

	// Add the extra fee to the user cart.
	WC()->cart->add_fee(
		__( 'Emergency processing fee', 'wc_ccf' ),
		WC()->cart->subtotal * $multipliers[ $form_data['msk-urgency-level'] ],
		false
	);
}
add_action( 'woocommerce_cart_calculate_fees', __NAMESPACE__ . '\\add_emergency_fee' );

Après quelques vérifications préalables (lignes 2 à 16), on va calculer le prix du frais à appliquer. On utilise WC()->cart->add_fee() pour ajouter un frais libre sur le panier en indiquant son nom en premier paramètre et son prix en second.

Information

La requête de WooCommerce est faite en AJAX. On a donc accès aux valeurs du formulaire stockées dans la variable $_POST, plus précisément dans la clé _POST['post_data'] qu’il faudra désérialiser. On utilise alors une fonction maison get_wc_posted_data() pour accéder à ces valeurs plus rapidement.

Rendre nos nouveaux champs visibles aux yeux de WooCommerce

Lors d’un envoi de commande (valide) à WooCommerce, les champs de formulaire traités se limitent uniquement à ceux existants/connus par ce dernier. C’est une sécurité qui permet à WooCommerce d’ignorer totalement des valeurs de formulaires qui ne proviendraient pas de WooCommerce ou d’extensions installées sur le site.

On empêche ainsi aux utilisateurs malveillants d’éditer les données envoyées pour essayer d’injecter du code malicieux dans le processus de commande.

<?php function add_custom_checkout_data_to_order_data_array( $data ) {
	$custom_keys = [
		'msk-urgent-order',
		'msk-urgency-level',
	];

	foreach ( $custom_keys as $key ) {
		if ( isset( $_POST[ $key ] ) ) {
			$data[ $key ] = sanitize_text_field( $_POST[ $key ] );
		}
	}

	return $data;
}
add_filter( 'woocommerce_checkout_posted_data', __NAMESPACE__ . '\\add_custom_checkout_data_to_order_data_array', 10, 2 );

Heureusement pour nous, on peut profiter du filtre woocommerce_checkout_posted_data pour indiquer à WooCommerce que tel ou tel champ du formulaire envoyé peut être utilisé pour nos logiques sur-mesure. Dans notre cas, on ajoute deux clés => valeurs au tableau de données :

  1. la clé msk-urgent-order pour savoir si la checkbox d’urgence était cochée par l’acheteur
  2. la clé msk-urgency-level pour connaître le niveau d’urgence choisi par l’acheteur

Attention

Même si nos valeurs ont été validées en amont avant l’envoi final du formulaire, pensez bien à nettoyer les valeurs reçues avec les fonctions sanitize_...() de WordPress.

Ce tableau de données contient donc les valeurs du formulaire de checkout fraîchement envoyé. Il va nous servir dans l’étape suivante.

Enregistrer la valeur des champs sur chaque commande passée

WooCommerce a créé une nouvelle commande, il est temps d’enregistrer les valeurs de nos champs personnalisées !

<?php function save_custom_checkout_data_in_order_metadata( $order_id, $data ) {
	$custom_keys = [
		'msk-urgent-order',
		'msk-urgency-level',
	];

	$order = wc_get_order( $order_id );

	foreach ( $custom_keys as $key ) {
		if ( isset( $data[ $key ] ) ) {
			$order->add_meta_data( $key, $data[ $key ] );
		}
	}

	$order->save();
}
add_action( 'woocommerce_checkout_update_order_meta', __NAMESPACE__ . '\\save_custom_checkout_data_in_order_metadata', 10, 2 );

On profite du hook woocommerce_checkout_update_order_meta qui ne s’exécute qu’après la création d’une commande par WooCommerce dans le processus d’achat front-end.

Notre fonction hookée recevra l’identifiant de la commande créée mais aussi le tableau $data du formulaire de commande. C’est dans ce tableau qu’on vient d’enregistrer nos données vitales relatives à l’urgence de la commande.

Dans notre cas, nous sauvegardons ces deux données custom dans les métadonnées de la commande pour garder en mémoire le choix de l’utilisateur.

Nos 2 métadonnées visibles dans le back-office de la commande créée

C’est à ce niveau que vous allez pouvoir vraiment exécuter des actions sur-mesure pour un site e-commerce personnalisé. Envoi d’un e-mail, délégation d’un process à une API, classification de la commande… à vous de traiter les différents cas avec les valeurs des champs que l’on vient d’ajouter au formulaire de checkout.

Conclusion

En quelques étapes, nous aurons réussi à ajouter des champs personnalisés sur le formulaire de commande WooCommerce. Ce tunnel d’achat sur-mesure de notre boutique e-commerce ravira sans doute vos acheteurs les plus pressés 🍊 !

Partagez-nous en commentaire l’usage que vous avez fait de ce tutoriel 🙂


Vous avez aimé cet article ?

Partagez-le sur vos réseaux sociaux en guise de remerciement :)


5 commentaires

Merci pour ce tuto super sympa.
J’ai juste une question.
Je veux faire moi même une modif afin d’afficher une date et un lieu de dépôt des produits.
Dans quel fichier fichier doit-on ajouter le code PHP ?
Merci

J’ai placé le code avec phpeverywhere sur la page ‘commander’, donc sur le formulaire de commande. Ca ne marche pas :/ Help ?

Laisser un commentaire