Aller au contenu principal

Pop up

Drupal commerce : personnaliser le processus de commande (Checkout)

Le processus du checkout de Drupal commerce est compatible dans la plus part des e-commerce commerces. Nous avons besoin pour un de nous client de mettre un processus de checkout assez différent de celui proposer par défaut. 

Parmi les fonctionnalités : 

  • Proposer la possibilité de s'inscrire pour les l'utilisateur non inscrit.
  • Ajouter des formulaires specifique lors du processus du checkout.
  • Modifier la valeur des boutons de sousmissions
Le checkout s'articule autour de plusieurs etapes.
Par defaut on distinque  5 etapes :
  • Identifiant (login)
  • Informations sur la commande (order_information)
  • Vérifier (review)
  • Paiement (payment)
  • Terminé (complete)
Voir : commerce/modules/checkout/src/Plugin/Commerce/CheckoutFlow/MultistepDefault.php
 

Chaque etape est constitué d'un ou plusieurs panneaux (panes). Par example les informations de contact est un pane.

<?php

namespace Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane;

use Drupal\Core\Form\FormStateInterface;

/**
 * Provides the contact information pane.
 *
 * @CommerceCheckoutPane(
 *   id = "contact_information",
 *   label = @Translation("Contact information"),
 *   default_step = "order_information",
 *   wrapper_element = "fieldset",
 * )
 */
class ContactInformation extends CheckoutPaneBase implements CheckoutPaneInterface {
...

Nous allons creer un pane afin d'y ajouter le code necessaire. Le code de base de notre pane ressemble à ceci :

<?php

namespace Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane;

use Drupal\Core\Form\FormStateInterface;

/**
 * Provides the contact information pane.
 *
 * @CommerceCheckoutPane(
 *   id = "custom_information",
 *   label = @Translation("Custom information"),
 *   default_step = "order_information",
 *   wrapper_element = "fieldset",
 * )
 */
class CustomInformation extends CheckoutPaneBase implements CheckoutPaneInterface {  
  /**
   *
   * {@inheritdoc}
   * @see \Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneInterface::buildPaneForm()
   */
  public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array $complete_form) {
    return $pane_form;
  }
}

La methode buildPaneForm contiendra le formulaire supplementaire.
Par deaut, elle serra disponible à l'etape "order_information".
 

Ajout d'un champs dans Checkout Pane

Nous pouvons ameliorer notre pane en ajoutant un champs texte area :

  /**
   *
   * {@inheritdoc}
   * @see \Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneInterface::buildPaneForm()
   */
  public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form) {
    $pane_form['description_need'] = [
      "#type" => 'text_format',
      '#format' => 'basic_html',
      '#title' => $this->t('Describe your need'),
      '#default_value' => '',
      '#required' => true,
      '#description' => $this->t("Please bring out the necessary characteristics and information.")
    ];
    return $pane_form;
  }
  
 /**
   *
   * {@inheritdoc}
   */
  public function validatePaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
    $values = $form_state->getValue($pane_form['#parents']);
    if (!empty($values['description_need']['value']) && \strlen($values['description_need']['value']) < 10) {
      $form_state->setError($pane_form, $this->t('Your description is short'));
    }
  }
  
  /**
   *
   * {@inheritdoc}
   */
  public function submitPaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
    $values = $form_state->getValue($pane_form['#parents']);
    // $values['description_need']
  }
Nous avons ejouter un validateur(validatePaneForm) et une methode de sauvegarde (submitPaneForm).

à ce stade nous avons deja un rendu :



On souhaite sauvegarder ses informations dans la commande, nous allons ajouter un champs à notre commande via le fichier module et le hook_entity_base_field_info.
 
/**
 * Implement hook_entity_base_field_info
 *
 * @param EntityTypeInterface $entity_type
 */
function [MODULE_NAME]_entity_base_field_info(EntityTypeInterface $entity_type) {
  $fields = [];
  if ($entity_type->id() == 'commerce_order') {
    $fields['hbk_description_need'] = BaseFieldDefinition::create('text_long')->setLabel(" Description need ")->setDisplayOptions('form', [])->setDisplayOptions('view',
      [
        'label' => 'hidden',
        'type' => 'text_default',
        'weight' => 0
      ])->setDisplayConfigurable('view', TRUE)->setDisplayConfigurable('form', true);
  }
  return $fields;
}
Ensuite il faut appliquer ces modifications au niveau de la base de données, on peut le faire de differente maniere, mais nous optons pour une commande drush entup :
vendor/bin/drush entup
Nous mettons à jour notre pane :
<?php

