Drupal Commerce 2.x - Rendre un prix dynamique ou calculé via un service tiers

Soumis par GoZ le ven 01/12/2017 - 12:55

Drupal Commerce 2.x - Rendre un prix dynamique ou calculé via un service tiers

Dans le cas d'un commerce BtoB ou métier, les règles de prix sont souvent plus compliquées qu'un simple prix par produit. Dans ce cas, deux choix possibles:

  • Le métier est dans un outil (CRM ou autre) externe.
  • Le métier et donc la règle de calcul doit être gérée par le site.

Dans les 2 cas, Drupal Commerce 2.x offre les mécanismes pour facilement intégrer ces règles métier pour définir le prix unitaire de notre produit.

Exemple: sur les sites de vente de billet d'avion, pour un même billet, les prix fluctuent suivant des règles de calcul (obscures) au cours du temps.

Pour mettre en place cela, nous allons pouvoir définir notre propre Price resolver.

Pour l'exemple, nous appellerons le module btob et nous appellerons un service qui permettra de faire appel à Salesforce pour obtenir le prix de notre produit. Le module Salesforce et son service sont purement fictifs et permettent d'imager simplement l'exemple.

Commençons par déclarer ce nouveau Price resolver en tant que service via un fichier dans notre module btob/btob.services.yml:

services:
  btob.salesforce_price_resolver:
    class: Drupal\btob\Resolver\SalesforcePriceResolver
    tags:
      - { name: commerce_price.price_resolver, priority: 10 }
    arguments: ['@salesforce.price_provider']

Comme nous pouvons le voir, le service s'appelle btob.salesforce_price_resolver et la class SalesforcePriceResolver se situe dans notre module dans le namespace Drupal\btob\Resolver.

La tag commerce_price.price_resolver permet de définir ce service comme price resolver. La priorité permet de définir dans quel ordre seront appelés les différents price resolver. Nous verrons ensuite un peu plus en détail comment se passe cet appel.

Dernier élément défini, l'argument @salesforce.price_provider permet de faire l'injection de dépendance de notre service salesforce qui nous fournira la méthode permettant de récupérer le prix de notre produit.

Notre service étant déclaré, il faut désormais créer la classe en question, soit le fichier

<?php

namespace Drupal\btob\Resolver;

use Drupal\commerce\Context;
use Drupal\commerce\PurchasableEntityInterface;
use Drupal\salesforce\SalesforcePriceProvider;

/**
 * Returns the price based on the salesforce price rule for the purchasable entity.
 */
class SalesforcePriceResolver implements PriceResolverInterface {

  /**
   * The Salesforce price provider service.
   *
   * @var \Drupal\salesforce\SalesforcePriceProvider
   */
  protected $salesforcePriceProvider;

  /**
   * {@inheritdoc}
   */
  public function __construct(SalesforcePriceProvider $salesforce_price_provider) {
    $this->salesforcePriceProvider = $salesforce_price_provider;
  }

  /**
   * {@inheritdoc}
   */
  public function resolve(PurchasableEntityInterface $entity, $quantity, Context $context) {
    return $this->salesforcePriceProvider->getPrice($entity, $quantity, $context);
  }

}

Nous retrouvons notre injection de dépendance de Salesforce Price Provider, mais le plus intéressant reste la méthode resolve(). C'est cette méthode qui va être appelée par le resolver et qui attend le prix unitaire du produit passé en paramètre.

Si vous souhaitez réaliser un test plus simple, retirez l'injection de dépendance propre à cet exemple, et retournez simplement dans votre resolver 99. Tous vos produits afficheront désormais en front le prix de 99 (euros, roupies, patates, la devise importe peu ici).

Ok, mais pourquoi ?

Commerce défini un service commerce_price.chain_price_resolver qui est appelé:

  • Lors d'un refresh de la commande.
  • Lors de l'affichage d'un prix formatté.

On voit dans sa définition qu'il récupère tous les services se taggant comme commerce_price.price_resolver.

services:
  commerce_price.chain_price_resolver:
    class: Drupal\commerce_price\Resolver\ChainPriceResolver
    tags:
      - { name: service_collector, call: addResolver, tag: commerce_price.price_resolver }

Ainsi, lorsque l'on souhaite obtenir un prix pour un produit, on peut directement faire appel à notre service en lui donnant les paramètre requis, et tous les resolver seront appelés jusqu'à obtenir le prix voulu.

$resolved_price = \Drupal\commerce_price\Resolver\ChainPriceResolver->resolve($purchasable_entity, $quantity, $context);

A noter que pour l'exemple, le code ci-dessus fait appel à notre service en static. Il est bien sur fortement recommandé de ne pas faire d'appel aux méthodes static mais d'utiliser l'injection de dépendance.