diff --git a/uc_attribute/uc_attribute.module b/uc_attribute/uc_attribute.module
index 7c74771..b0b270b 100644
--- a/uc_attribute/uc_attribute.module
+++ b/uc_attribute/uc_attribute.module
@@ -211,6 +211,27 @@ function uc_attribute_uc_form_alter(&$form, &$form_state, $form_id) {
 }
 
 /**
+ * Implements hook_form_FORM_ID_alter() for uc_product_buy_it_now_form().
+ */
+function uc_attribute_form_uc_product_buy_it_now_form_alter(&$form, &$form_state) {
+  $form['#validate'][] = 'uc_attribute_buy_it_now_form_validate';
+}
+
+/**
+ * Validation callback for "buy it now" forms with attributes.
+ */
+function uc_attribute_buy_it_now_form_validate(&$form, &$form_state) {
+  $attributes = uc_product_get_attributes($form_state['values']['nid']);
+  if (!empty($attributes)) {
+    drupal_set_message(t('This product has options that need to be selected before purchase. Please select them in the form below.'), 'error');
+    $form_state['redirect'] = 'node/' . $form_state['values']['nid'];
+
+    // Prevent submit handlers from running.
+    $form_state['submit_handlers'] = array();
+  }
+}
+
+/**
  * Implements hook_node_load().
  */
 function uc_attribute_node_load($nodes) {
diff --git a/uc_catalog/lib/Drupal/uc_catalog/Tests/CatalogTest.php b/uc_catalog/lib/Drupal/uc_catalog/Tests/CatalogTest.php
index 94df299..4fbf11d 100644
--- a/uc_catalog/lib/Drupal/uc_catalog/Tests/CatalogTest.php
+++ b/uc_catalog/lib/Drupal/uc_catalog/Tests/CatalogTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\uc_catalog\Tests;
 
+use Drupal\Core\Language\Language;
 use Drupal\uc_store\Tests\UbercartTestBase;
 
 /**
@@ -14,8 +15,8 @@ use Drupal\uc_store\Tests\UbercartTestBase;
  */
 class CatalogTest extends UbercartTestBase {
 
-  public static $modules = array('uc_catalog', 'field_ui');
-  public static $adminPermissions = array('administer catalog', 'administer node fields', 'administer taxonomy_term fields');
+  public static $modules = array('uc_catalog', 'uc_attribute', 'field_ui');
+  public static $adminPermissions = array('administer catalog', 'administer node fields', 'administer taxonomy_term fields', 'view catalog');
 
   public static function getInfo() {
     return array(
@@ -25,6 +26,50 @@ class CatalogTest extends UbercartTestBase {
     );
   }
 
+  /**
+   * Tests the catalog display and "buy it now" button.
+   */
+  public function testCatalog() {
+    $this->drupalLogin($this->adminUser);
+
+    $term = $this->createTerm();
+    $product = $this->createProduct(array(
+      'taxonomy_catalog' => array($term->id()),
+    ));
+
+    $this->drupalGet('catalog');
+    $this->assertTitle('Catalog | Drupal');
+    $this->assertLink($term->label(), 0, 'The term is listed in the catalog.');
+
+    $this->clickLink($term->label());
+    $this->assertTitle($term->label() . ' | Drupal');
+    $this->assertLink($product->label(), 0, 'The product is listed in the catalog.');
+    $this->assertText($product->model, 'The product SKU is shown in the catalog.');
+    $this->assertText(uc_currency_format($product->sell_price), 'The product price is shown in the catalog.');
+
+    $this->drupalPostForm(NULL, array(), 'Add to cart');
+    $this->assertText($product->label() . ' added to your shopping cart.');
+  }
+
+  /**
+   * Tests the catalog with a product with attributes.
+   */
+  public function testCatalogAttribute() {
+    $this->drupalLogin($this->adminUser);
+
+    $term = $this->createTerm();
+    $product = $this->createProduct(array(
+      'taxonomy_catalog' => array($term->id()),
+    ));
+    $attribute = $this->createAttribute(array('display' => 0));
+    uc_attribute_subject_save($attribute, 'product', $product->id());
+
+    $this->drupalGet('catalog/' . $term->id());
+    $this->drupalPostForm(NULL, array(), 'Add to cart');
+    $this->assertNoText($product->label() . ' added to your shopping cart.');
+    $this->assertText('This product has options that need to be selected before purchase. Please select them in the form below.');
+  }
+
   public function testCatalogField() {
     $this->drupalLogin($this->adminUser);
 
@@ -34,6 +79,9 @@ class CatalogTest extends UbercartTestBase {
     $this->drupalGet('admin/structure/types/manage/product/fields');
     $this->assertText('taxonomy_catalog', 'Catalog taxonomy term reference field exists for products.');
 
+    $this->drupalGet('node/add/product');
+    $this->assertFieldByName('taxonomy_catalog', NULL, 'Catalog taxonomy field is shown on product node form.');
+
     // Check that product kits get the catalog taxonomy.
     \Drupal::moduleHandler()->install(array('uc_product_kit'));
 
@@ -59,4 +107,22 @@ class CatalogTest extends UbercartTestBase {
     $this->drupalGet('admin/structure/types/manage/product/fields');
     $this->assertText('taxonomy_catalog', 'Catalog taxonomy term reference field exists.');
   }
+
+  /**
+   * Returns a new term with random properties in the catalog vocabulary.
+   */
+  function createTerm() {
+    $term = entity_create('taxonomy_term', array(
+      'name' => $this->randomName(),
+      'description' => array(
+        'value' => $this->randomName(),
+        'format' => 'plain_text',
+      ),
+      'vid' => 'catalog',
+      'langcode' => Language::LANGCODE_NOT_SPECIFIED,
+    ));
+    $term->save();
+    return $term;
+  }
+
 }
diff --git a/uc_catalog/uc_catalog.module b/uc_catalog/uc_catalog.module
index 6987f77..921b12a 100644
--- a/uc_catalog/uc_catalog.module
+++ b/uc_catalog/uc_catalog.module
@@ -350,6 +350,12 @@ function uc_catalog_add_node_type($type) {
     ));
     $instance->save();
 
+    entity_get_form_display('node', $type, 'default')
+      ->setComponent('taxonomy_catalog', array(
+        'type' => 'options_select',
+      ))
+      ->save();
+
     entity_get_display('node', $type, 'default')
       ->setComponent('taxonomy_catalog')
       ->save();
diff --git a/uc_product/lib/Drupal/uc_product/Form/AddToCartForm.php b/uc_product/lib/Drupal/uc_product/Form/AddToCartForm.php
new file mode 100644
index 0000000..405d0a0
--- /dev/null
+++ b/uc_product/lib/Drupal/uc_product/Form/AddToCartForm.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\uc_product\Form\AddToCartForm.
+ */
+
+namespace Drupal\uc_product\Form;
+
+use Drupal\node\NodeInterface;
+
+/**
+ * Defines a complex form for adding a product to the cart.
+ */
+class AddToCartForm extends BuyItNowForm {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'uc_product_add_to_cart_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state, NodeInterface $node = NULL) {
+    $form = parent::buildForm($form, $form_state, $node);
+
+    $form['node'] = array(
+      '#type' => 'value',
+      '#value' => isset($form_state['storage']['variant']) ? $form_state['storage']['variant'] : $node,
+    );
+
+    if ($node->default_qty > 0) {
+      if (\Drupal::config('uc_product.settings')->get('add_to_cart_qty')) {
+        $form['qty'] = array(
+          '#type' => 'uc_quantity',
+          '#title' => t('Quantity'),
+          '#default_value' => $node->default_qty,
+        );
+      }
+      else {
+        $form['qty']['#value'] = $node->default_qty;
+      }
+    }
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, array &$form_state) {
+    $data = \Drupal::moduleHandler()->invokeAll('uc_add_to_cart_data', array($form_state['values']));
+    $form_state['storage']['variant'] = uc_product_load_variant($form_state['values']['nid'], $data);
+  }
+
+}
diff --git a/uc_product/lib/Drupal/uc_product/Form/BuyItNowForm.php b/uc_product/lib/Drupal/uc_product/Form/BuyItNowForm.php
new file mode 100644
index 0000000..28050d2
--- /dev/null
+++ b/uc_product/lib/Drupal/uc_product/Form/BuyItNowForm.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\uc_product\Form\BuyItNowForm.
+ */
+
+namespace Drupal\uc_product\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\node\NodeInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
+/**
+ * Defines a simple form for adding a product to the cart.
+ */
+class BuyItNowForm extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'uc_product_buy_it_now_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state, NodeInterface $node = NULL) {
+    $form['nid'] = array(
+      '#type' => 'value',
+      '#value' => $node->id(),
+    );
+
+    $form['qty'] = array(
+      '#type' => 'value',
+      '#value' => 1,
+    );
+
+    $form['actions'] = array('#type' => 'actions');
+    $form['actions']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Add to cart'),
+      '#id' => 'edit-submit-' . $node->id(),
+    );
+
+    uc_form_alter($form, $form_state, __FUNCTION__);
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    $data = \Drupal::moduleHandler()->invokeAll('uc_add_to_cart_data', array($form_state['values']));
+    $msg = \Drupal::config('uc_cart.settings')->get('add_item_msg');
+    $form_state['redirect'] = uc_cart_add_item($form_state['values']['nid'], $form_state['values']['qty'], $data, NULL, $msg);
+  }
+
+}
diff --git a/uc_product/lib/Drupal/uc_product/Plugin/views/field/AddToCart.php b/uc_product/lib/Drupal/uc_product/Plugin/views/field/AddToCart.php
index 076b489..cccc549 100644
--- a/uc_product/lib/Drupal/uc_product/Plugin/views/field/AddToCart.php
+++ b/uc_product/lib/Drupal/uc_product/Plugin/views/field/AddToCart.php
@@ -26,7 +26,7 @@ class AddToCart extends FieldPluginBase {
     $nid = $this->getValue($values);
     $node = node_load($nid);
     if (uc_product_is_product($node)) {
-      $form = drupal_get_form('uc_product_add_to_cart_form', $node);
+      $form = drupal_get_form('\Drupal\uc_product\Form\AddToCartForm', $node);
       return drupal_render($form);
     }
   }
