Image mise en avant pour l'article

Les Checkout Panes Custom dans Drupal Commerce

28 septembre 2022
E-commerce - Drupal
Drupal Commerce est livré avec une solution « clé en main » pour créer son propre tunnel d’achat.


Il est possible de le personnaliser à souhait et même d'y ajouter des formulaires personnalisés appelés « checkout panes ». Dans cet article, je vous explique comment créer des Checkouts Panes Custom et comment les configurer, de la version simple à la version la plus complexe.

 

Présentation

La configuration du tunnel d’achat se situe ici : admin/commerce/config/checkout-flows.

Sur la page d’édition du workflow, nous pouvons choisir quand apparait tel ou tel formulaire, si l’utilisateur doit se connecter avant l’achat ou même créer un compte par exemple.

Ce que nous organisons ici via drag & drop, c’est ce que Drupal Commerce appelle les « checkout panes ». Ceux livrés de base avec le module permettent de mettre en place un tunnel d’achat simple. Nous y trouverons :

  • Les sous-formulaires liés aux informations de contact,
  • Les sous-formulaires de paiement,
  • Les items du panier,
  • Ou encore les promotions applicables à la commande.

Checkout panes custom, kesako ?

 

Checkout panes, kesako ?

Pour comprendre ce que sont les « checkout panes », il faut bien comprendre comment est construit le tunnel d’achat de Drupal Commerce.

C’est tout simplement un formulaire multi étapes (multistep form) qui va ajouter des informations à l’entité commande (order) jusqu’au paiement et sa validation finale.

Des modules contrib existent pour customiser ce tunnel d’achat, comme par exemple Commerce Shipping qui va permettre d’ajouter un formulaire pour informer une adresse de livraison différente de celle de facturation ou ajouter toutes les fonctions découlant de son utilisation.

Parfois, nous avons aussi besoin d’une utilisation très spécifique et Drupal Commerce permet de personnaliser soi-même son Checkout Pane Custom de façon relativement simple.

 

 

Usage custom

Dans notre cas, nous avons eu besoin de deux solutions custom. Il est possible qu’au moins une des deux ait été disponible en module contrib. Mais il était plus rapide de les développer soi-même, l’implémentation de ces formulaires étant relativement simple.

Notre besoin le plus simple était d’ajouter un formulaire avec une checkbox (case à cocher) pour valider les CGV (Conditions Générales de Vente). Cet exemple est décrit dans le « cas simple ci-dessous ».

Notre besoin, un peu plus complexe, était d’ajouter un formulaire de type « bénéficiaire » au formulaire de commande principal. Ce formulaire permettrait d’ajouter des informations de facturation pour offrir l’objet ou la prestation à un tiers. Il fallait donc ajouter des champs pour les nom, prénom, adresse, email, etc. Pour cette situation, nous allons utiliser les capacités de Drupal pour gérer la création de formulaire et la gestion des données s’y trouvant. Cet exemple est décrit dans le « cas complexe ci-dessous ».

 

 

Notions générales

Dans le module, les checkout panes custom doivent être créées comme n'importe quel autre type de plugin.

Checkout pane module architecture

Chaque classe correspond à un checkout pane custom étend la classe CheckoutPaneBase. Afin de l’ajouter au « checkout flow » et de pouvoir administrer son affichage dans les étapes de commande, j’ajoute des annotations à la classe, à la manière d’un block Drupal.

 

Cas simple (CGV)

Ce formulaire n’est qu’une simple case à cocher (checkbox) pour valider la commande et passer à l’étape de paiement. On peut donc créer le formulaire complètement dans le code en utilisant la Form API.


/**
 * Ajoute formulaire confirmant les CGV avant achat
 *
 * @CommerceCheckoutPane(
 *   id = "cgv_custom_pane",
 *   label = @Translation("Conditions générales de vente"),
 *   default_step = "review",
 *   wrapper_element = "fieldset",
 * )
 */
class CGVPaneForm extends CheckoutPaneBase {

  public function __construct(array $configuration, $plugin_id, $plugin_definition, CheckoutFlowInterface $checkout_flow, EntityTypeManagerInterface $entity_type_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $checkout_flow, $entity_type_manager);
  }

  public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form): array {
    $pane_form['field_cgv'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Je déclare avoir lu et accepté les ' . Link::fromTextAndUrl($this->t('conditions générales de vente'), $this->getCGVModalLink())
          ->toString() . '.'),
      '#required' => TRUE,
    ];
    $pane_form['#attached'] = [
      'library' => [
        'core/drupal.dialog.ajax',
        'digiteurs_ecommerce/cgv_form',
      ],
    ];

    return $pane_form;
  }

  public function getCGVModalLink(): Url {
    $link_url = Url::fromRoute('digiteurs_ecommerce.content_modal');
    $link_url->setOptions([
      'attributes' => [
        'class' => ['use-ajax', 'button', 'button--small'],
        'data-dialog-type' => 'modal',
        'data-dialog-options' => Json::encode(['width' => 600]),
      ],
    ]);
    return $link_url;
  }
}