namespace Drupal\hbk_souscription_pfna\Plugin\Commerce\CheckoutPane;

use Drupal\Core\Form\FormStateInterface;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneBase;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneInterface;

/**
 * Provides the contact information pane.
 *
 * @CommerceCheckoutPane(
 *   id = "custom_information",
 *   label = @Translation("Custom information"),
 *   default_step = "order_information",
 *   wrapper_element = "fieldset",
 * )
 */
class CustomInformation extends CheckoutPaneBase implements CheckoutPaneInterface {
  
  /**
   *
   * {@inheritdoc}
   * @see \Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneInterface::buildPaneForm()
   */
  public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form) {
    $pane_form['description_need'] = [
      "#type" => 'text_format',
      '#format' => 'basic_html',
      '#title' => $this->t('Describe your need'),
      '#default_value' => $this->order->get('hbk_description_need')->value,
      '#required' => true,
      '#description' => $this->t("Please bring out the necessary characteristics and information.")
    ];
    return $pane_form;
  }
  
  /**
   *
   * {@inheritdoc}
   */
  public function validatePaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
    $values = $form_state->getValue($pane_form['#parents']);
    if (!empty($values['description_need']['value']) && \strlen($values['description_need']['value']) < 10) {
      $form_state->setError($pane_form, $this->t('Your description is short'));
    }
  }
  
  /**
   *
   * {@inheritdoc}
   */
  public function submitPaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
    $values = $form_state->getValue($pane_form['#parents']);
    $this->order->set('hbk_description_need', $values['description_need']['value']);
  }
}
L'approche que nous venons de parcourir est adpater si vous avez peu de champs, mais dans la mesure ou vous avez un nombre important de champs vous devez utiliser un profil.
 

Ajout d'un profile dans le Checkout Pane

L'ajout d'un profile suit la meme logique que celui d'un champs, mais des differences importante suiviennent car on a deux formulaire à gerer : le formulaire par defaut ( checkout ) et le formulaire du profile.

Definition du besoin :
Nous souhaitons ajouter un formulaire supplementaire utilisant un profile. L'utilisateur le remplit une seule foix et par la suite, il peut mettre ses informations à jour. 
 
/**
 * Implement hook_entity_base_field_info
 *
 * @param EntityTypeInterface $entity_type
 */