diff --git a/uc_product/lib/Drupal/uc_product/Plugin/views/field/BuyItNow.php b/uc_product/lib/Drupal/uc_product/Plugin/views/field/BuyItNow.php
index 538d54a..d6eff65 100644
--- a/uc_product/lib/Drupal/uc_product/Plugin/views/field/BuyItNow.php
+++ b/uc_product/lib/Drupal/uc_product/Plugin/views/field/BuyItNow.php
@@ -26,7 +26,7 @@ class BuyItNow extends FieldPluginBase {
     $nid = $this->getValue($values);
     $node = node_load($nid);
     if (uc_product_is_product($node)) {
-      $form = drupal_get_form('uc_catalog_buy_it_now_form', $node);
+      $form = drupal_get_form('\Drupal\uc_product\Form\BuyItNowForm', $node);
       return drupal_render($form);
     }
   }
diff --git a/uc_product/uc_product.module b/uc_product/uc_product.module
index 5f2c0e8..0932a69 100644
--- a/uc_product/uc_product.module
+++ b/uc_product/uc_product.module
@@ -538,7 +538,7 @@ function uc_product_node_delete($node) {
  *   The current form state.  This must contain a 'variant' entry in the
  *   'storage' array which represents the product as configured by user input
  *   data. In most cases, this is provided automatically by
- *   uc_product_add_to_cart_form_validate().
+ *   AddToCartForm::validateForm().
  * @param $keys
  *   An array of keys in the built product content which should be replaced
  *   (e.g. 'display_price').
@@ -581,7 +581,7 @@ function uc_product_view_product(EntityInterface $node, EntityViewDisplay $displ
     // Build the 'add to cart' form, and use the updated variant based on data
     // provided by the form (e.g. attribute default options).
     if (module_exists('uc_cart') && $variant->id() && empty($variant->data['display_only'])) {
-      $add_to_cart_form = drupal_get_form('uc_product_add_to_cart_form', $variant);
+      $add_to_cart_form = drupal_get_form('\Drupal\uc_product\Form\AddToCartForm', $variant);
       if (\Drupal::config('uc_product.settings')->get('update_node_view')) {
         $variant = $add_to_cart_form['node']['#value'];
       }
@@ -933,122 +933,6 @@ function uc_product_uc_add_to_cart_data($form_values) {
 }
 
 /**
- * Form builder for uc_catalog_buy_it_now_form().
- *
- * @see uc_product_forms()
- * @see uc_catalog_buy_it_now_form_validate()
- * @see uc_catalog_buy_it_now_form_submit()
- * @ingroup forms
- */
-function uc_catalog_buy_it_now_form($form, &$form_state, $node) {
-  $form['nid'] = array('#type' => 'hidden', '#value' => $node->id());
-  $form['actions'] = array('#type' => 'actions');
-  $form['actions']['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Add to cart'),
-    '#id' => 'edit-submit-' . $node->id(),
-    '#attributes' => array(
-      'class' => array('list-add-to-cart'),
-    ),
-  );
-
-  uc_form_alter($form, $form_state, __FUNCTION__);
-
-  return $form;
-}
-
-/**
- * Redirects to the product page if attributes need to be selected.
- *
- * @see uc_catalog_buy_it_now_form()
- * @see uc_catalog_buy_it_now_form_submit()
- */
-function uc_catalog_buy_it_now_form_validate($form, &$form_state) {
-  if (module_exists('uc_attribute')) {
-    $attributes = uc_product_get_attributes($form_state['values']['nid']);
-    if (!empty($attributes)) {
-      drupal_set_message(t('This product has options that need to be selected before purchase. Please select them in the form below.'), 'error');
-      drupal_goto('node/' . $form_state['values']['nid']);
-    }
-  }
-}
-
-/**
- * Form submission handler for uc_catalog_buy_it_now_form().
- *
- * @see uc_catalog_buy_it_now_form()
- * @see uc_catalog_buy_it_now_form_validate()
- */
-function uc_catalog_buy_it_now_form_submit($form, &$form_state) {
-  $data = \Drupal::moduleHandler()->invokeAll('uc_add_to_cart_data', array($form_state['values']));
-  $msg = \Drupal::config('uc_cart.settings')->get('add_item_msg');
-  $form_state['redirect'] = uc_cart_add_item($form_state['values']['nid'], 1, $data, NULL, $msg);
-}
-
-/**
- * Form to add the $node product to the cart.
- *
- * @param $node
- *   A product node.
- *
- * @see uc_product_forms()
- * @see uc_product_add_to_cart_form_submit()
- * @ingroup forms
- */
-function uc_product_add_to_cart_form($form, &$form_state, $node) {
-  $form['nid'] = array('#type' => 'value', '#value' => $node->id());
-
-  if ($node->default_qty > 0 && \Drupal::config('uc_product.settings')->get('add_to_cart_qty')) {
-    $form['qty'] = array(
-      '#type' => 'uc_quantity',
-      '#title' => t('Quantity'),
-      '#default_value' => $node->default_qty,
-    );
-  }
-  else {
-    $form['qty'] = array('#type' => 'hidden', '#value' => $node->default_qty ? $node->default_qty : 1);
-  }
-
-  $form['actions'] = array('#type' => 'actions');
-  $form['actions']['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Add to cart'),
-    '#id' => 'edit-submit-' . $node->id(),
-    '#attributes' => array(
-      'class' => array('node-add-to-cart'),
-    ),
-  );
-
-  $form['node'] = array(
-    '#type' => 'value',
-    '#value' => isset($form_state['storage']['variant']) ? $form_state['storage']['variant'] : $node,
-  );
-
-  uc_form_alter($form, $form_state, __FUNCTION__);
-
-  return $form;
-}
-
-/**
- * Form validation handler for uc_product_add_to_cart_form().
- */
-function uc_product_add_to_cart_form_validate($form, &$form_state) {
-  $data = \Drupal::moduleHandler()->invokeAll('uc_add_to_cart_data', array($form_state['values']));
-  $form_state['storage']['variant'] = uc_product_load_variant($form_state['values']['nid'], $data);
-}
-
-/**
- * Form submission handler for uc_product_add_to_cart_form().
- *
- * @see uc_product_add_to_cart_form()
- */
-function uc_product_add_to_cart_form_submit($form, &$form_state) {
-  $data = \Drupal::moduleHandler()->invokeAll('uc_add_to_cart_data', array($form_state['values']));
-  $msg = \Drupal::config('uc_cart.settings')->get('add_item_msg');
-  $form_state['redirect'] = uc_cart_add_item($form_state['values']['nid'], $form_state['values']['qty'], $data, NULL, $msg);
-}
-
-/**
  * Returns an array of product node types.
  */
 function uc_product_types() {
diff --git a/uc_product_kit/uc_product_kit.module b/uc_product_kit/uc_product_kit.module
index b60d518..62b30df 100644
--- a/uc_product_kit/uc_product_kit.module
+++ b/uc_product_kit/uc_product_kit.module
@@ -108,7 +108,7 @@ function uc_product_kit_uc_form_alter(&$form, &$form_state, $form_id) {
     $form['#validate'][] = 'uc_product_kit_add_to_cart_form_validate';
     $form['#submit'][] = 'uc_product_kit_add_to_cart_form_submit';
   }
-  elseif ($form_id == 'uc_catalog_buy_it_now_form') {
+  elseif ($form_id == 'uc_product_buy_it_now_form') {
     $kit = $form['node']['#value'];
     if ($kit->getType() !== 'product_kit') {
       return;
@@ -732,13 +732,14 @@ function uc_product_kit_node_view(EntityInterface $node, EntityViewDisplay $disp
 }
 
 /**
- * Form validation handler for uc_product_add_to_cart_form().
+ * Form validation handler for adding a product kit to the cart.
  *
- * @see uc_product_kit_add_to_cart_form()
- * @see uc_product_add_to_cart_form_validate()
+ * @see \Drupal\uc_product\Form\AddToCartForm
  */
 function uc_product_kit_add_to_cart_form_validate($form, &$form_state) {
-  uc_product_add_to_cart_form_validate($form, $form_state);
+  $data = \Drupal::moduleHandler()->invokeAll('uc_add_to_cart_data', array($form_state['values']));
+  $form_state['storage']['variant'] = uc_product_load_variant($form_state['values']['nid'], $data);
+
   foreach ($form_state['storage']['variant']->products as &$product) {
     $data = \Drupal::moduleHandler()->invokeAll('uc_add_to_cart_data', array($form_state['values']['products'][$product->id()]));
     $data += $product->data;
@@ -751,7 +752,7 @@ function uc_product_kit_add_to_cart_form_validate($form, &$form_state) {
 /**
  * Adds each product kit's component to the cart in the correct quantities.
  *
- * @see uc_product_kit_add_to_cart_form()
+ * @see \Drupal\uc_product\Form\AddToCartForm
  */
 function uc_product_kit_add_to_cart_form_submit($form, &$form_state) {
   if (\Drupal::config('uc_cart.settings')->get('add_item_msg')) {
