Skip to main content
Home Home

Join

  • Get in touch

Main navigation

  • Blog
  • Team

Creating a custom checkout pane for Drupal Commerce in Drupal 8

In this article, I'll show how you can create a custom checkout pane for Drupal Commerce in Drupal 8. For this purpose, we'll create a checkout pane with a configuration form and ability for users to add coupons to their order.

17 May 2017
Dalibor
Senior Drupal Architect, Team Lead
Tags
Drupal
Drupal 8
Drupal modules
Planet Drupal
Share

In this article, I'll show how you can create a custom checkout pane for Drupal Commerce in Drupal 8. For this purpose, we'll create a checkout pane with a configuration form and the ability for users to add coupons to their order.

Creating a custom checkout pane

Checkout pane needs to be created with the correct annotation and in the correct namespace. We'll create CommerceCoupons class in module_name/src/Plugin/Commerce/CheckoutPane directory with annotation like in code below.


namespace Drupal\module_name\Plugin\Commerce\CheckoutPane;

use Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowInterface;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneBase;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;

/**
 * Provides the coupons pane.
 *
 * @CommerceCheckoutPane(
 *   id = "coupons",
 *   label = @Translation("Redeem Coupon"),
 *   default_step = "order_information",
 * )
 */
class CommerceCoupons extends CheckoutPaneBase implements CheckoutPaneInterface {


/**
 * {@inheritdoc}
 */
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);
}

This means that our custom checkout pane will have an id 'coupons' and will be labelled as Redeem Coupon and will be set on order information step by default.

First, we'll create the settings (configuration) form for our pane. For this purpose, we'll have settings if we, for example, want to set up that user has the ability to redeem only one coupon on order. For this, we'll have to implement four functions.

First, we'll setup our default configuration.


/**
 * {@inheritdoc}
 */
public function defaultConfiguration() {
  return [
      'single_coupon' => FALSE,
    ] + parent::defaultConfiguration();
}

Then we'll implement summary for our configuration form.


/**
 * {@inheritdoc}
 */
public function buildConfigurationSummary() {
  $summary = !empty($this->configuration['single_coupon']) ? $this->t('Single Coupon Usage on Order: Yes') : $this->t('Single Coupon Usage on Order: No');
  return $summary;
}

Next, we'll build our configuration form.


/**
 * {@inheritdoc}
 */
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
  $form = parent::buildConfigurationForm($form, $form_state);
  $form['single_coupon'] = [
    '#type' => 'checkbox',
    '#title' => $this->t('Single Coupon Usage on Order?'),
    '#description' => $this->t('User can enter only one coupon on order.'),
    '#default_value' => $this->configuration['single_coupon'],
  ];

  return $form;
}

And save these settings on submit.


/**
 * {@inheritdoc}
 */
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
  parent::submitConfigurationForm($form, $form_state);

  if (!$form_state->getErrors()) {
    $values = $form_state->getValue($form['#parents']);
    $this->configuration['single_coupon'] = !empty($values['single_coupon']);
  }
}

You can see how this will look like on Checkout flow edit page.

Checkout flow

Next, we'll build our pane form. We'll check our configuration, and if the order has a coupon applied on it and has config setup for the single coupon, won't show form.


/**
 * {@inheritdoc}
 */
public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form) {
  $order_has_coupons = $this->order->coupons->referencedEntities();
  if($this->configuration['single_coupon'] && $order_has_coupons){
    return $pane_form;
  }
  $pane_form['coupon'] = [
    '#type' => 'textfield',
    '#title' => $this->t('Coupon'),
    '#default_value' => '',
    '#required' => FALSE,
  ];
  return $pane_form;
}

After that, we'll implement validation for our coupons and check if a valid coupon is provided and if it can be applied to this order.


/**
 * {@inheritdoc}
 */
public function validatePaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
  $values = $form_state->getValue($pane_form['#parents']);
  if(!empty($values['coupon'])){
    $coupon_code = $values['coupon'];
    /* @var \Drupal\commerce_promotion\Entity\Coupon $commerce_coupon */
    $commerce_coupon = $this->entityTypeManager->getStorage('commerce_promotion_coupon')->loadByCode($coupon_code);
    $valid = false;
    if($commerce_coupon){
      $promotion = $commerce_coupon->getPromotion();
      $valid = $promotion->applies($this->order) ? true : false;
      if($valid){
        foreach($this->order->coupons->referencedEntities() as $coupon){
          if($commerce_coupon->id() == $coupon->id()){
            $form_state->setError($pane_form, $this->t('Coupon already applied to order.'));
          }
        }
      }
    }
    if(!$valid){
      $form_state->setError($pane_form, $this->t('The specified coupon does not apply for this order.'));
    }
  }
}

What is left for us is to save coupon on order and process coupon. For this, we'll use commerce promotion processor.


/**
 * {@inheritdoc}
 */
public function submitPaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
  $values = $form_state->getValue($pane_form['#parents']);
  if(!empty($values['coupon'])){
    $coupon_code = $values['coupon'];
    $commerce_coupon = $this->entityTypeManager->getStorage('commerce_promotion_coupon')->loadByCode($coupon_code);
    if($commerce_coupon){
      $this->order->coupons[] = $commerce_coupon->id();
      $coupon_order_processor = \Drupal::service('commerce_promotion.promotion_order_processor');
      $coupon_order_processor->process($this->order);
      $this->order->save();
    }
  }
}

We're done and we can test our new checkout pane.

Comments

Murz (not verified) 1 month 1 week ago

Thanks for the article! How must this file be named in directory - any name or predefined format? And will be good to include full file to download.

  • Reply

Leave a reply

About text formats

LOOKING FOR A DIGITAL EXPERIENCE?

Looking for a digital experience built on Drupal?
Get in touch with Drupal experts!

Get in touch

 

hello@ws.agency

+385 1 4646 482

Slavonska avenija 3
10000 Zagreb
Croatia
European Union EU flag

EU Vat ID: HR 97669668809

© 2019 Websolutions Agency. All Rights Reserved.

Home

Footer menu

  • Privacy
  • Sitemap
  • Blog
  • Careers
  • Get in touch