3

By default, Commerce 2 requests credit card info + address info (name, address, and company name) for all orders. I want to have users enter their credit card information but I don't want to collect their name, address, or company name. Stripe does not require name/address info so I don't want to require it, either.

As my payment gateway, I am using Commerce Stripe, but this applies to all payment gateways, such as Commerce PayPal, and the Manual (no credit card) payment gateway.

So, how can I safely remove the address form from the checkout page?

I first tried to hack Commerce directly (bad practice, just trying to see how it worked) but I broke the module.

As I looked around, it appeared that the billing info is deeply wired into the Commerce code. Is there a way to decouple it? Not all stores (those that sell digital products, etc.) need their customers' physical addresses.

On the checkout "Payment information" screen, I want to remove all of the fields from "Country" down to "Postal code":

commerce checkout screen

2
  • 1
    You can disable the order details in your checkout flow but I haven't been able to completely disable that step.
    – Darvanen
    Commented Sep 29, 2017 at 4:26
  • I would try to use hook_form_alter unset($form['field_address']); and if necessary override the validation function. Just might work.
    – No Sssweat
    Commented Oct 5, 2017 at 8:09

3 Answers 3

4

It is quite hard to do indeed. One way to do this is by setting a dummy billing address and hide the address form.

You will need to do the following:

  1. In a custom module, add a CommerceCheckoutPane plugin. The plugin class should extend \Drupal\commerce_payment\Plugin\Commerce\CheckoutPane\PaymentInformation.
  2. Override the buildPaneForm() method, in there:

    • Set an #after_build callback. In this callback, make each address field non-required. The reason that you need an #after_build callback is that else address fields won't be available.
    • On the commerce_profile_select element, set an #element_validate callback. In this callback, set the address values. The reason that you need an #element_validate is because a general form validation callback is called too late in the validation process to set the address in time. This is because the address element has a constraint and the address is validated against this constraint after #element_validate and before a general form validate. Should you try to set the address in a general form validation callback, then you'll get address field required errors because of the constraint.

      You can set the #element_validate callback as well in the #after_build callback, which I've done in the code example below.

  3. To hide the address form, you'll need to set #access to FALSE for the address form. This needs to happen in the #after_build callback as well, because earlier the address form isn't available yet.
  4. On the checkout flow settings, disable the default Payment information pane (and enable the custom Payment information pane if not yet enabled).
  5. To hide the dummy billing address on the 'Review' step, copy the template commerce-payment-method--credit-card.html.twig (and/or commerce-payment-method.html.twig if you are using a non-credit card payment method) to your theme and remove the line {{ payment_method.billing_profile }} from it.

Code example (file name: mymodule/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php):

namespace Drupal\custom_commerce\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) {
    // Set dummy address.
    $address = [
      'given_name' => 'A',
      'family_name' => 'A',
      'address_line1' => 'Dummy street',
      'postal_code' => '1234 AB',
      'locality' => 'Dummy city',
      'country_code' => 'NL',
    ];
    $form_state->setValue($element['address']['widget'][0]['address']['#parents'], $address);
  }

}
5
  • Thanks, this is an impressive answer. I don't mind using dummy data if the form can't be completely unset, because it's better to fill the db with garbage data than to use actual data that might get exposed. One question: if I use a dummy address as you described, will this address be passed to Stripe/PayPal/whatever other payment gateway I am using as part of the order process? I don't want to use dummy data and then cause legitimate credit cards to fail because the dummy postal code is wrong for the card. Commented Oct 2, 2017 at 14:38
  • 1
    I haven't used these payment gateways yet (I'm still in the process of setting up a store), but if the payment method in question works with billing addresses, it will probably receive that dummy address, yes. I think I read 'X does not require address info' as 'X does not use address info'.
    – MegaChriz
    Commented Oct 2, 2017 at 14:50
  • When I added this code to my site and replaced the payment information pane with the new custom one, the address was successfully hidden. However, when using this custom pane, the payment is not sent to the payment gateway (Stripe in my case). When I revert to the standard payment info pane, the payment is sent successfully, even when using a payment method (test credit card) that was saved with the custom pane (in other words, when using a payment method with the dummy address and the standard payment info pane, it works). Commented Oct 3, 2017 at 16:49
  • It sounds like that the code that I posted needs some work. We should aim for a solution where you don't need to use both payment info panes.
    – MegaChriz
    Commented Oct 4, 2017 at 8:57
  • Yes, specifically, when the posted code is used by itself, the order completes but the payment does not get made (the resulting order has no payment attached, and the payment gateway does not receive the payment data). Commented Oct 4, 2017 at 10:03
3
+100

TL:DR;

Go to admin/config/people/profiles/types/manage/customer/form-display and disable the form elements you want to hide (config applies to entire site)

Explanation

It should be fairly easy to do this, though let me just say up front that I haven't tested this myself.

What you are seeing as the address, is the "billing profile" (both the order and the payment method has a billing profile IIRC. DC has not created the billing profile themselves (unlike in Drupal 7) but are instead using the profile module.

When DC renders the "billing profile" or "billing information" it respects the global settings made for the profile type it uses (customer). This means you can configure how the form should appear, including disabling all elements. This can be done at admin/config/people/profiles/types/manage/customer/form-display. This also means that you can add additional fields to the profile, if you wanted to collect more information without touching DC itself.

Disclaimer - I have not tested this myself but having messed with Commerce quite a lot I'm pretty confident that this will work.

3
  • 1
    Wow, it really was that easy. Address can be disabled in one click and the payment goes through. Commented Oct 7, 2017 at 15:33
  • @googletorp This is the real answer which not only solves this particular problem, but also other cases where you want to collect additional information. You can also create a new profile type and I think, I am not sure, but it should be available as a pane in the checkout process.
    – Umair
    Commented Apr 13, 2020 at 11:40
  • @googletorp Is it necessary to have a "customer" profile type in order to complete the checkout process?
    – Umair
    Commented Apr 13, 2020 at 11:41
0

Actually, according to this thread on drupal.org, the fact that you can hide the billing form and Commerce Stripe still works is an anomaly; usually, this should cause Commerce to crash.

At this time, Commerce 2 requires a billing address.

There is a experimental patch and a discussion of why Commerce requires a billing address on drupal.org.

1
  • I admit that on the Commerce site that I'm building, the billing address is asked at an earlier stage and the code example in my answer was created from the code in that project. In the code example I only added the dummy address part, assuming that would work for payment methods that don't require a billing address (like the payment method 'cash on delivery').
    – MegaChriz
    Commented Dec 1, 2017 at 11:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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