function hbk_souscription_pfna_entity_base_field_info(EntityTypeInterface $entity_type) {
  $fields = [];
  if ($entity_type->id() == 'commerce_order') {
    $fields['hbk_description_need'] = BaseFieldDefinition::create('text_long')->setLabel(" Description need ")->setDisplayOptions('form', [])->setDisplayOptions('view',
      [
        'label' => 'hidden',
        'type' => 'text_default',
        'weight' => 0
      ])->setDisplayConfigurable('view', TRUE)->setDisplayConfigurable('form', true);
    $fields['hbk_custom_profile'] = BaseFieldDefinition::create('entity_reference')->setLabel(t('Custom profile'))->setSetting('target_type', 'profile')->setSetting('handler', 'default')->setDisplayOptions(
      'form', [
        'type' => 'entity_reference_autocomplete',
        'weight' => 5,
        'settings' => [
          'match_operator' => 'CONTAINS',
          'size' => '60',
          'autocomplete_type' => 'tags',
          'placeholder' => ''
        ]
      ])->setDisplayConfigurable('form', TRUE)->setDisplayConfigurable('view', TRUE)->setCardinality(1);
  }
  return $fields;
Nous ajoutons un nouveau champs "hbk_custom_profile" à l'entité commerce, ce champs contiendra l'id du profile.
 
<?php

namespace Drupal\hbk_souscription_pfna\Plugin\Commerce\InlineForm;

use Drupal\commerce\Plugin\Commerce\InlineForm\EntityInlineFormBase;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\profile\Entity\ProfileInterface;

/**
 * Provides an inline form for managing a customer profile.
 *
 * Allows copying values to and from the customer's address book.
 *
 * Supports two modes, based on the profile type setting:
 * - Single: The customer can have only a single profile of this type.
 * - Multiple: The customer can have multiple profiles of this type.
 *
 * @CommerceInlineForm(
 *   id = "hbk_custom_profile",
 *   label = @Translation("Hbk custom profile"),
 * )
 */
class HbkCustomerProfile extends EntityInlineFormBase {
  
  /**
   *
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [
      'form_mode' => 'default',
      'skip_save' => FALSE
    ];
  }
  
  /**
   *
   * {@inheritdoc}
   */
  public function buildInlineForm(array $inline_form, FormStateInterface $form_state) {
    $inline_form = parent::buildInlineForm($inline_form, $form_state);
    assert($this->entity instanceof ProfileInterface);
    
    $form_display = EntityFormDisplay::collectRenderDisplay($this->entity, $this->configuration['form_mode']);
    $form_display->buildForm($this->entity, $inline_form, $form_state);
    
    return $inline_form;
  }
  
  /**
   *
   * {@inheritdoc}
   */
  public function submitInlineForm(array &$inline_form, FormStateInterface $form_state) {
    parent::submitInlineForm($inline_form, $form_state);
    assert($this->entity instanceof ProfileInterface);
    
    $form_display = EntityFormDisplay::collectRenderDisplay($this->entity, $this->configuration['form_mode']);
    $form_display->extractFormValues($this->entity, $inline_form, $form_state);
    
    if (empty($this->configuration['skip_save'])) {
      $this->entity->save();
    }
  }
}
Nous creons le plugin CommerceInlineForm afin de faire le rendu du profile dans le CheckoutPane.
 
$customProfile = null;
    // On recupere le custom profile, s'il existe.
    $id_profile = $this->order->get("hbk_custom_profile")->target_id;
    if ($id_profile) {
      $this->messenger()->addStatus("Le profile est deja rataché à la commande ");
      $customProfile = $this->entityTypeManager->getStorage("profile")->load($id_profile);
    }
    if (!$customProfile) {
      
      $profile_storage = $this->entityTypeManager->getStorage("profile");
      // On verifie si profile existe deja.
      $customProfiles = $this->entityTypeManager->getStorage("profile")->loadByProperties([
        'uid' => \Drupal::currentUser()->id(),
        'type' => 'informations_personnelles'
      ]);
      if ($customProfiles) {
        $this->messenger()->addStatus("Le profile a deja été creer ");
        $customProfile = reset($customProfiles);
      }
      else {
        $this->messenger()->addStatus(" Creation d'un nouveau profile ");
        $customProfile = $profile_storage->create([
          'type' => 'informations_personnelles',
          'uid' => \Drupal::currentUser()->id()
        ]);
      }
    }
    /**
     *
     * @var \Drupal\hbk_souscription_pfna\Plugin\Commerce\InlineForm\HbkCustomerProfile $inline_form
     */
    $inline_form = $this->inlineFormManager->createInstance('hbk_custom_profile', [
      'form_mode' => 'default',
      'skip_save' => FALSE
    ], $customProfile);
    
    $pane_form['custom_profile'] = [
      '#parents' => array_merge($pane_form['#parents'], [
        'profile'
      ]),
      '#inline_form' => $inline_form
    ];
    $pane_form['custom_profile'] = $inline_form->buildInlineForm($pane_form['custom_profile'], $form_state);
    
Nous ajoutons une entrée dans le formulaire afin de faire le rendu du profil.
 
  /**
   *
   * {@inheritdoc}
   */
  public function submitPaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
    /** @var \Drupal\hbk_souscription_pfna\Plugin\Commerce\InlineForm\HbkCustomerProfile $inline_form */
    $inline_form = $pane_form['custom_profile']['#inline_form'];
    // on met à jour l'entité profile.
    $inline_form->submitInlineForm($pane_form['custom_profile'], $form_state);
    /**
     *
     * @var \Drupal\profile\Entity\Profile $profile
     */
    $profile = $inline_form->getEntity();
    //
    $values = $form_state->getValue($pane_form['#parents']);
    $this->order->set('hbk_description_need', $values['description_need']['value']);
    $this->order->set('hbk_custom_profile', $profile->id());
  }
Nous mettons à jour la sauvegarde.

Le code final  pour notre CheckoutPane :
<?php

namespace Drupal\hbk_souscription_pfna\Plugin\Commerce\CheckoutPane;

use Drupal\Core\Form\FormStateInterface;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneBase;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\commerce\InlineFormManager;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;

/**
 * Provides the contact information pane.
 *
 * @CommerceCheckoutPane(
 *   id = "custom_information",
 *   label = @Translation("Custom information"),
 *   default_step = "order_information",
 *   wrapper_element = "fieldset",
 * )
 */
