1

I want my checkout flow to look like this:

  1. Users enter their credit card information and press "Next."

  2. Users enter their address information (customer profile), then press "Next."

In the default Commerce workflow, users enter their credit card info and address on the same page.

I want to split credit card info and address info into different pages.

How can I do this safely?

Why do this?

Because if a user makes an error when typing their address and validation fails, it also resets the credit card information. I want to have the user do the credit card and address info separately so if validation fails, they only have to re-input one or the other.

4
+100

Creating a checkout flow plugin may solve your problem. Create a Plugin in your custom module like an example shown below:

namespace Drupal\my_checkout_flow\Plugin\Commerce\CheckoutFlow;

use Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowWithPanesBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * @CommerceCheckoutFlow(
 *  id = "custom_checkout_flow",
 *  label = @Translation("Custom checkout flow"),
 * )
 */
class CustomCheckoutFlow extends CheckoutFlowWithPanesBase {

  /**
   * {@inheritdoc}
   */
  public function getSteps() {
    return [
      'login' => [
        'label' => $this->t('Login'),
        'previous_label' => $this->t('Return to login'),
        'has_order_summary' => FALSE,
      ],
      'card_info' => [
        'label' => $this->t('Card information'),
        'previous_label' => $this->t('Return to Card information'),
        'next_label' => $this->t('Continue to Card information'),
        'has_order_summary' => FALSE,
      ],
      'address_information' => [
        'label' => $this->t('Address information'),
        'previous_label' => $this->t('Return to Address information'),
        'next_label' => $this->t('Continue to Address information'),
        'has_order_summary' => FALSE,
      ],
      'review' => [
        'label' => $this->t('Review'),
        'has_order_summary' => TRUE,
      ],
    ] + parent::getSteps();
  }

}

After creating a plugin, you need to change the checkout flow settings for your order type.

For more information visit: https://docs.drupalcommerce.org/commerce2/developer-guide/checkout/create-custom-checkout-flow


Update

CustomCheckoutFlow

<?php

namespace Drupal\my_checkout_flow\Plugin\Commerce\CheckoutFlow;

use Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowWithPanesBase;

/**
 * @CommerceCheckoutFlow(
 *  id = "custom_checkout_flow",
 *  label = @Translation("Custom checkout flow"),
 * )
 */
class CustomCheckoutFlow extends CheckoutFlowWithPanesBase {

  /**
   * {@inheritdoc}
   */
  public function getSteps() {
    return [
      'login' => [
        'label' => $this->t('Login'),
        'previous_label' => $this->t('Go back'),
        'has_sidebar' => FALSE,
      ],
      'card_information' => [
        'label' => $this->t('Card information'),
        'previous_label' => $this->t('Go back'),
        'has_sidebar' => TRUE,
      ],
      'order_information' => [
        'label' => $this->t('Order information'),
        'has_sidebar' => TRUE,
        'next_label' => $this->t('Continue to Order information'),
        'previous_label' => $this->t('Go back'),
      ],
      'review' => [
        'label' => $this->t('Review'),
        'next_label' => $this->t('Continue to review'),
        'previous_label' => $this->t('Go back'),
        'has_sidebar' => TRUE,
      ],
    ] + parent::getSteps();
  }

}

Then create CheckoutPanes as shown below:

BillingInformation

<?php

namespace Drupal\my_checkout_flow\Plugin\Commerce\CheckoutPane;

use \Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\BillingInformation;

/**
 * Provides the billing information pane.
 *
 * @CommerceCheckoutPane(
 *   id = "custom_billing_information",
 *   label = @Translation("Billing information"),
 *   default_step = "order_information",
 *   wrapper_element = "fieldset",
 * )
 */
class CustomBillingInformation extends BillingInformation {

}

PaymentInformation

<?php
namespace Drupal\my_checkout_flow\Plugin\Commerce\CheckoutPane;