Aperçu du formulaire CGV ajouté au processus de commande :

Checkout pane : case à cocher dans les conditions générales de vente

 

Cas plus complexe (formulaire bénéficiaire)

Ici, j’utilise le BO Drupal pour créer le formulaire afin qu’il soit modifiable par le webmestre par exemple (s’il souhaite ajouter ou retirer des champs). Nous le récupérons ensuite pour l’afficher dans le tunnel d’achat. Ce formulaire peut être configuré ici : admin/commerce/config/order-types/default/edit/form-display (après avoir ajouté le mode d’affichage adéquat dans les réglages de mode d’affichage).

La fonction buildSummary() permet d’afficher un résumé des données entrées à l’étape suivante.

NB : il est également possible d’ajouter une méthode isVisible() qui permettra d’afficher ou non le formulaire en fonction d’une condition.


/**
 * Provides a custom form to add beneficiary to order.
 *
 * @CommerceCheckoutPane(
 *   id = "beneficiary_custom_pane",
 *   label = @Translation("Formulaire bénéficiaire"),
 *   default_step = "order_information",
 *   wrapper_element = "fieldset",
 * )
 */
class BeneficiairePaneForm extends CheckoutPaneBase {

  const FORM_MODE_BENEFICIAIRE = 'beneficiaire_formation';

  const FIELD_BENEFICIAIRE = 'field_beneficiaire_formation';

  /**
   * @param array $configuration
   * @param $plugin_id
   * @param $plugin_definition
   * @param \Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowInterface $checkout_flow
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, CheckoutFlowInterface $checkout_flow, EntityTypeManagerInterface $entity_type_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $checkout_flow, $entity_type_manager);
  }

  /**
   * Création du formulaire custom pour ajouter un bénéficiaire à une formation
   * @param array $pane_form
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   * @param array $complete_form
   *
   * @return array
   */
  public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form): array {
    // On récupère le formulaire de commande avec son mode d'affichage custom créé dans le BO
    $formDisplay = EntityFormDisplay::collectRenderDisplay($this->order, self::FORM_MODE_BENEFICIAIRE);
    $formDisplay->buildForm($this->order, $pane_form, $form_state);
    return $pane_form;
  }

  /**
   * @param array $pane_form
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   * @param array $complete_form
   */
  public function validatePaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
    $form_display = EntityFormDisplay::collectRenderDisplay($this->order, self::FORM_MODE_BENEFICIAIRE);
    $form_display->extractFormValues($this->order, $pane_form, $form_state);
    $form_display->validateFormValues($this->order, $pane_form, $form_state);
  }

  /**
   * @param array $pane_form
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   * @param array $complete_form
   */
  public function submitPaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
    $form_display = EntityFormDisplay::collectRenderDisplay($this->order, self::FORM_MODE_BENEFICIAIRE);
    $form_display->extractFormValues($this->order, $pane_form, $form_state);
  }

  /**
   * Création du résumé des valeurs entrées dans le formulaire, affiché sur la page de récapitulatif
   * @return array
   */
  public function buildPaneSummary(): array {
    if ($this->order->get(self::FIELD_BENEFICIAIRE)->value) {
      $view_builder = $this->entityTypeManager->getViewBuilder('commerce_order');
      return [
        '#title' => $this->t('Participant(e) à la formation'),
        'beneficiaire' => $view_builder->view($this->order, self::FORM_MODE_BENEFICIAIRE),
      ];
    }
    return [];
  }
}

Aperçu du formulaire de bénéficiaire ajouté au processus de commande :

checkout pane : formulaire de bénéficiaire

 

Theming

Le formulaire sera appelé dans le template principal grâce à son id de cette manière : et peut être personnalisé via un template custom.

Pour personnaliser le CSS de manière « propre », j’ajoute également une librairie custom à la configuration du thème, dans le fichier my_theme.info.yml :


libraries-override:
  commerce_cart/cart_block:
    css:
      layout:
        css/commerce_cart.layout.css: css/commerce_cart.layout.css

 

J’espère que cet article vous a été utile. Je vous conseille de lire également les articles suivants :

 


Crédit photo : SARINYAPINNGAM

Image mise en avant pour l'article
Jérôme Guyon
Développeur Drupal
De l'urgence de migrer sur Drupal 9 : conseils et bonnes pratiques
Voir le webinar !
Vous connaissez la technologie Drupal et vous souhaitez participer à une aventure humaine ?
Adimeo recrute un Développeur Drupal pour réaliser des développements back pour des clients ...
Voir notre offre d'emploi !