class CustomInformation extends CheckoutPaneBase implements CheckoutPaneInterface {
  
  /**
   * The inline form manager.
   *
   * @var \Drupal\commerce\InlineFormManager
   */
  protected $inlineFormManager;
  
  /**
   * Constructs a new BillingInformation object.
   *
   * @param array $configuration
   *        A configuration array containing information about the plugin
   *        instance.
   * @param string $plugin_id
   *        The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *        The plugin implementation definition.
   * @param \Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowInterface $checkout_flow
   *        The parent checkout flow.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *        The entity type manager.
   * @param \Drupal\commerce\InlineFormManager $inline_form_manager
   *        The inline form manager.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, CheckoutFlowInterface $checkout_flow, EntityTypeManagerInterface $entity_type_manager, InlineFormManager $inline_form_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $checkout_flow, $entity_type_manager);
    
    $this->inlineFormManager = $inline_form_manager;
  }
  
  /**
   *
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, CheckoutFlowInterface $checkout_flow = NULL) {
    return new static($configuration, $plugin_id, $plugin_definition, $checkout_flow, $container->get('entity_type.manager'), $container->get('plugin.manager.commerce_inline_form'));
  }
  
  /**
   *
   * {@inheritdoc}
   * @see \Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneInterface::buildPaneForm()
   */
  public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form) {
    $pane_form['description_need'] = [
      "#type" => 'text_format',
      '#format' => 'basic_html',
      '#title' => $this->t('Describe your need'),
      '#default_value' => $this->order->get('hbk_description_need')->value,
      '#required' => true,
      '#description' => $this->t("Please bring out the necessary characteristics and information.")
    ];
    $customProfile = null;
    // On recupere le custom profile, s'il existe.
    $id_profile = $this->order->get("hbk_custom_profile")->target_id;
    if ($id_profile) {
      $customProfile = $this->entityTypeManager->getStorage("profile")->load($id_profile);
    }
    if (!$customProfile) {
      
      $profile_storage = $this->entityTypeManager->getStorage("profile");
      // On verifie si profile existe deja.
      $customProfiles = $this->entityTypeManager->getStorage("profile")->loadByProperties([
        'uid' => \Drupal::currentUser()->id(),
        'type' => 'informations_personnelles'
      ]);
      if ($customProfiles) {
        $customProfile = reset($customProfiles);
      }
      else {
        $customProfile = $profile_storage->create([
          'type' => 'informations_personnelles',
          'uid' => \Drupal::currentUser()->id()
        ]);
      }
    }
    /**
     *
     * @var \Drupal\hbk_souscription_pfna\Plugin\Commerce\InlineForm\HbkCustomerProfile $inline_form
     */
    $inline_form = $this->inlineFormManager->createInstance('hbk_custom_profile', [
      'form_mode' => 'default',
      'skip_save' => FALSE
    ], $customProfile);
    
    $pane_form['custom_profile'] = [
      '#parents' => array_merge($pane_form['#parents'], [
        'profile'
      ]),
      '#inline_form' => $inline_form
    ];
    $pane_form['custom_profile'] = $inline_form->buildInlineForm($pane_form['custom_profile'], $form_state);
    
    return $pane_form;
  }
  
  /**
   *
   * {@inheritdoc}
   */
  public function validatePaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
    $values = $form_state->getValue($pane_form['#parents']);
    if (!empty($values['description_need']['value']) && \strlen($values['description_need']['value']) < 10) {
      $form_state->setError($pane_form, $this->t('Your description is short'));
    }
  }
  
  /**
   *
   * {@inheritdoc}
   */
  public function submitPaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
    /** @var \Drupal\hbk_souscription_pfna\Plugin\Commerce\InlineForm\HbkCustomerProfile $inline_form */
    $inline_form = $pane_form['custom_profile']['#inline_form'];
    // on met à jour l'entité profile.
    $inline_form->submitInlineForm($pane_form['custom_profile'], $form_state);
    /**
     *
     * @var \Drupal\profile\Entity\Profile $profile
     */
    $profile = $inline_form->getEntity();
    //
    $values = $form_state->getValue($pane_form['#parents']);
    $this->order->set('hbk_description_need', $values['description_need']['value']);
    $this->order->set('hbk_custom_profile', $profile->id());
  }
}
Ailleurs sur le web :
https://www.adimeo.com/blog-technique/checkout-panes-custom-drupal-commerce
Stephane K

Écrit le

Il y'a 6 months
Modifié
Il y'a 5 months
Loading ...