use Drupal\commerce_payment\Plugin\Commerce\CheckoutPane\PaymentInformation as PaymentInformationBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;

/**
 * Provides the payment information pane.
 *
 * @CommerceCheckoutPane(
 *   id = "custom_commerce_payment_information",
 *   label = @Translation("Custom Payment information"),
 *   display_label = @Translation("Payment information"),
 *   default_step = "order_information",
 *   wrapper_element = "fieldset",
 * )
 */
class PaymentInformation extends PaymentInformationBase {

  /**
   * {@inheritdoc}
   */
  public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form) {
    $pane_form = parent::buildPaneForm($pane_form, $form_state, $complete_form);

    // Add an after build callback in order to make modifications on the address form.
    $pane_form['#after_build'][] = [$this, 'paneFormAfterBuild'];

    return $pane_form;
  }

  /**
   * After build callback for the pane form.
   */
  public function paneFormAfterBuild(array $pane_form, FormStateInterface $form_state) {
    // Get billing form element. Where it is located depends on the payment method that is chosen.
    if (isset($pane_form['add_payment_method']['billing_information'])) {
      $billing_form = &$pane_form['add_payment_method']['billing_information'];
    }
    elseif (isset($pane_form['billing_information'])) {
      $billing_form = &$pane_form['billing_information'];
    }
    else {
      // No billing information found.
      return $pane_form;
    }

    // Get the address form element.
    $address_form = &$billing_form['address']['widget']['0']['address'];

    // Add element validation callback to autofill the address.
    $billing_form['#element_validate'] = array_merge(
      [[$this, 'profileSelectValidate']],
      \Drupal::service('element_info')->getInfoProperty('commerce_profile_select', '#element_validate', [])
    );

    // Set all address fields to non-required.
    foreach (Element::children($address_form) as $key) {
      $address_form[$key]['#required'] = FALSE;
    }

    // Hide the address form.
    $address_form['#access'] = FALSE;

    return $pane_form;
  }

  /**
   * Element validation callback for the profile select element.
   */
  public function profileSelectValidate(array &$element, FormStateInterface $form_state) {
    $billing_profile = $this->order->getBillingProfile()->get('address')->getValue();
    $form_state->setValue($element['address']['widget'][0]['address']['#parents'], $billing_profile[0]);
  }

}

PaymentProcess

<?php

namespace Drupal\my_checkout_flow\Plugin\Commerce\CheckoutPane;

use Drupal\commerce_payment\Plugin\Commerce\CheckoutPane\PaymentProcess as BasePaymentProcess;

/**
 * Provides the payment process pane.
 *
 * @CommerceCheckoutPane(
 *   id = "custom_payment_process",
 *   label = @Translation("Custom Payment process"),
 *   default_step = "payment",
 *   wrapper_element = "container",
 * )
 */
class PaymentProcess extends BasePaymentProcess {

  /**
   * {@inheritdoc}
   */
  public function isVisible() {
    // This pane can't be used without the PaymentInformation pane.
    $payment_info_pane = $this->checkoutFlow->getPane('custom_commerce_payment_information');
    return $payment_info_pane->isVisible() && $payment_info_pane->getStepId() != '_disabled';
  }

}

After this set checkout panes for your custom checkout flow as shown in the image below:

enter image description here

Find full code at https://github.com/patilvishalvs/my_checkout_flow

  • It's the form element related issues. Your form state is changing drop down value to an option which is not available in the options list of the former element. – Vishal Patil Nov 29 '17 at 15:26
  • Sorry, that was an unrelated error. I tried again and the checkout flow steps show up properly. However, the problem is that with the Commerce Stripe or Commerce Braintree modules, the credit card input form and the address input form appear to be locked together as a set (payment information); I can't find a way to separate the credit card number/expiration/cvc from the billing address. – Patrick Kenny Nov 29 '17 at 18:55
  • Please see updated answer. – Vishal Patil Nov 30 '17 at 10:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.