January 23, 2017

Modifying product prices in Drupal Commerce 2 for Drupal 8

Written by Sam Oltz @pianomansam

Share on LinkedIn

It's not uncommon for an e-commerce site to offer different prices for the same product based on certain conditions. One way to achieve this is through a coupon. Coupons are a great way to handle this, but they require the user to know about and apply them.

Another method is to automatically apply a price difference. Perhaps the product should receive a discount if a certain volume is purchased. Or perhaps VIP users should get a blanket percentage discount. Automatic price differences have the benefit of not requiring user intervention and can been seen even before the product is added to the cart.

Commerce 1 on Drupal 7 used Rules to accomplish automatic price differences. Commerce integrated with Rules by providing an event called "Calculating the sell price of a product". Unfortunately, this integration doesn't exist (yet?) in Commerce 2 on Drupal 8. But fortunately there is a way to do this with some custom code. I couldn't find much documentation out there so I'm documenting the process here today.

I give credit to mglaman/commerce_demo for providing example code to follow. It provides several different demonstrations, though, so I'd like to provide a very basic example that just demonstrates price resolvers.

I'll assume we'll create a custom module just to do price modifications. We'll start out by applying a 20% discount to all products. The adjusted price will show up on products in the shopping cart, but will require changing the price field display to "Calculated Price" for it to be reflected on product view.

We need three files: an info yaml file, a service yaml file, and price resolver class file.

custom_price.info.yml

name: Custom Price
type: module
description: Custom module to discount all product prices by 20%
core: 8.x
package: Custom
dependencies:
  - commerce_price

custom_price.services.yml

services:
  custom_price.custom_price_resolver:
    class: Drupal\custom_price\Resolvers\CustomPriceResolver
    arguments: ['@request_stack']
    tags:
      - { name: commerce_price.price_resolver, priority: 600 }

src/Resolvers/CustomPriceResolver.php

<?php
namespace Drupal\custom_price\Resolvers;

use Drupal\commerce\Context;
use Drupal\commerce\PurchasableEntityInterface;
use Drupal\commerce_price\Resolver\PriceResolverInterface;

/**
 * Returns a blanket 20% price.
 */
class CustomPriceResolver implements PriceResolverInterface {
  /**
   * {@inheritdoc}
   */
  public function resolve(PurchasableEntityInterface $entity, $quantity, Context $context) {
    $entity->getPrice()->multiply('0.80');
  }
}

Complex Examples

From here we can introduce additional conditions. For example, we can require the user have a certain role. Here's an example of requiring the "vip" role.

src/Resolvers/CustomPriceResolver.php

<?php
namespace Drupal\custom_price\Resolvers;

use Drupal\commerce\Context;
use Drupal\commerce\PurchasableEntityInterface;
use Drupal\commerce_price\Resolver\PriceResolverInterface;
use \Drupal\user\Entity\User;

/**
 * Returns the price based on the current user being a VIP user.
 */
class CustomPriceResolver implements PriceResolverInterface {
  /**
   * {@inheritdoc}
   */
  public function resolve(PurchasableEntityInterface $entity, $quantity, Context $context) {
    $current_user_id = \Drupal::currentUser()->id();
    $account = User::load($current_user_id);

    if ($account->hasRole('vip')) {
      return $entity->getPrice()->multiply('0.80');
    }

    return $entity->getPrice();
  }
}

It's also possible use a second commerce price field to hold another price value. Here's an updated example that gives user with the "vip" role the price stored in the VIP Price field.

<?php
namespace Drupal\custom_price\Resolvers;

use Drupal\commerce\Context;
use Drupal\commerce\PurchasableEntityInterface;
use Drupal\commerce_price\Resolver\PriceResolverInterface;
use Drupal\commerce_price\Price;
use \Drupal\user\Entity\User;

/**
 * Returns the price based on the current user being a VIP user.
 */
class CustomPriceResolver implements PriceResolverInterface {
  /**
   * {@inheritdoc}
   */
  public function resolve(PurchasableEntityInterface $entity, $quantity, Context $context) {
    $current_user_id = \Drupal::currentUser()->id();
    $account = User::load($current_user_id);

    if ($account->hasRole('vip')) {
      $vip_price = $entity->get('field_vip_price');
      return new Price($vip_price->number, $vip_price->currency_code);
    }

    return $entity->getPrice();
  }
}

We'd love to chat about your next web or application project!