diff --git a/shipping/uc_quote/src/Plugin/Ubercart/CheckoutPane/QuotePane.php b/shipping/uc_quote/src/Plugin/Ubercart/CheckoutPane/QuotePane.php
new file mode 100644
index 0000000..5093feb
--- /dev/null
+++ b/shipping/uc_quote/src/Plugin/Ubercart/CheckoutPane/QuotePane.php
@@ -0,0 +1,169 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\uc_quote\Plugin\Ubercart\CheckoutPane\QuotePane.
+ */
+
+namespace Drupal\uc_quote\Plugin\Ubercart\CheckoutPane;
+
+use Drupal\Component\Utility\Xss;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\uc_cart\CheckoutPanePluginBase;
+use Drupal\uc_order\OrderInterface;
+
+/**
+ * Shipping quote checkout pane plugin.
+ *
+ * @CheckoutPane(
+ *   id = "quotes",
+ *   title = @Translation("Calculate shipping cost"),
+ *   weight = 5,
+ *   shippable = TRUE
+ * )
+ */
+class QuotePane extends CheckoutPanePluginBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function view(OrderInterface $order, array $form, FormStateInterface $form_state) {
+    $contents['#description'] = Xss::filterAdmin(\Drupal::config('uc_quote.settings')->get('pane_description'));
+
+    $contents['#attached']['library'][] = 'uc_quote/uc_quote.styles';
+
+    $contents['uid'] = array(
+      '#type' => 'hidden',
+      '#value' => \Drupal::currentUser()->id(),
+    );
+    $contents['quote_button'] = array(
+      '#type' => 'submit',
+      '#value' => t('Click to calculate shipping'),
+      '#submit' => [[$this, 'paneSubmit']],
+      '#weight' => 0,
+      '#ajax' => array(
+        'effect' => 'slide',
+        'progress' => array(
+          'type' => 'bar',
+          'message' => t('Receiving quotes...'),
+        ),
+      ),
+      // Shipping quotes can be retrieved even if the form doesn't validate.
+      '#limit_validation_errors' => array(),
+    );
+    $contents['quotes'] = array(
+      '#tree' => TRUE,
+      '#prefix' => '<div id="quote">',
+      '#suffix' => '</div>',
+      '#weight' => 1,
+    );
+
+    $contents['quotes'] += $order->quote_form;
+
+    $form_state->set(['uc_ajax', 'uc_quote', 'panes][quotes][quote_button'], array(
+      'payment-pane' => 'uc_ajax_replace_checkout_pane',
+      'quotes-pane' => 'uc_ajax_replace_checkout_pane'
+    ));
+    $form_state->set(['uc_ajax', 'uc_quote', 'panes][quotes][quotes][quote_option'], array(
+      'payment-pane' => 'uc_ajax_replace_checkout_pane',
+    ));
+
+    return $contents;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepare(OrderInterface $order, array $form, FormStateInterface $form_state) {
+    // If a quote was explicitly selected, add it to the order.
+    if (isset($form['panes']['quotes']['quotes']['quote_option']['#value']) && isset($form['panes']['quotes']['quotes']['quote_option']['#default_value'])
+      && $form['panes']['quotes']['quotes']['quote_option']['#value'] !== $form['panes']['quotes']['quotes']['quote_option']['#default_value']) {
+      $quote_option = explode('---', $form_state->getValue(['panes', 'quotes', 'quotes', 'quote_option']));
+      $order->quote['method'] = $quote_option[0];
+      $order->quote['accessorials'] = $quote_option[1];
+      $order->data->uc_quote_selected = TRUE;
+    }
+
+    // If the current quote was never explicitly selected, discard it and
+    // use the default.
+    if (empty($order->data->uc_quote_selected)) {
+      unset($order->quote);
+    }
+
+    // Ensure that the form builder uses the default value to decide which
+    // radio button should be selected.
+    $input = $form_state->getUserInput();
+    unset($input['panes']['quotes']['quotes']['quote_option']);
+    $form_state->setUserInput($input);
+
+    $order->quote_form = uc_quote_build_quote_form($order, !$form_state->get('quote_requested'));
+
+    $default_option = _uc_quote_extract_default_option($order->quote_form);
+    if ($default_option) {
+      $order->quote['rate'] = $order->quote_form[$default_option]['rate']['#value'];
+
+      $quote_option = explode('---', $default_option);
+      $order->quote['method'] = $quote_option[0];
+      $order->quote['accessorials'] = $quote_option[1];
+      $methods = uc_quote_methods();
+      $method = $methods[$quote_option[0]];
+
+      $label = $method['quote']['accessorials'][$quote_option[1]];
+
+      $result = db_query("SELECT line_item_id FROM {uc_order_line_items} WHERE order_id = :id AND type = :type", [':id' => $order->id(), ':type' => 'shipping']);
+      if ($lid = $result->fetchField()) {
+        uc_order_update_line_item($lid,
+          $label,
+          $order->quote['rate']
+        );
+      }
+      else {
+        uc_order_line_item_add($order->id(), 'shipping',
+          $label,
+          $order->quote['rate']
+        );
+      }
+    }
+    // If there is no default option, then no valid quote was selected.
+    else {
+      unset($order->quote);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function process(OrderInterface $order, array $form, FormStateInterface $form_state) {
+    $this->prepare($order, $form, $form_state);
+
+    if (!isset($order->quote) && \Drupal::config('uc_quote.settings')->get('require_quote')) {
+      $form_state->setErrorByName('panes][quotes][quotes][quote_option', t('You must select a shipping option before continuing.'));
+      return FALSE;
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function review(OrderInterface $order) {
+    $review = array();
+
+    $result = db_query("SELECT * FROM {uc_order_line_items} WHERE order_id = :id AND type = :type", [':id' => $order->id(), ':type' => 'shipping']);
+    if ($line_item = $result->fetchAssoc()) {
+      $review[] = array('title' => $line_item['title'], 'data' => array('#theme' => 'uc_price', '#price' => $line_item['amount']));
+    }
+
+    return $review;
+  }
+
+  /**
+   * Pane submission handler to trigger quote calculation.
+   */
+  public function paneSubmit($form, FormStateInterface $form_state) {
+    $form_state->setRebuild();
+    $form_state->set('quote_requested', TRUE);
+  }
+
+}
diff --git a/shipping/uc_quote/uc_quote.module b/shipping/uc_quote/uc_quote.module
index 785a42e..f86f22f 100644
--- a/shipping/uc_quote/uc_quote.module
+++ b/shipping/uc_quote/uc_quote.module
@@ -218,19 +218,6 @@ function uc_quote_uc_cart_pane($items) {
 }
 
 /**
- * Defines the shipping quote checkout pane.
- */
-function uc_quote_uc_checkout_pane() {
-  $panes['quotes'] = array(
-    'callback' => 'uc_checkout_pane_quotes',
-    'title' => t('Calculate shipping cost'),
-    'weight' => 5,
-    'shippable' => TRUE,
-  );
-  return $panes;
-}
-
-/**
  * Implements hook_uc_order_pane().
  *
  * Defines the shipping quote order pane.
@@ -474,150 +461,6 @@ function uc_cart_pane_quotes($form, FormStateInterface $form_state, $items) {
 }
 
 /**
- * Shipping quote checkout pane callback.
- *
- * Selects a quoting method based on the enabled methods' weight and the types
- * of products in the cart. The "Get Quotes" button fires a callback that
- * returns a form for the customer to select a rate based on their needs and
- * preferences.
- *
- * Adds a line item to the order that records the chosen shipping quote.
- *
- * @see uc_quote_checkout_pane_quotes_submit()
- */
-function uc_checkout_pane_quotes($op, &$order, $form = NULL, FormStateInterface $form_state = NULL) {
-  $quote_config = \Drupal::config('uc_quote.settings');
-  switch ($op) {
-    case 'view':
-      $description = Xss::filterAdmin($quote_config->get('pane_description'));
-
-      $contents['#attached']['library'][] = 'uc_quote/uc_quote.styles';
-
-      $contents['uid'] = array(
-        '#type' => 'hidden',
-        '#value' => \Drupal::currentUser()->id(),
-      );
-      $contents['quote_button'] = array(
-        '#type' => 'submit',
-        '#value' => t('Click to calculate shipping'),
-        '#submit' => array('uc_quote_checkout_pane_quotes_submit'),
-        '#weight' => 0,
-        '#ajax' => array(
-          'effect' => 'slide',
-          'progress' => array(
-            'type' => 'bar',
-            'message' => t('Receiving quotes...'),
-          ),
-        ),
-        // Shipping quotes can be retrieved even if the form doesn't validate.
-        '#limit_validation_errors' => array(),
-      );
-      $contents['quotes'] = array(
-        '#tree' => TRUE,
-        '#prefix' => '<div id="quote">',
-        '#suffix' => '</div>',
-        '#weight' => 1,
-      );
-
-      $contents['quotes'] += $order->quote_form;
-
-      $form_state->set(['uc_ajax', 'uc_quote', 'panes][quotes][quote_button'], array(
-        'payment-pane' => 'uc_ajax_replace_checkout_pane',
-        'quotes-pane' => 'uc_ajax_replace_checkout_pane'
-      ));
-      $form_state->set(['uc_ajax', 'uc_quote', 'panes][quotes][quotes][quote_option'], array(
-        'payment-pane' => 'uc_ajax_replace_checkout_pane',
-      ));
-
-      return array('description' => $description, 'contents' => $contents);
-
-    case 'prepare':
-    case 'process':
-      // If a quote was explicitly selected, add it to the order.
-      if (isset($form['panes']['quotes']['quotes']['quote_option']['#value']) && isset($form['panes']['quotes']['quotes']['quote_option']['#default_value'])
-          && $form['panes']['quotes']['quotes']['quote_option']['#value'] !== $form['panes']['quotes']['quotes']['quote_option']['#default_value']) {
-        $quote_option = explode('---', $form_state->getValue(['panes', 'quotes', 'quotes', 'quote_option']));
-        $order->quote['method'] = $quote_option[0];
-        $order->quote['accessorials'] = $quote_option[1];
-        $order->data->uc_quote_selected = TRUE;
-      }
-
-      // If the current quote was never explicitly selected, discard it and
-      // use the default.
-      if (empty($order->data->uc_quote_selected)) {
-        unset($order->quote);
-      }
-
-      // Ensure that the form builder uses the default value to decide which
-      // radio button should be selected.
-      $input = $form_state->getUserInput();
-      unset($input['panes']['quotes']['quotes']['quote_option']);
-      $form_state->setUserInput($input);
-
-      $order->quote_form = uc_quote_build_quote_form($order, !$form_state->get('quote_requested'));
-
-      $default_option = _uc_quote_extract_default_option($order->quote_form);
-      if ($default_option) {
-        $order->quote['rate'] = $order->quote_form[$default_option]['rate']['#value'];
-
-        $quote_option = explode('---', $default_option);
-        $order->quote['method'] = $quote_option[0];
-        $order->quote['accessorials'] = $quote_option[1];
-        $methods = uc_quote_methods();
-        $method = $methods[$quote_option[0]];
-
-        $label = $method['quote']['accessorials'][$quote_option[1]];
-
-        $result = db_query("SELECT line_item_id FROM {uc_order_line_items} WHERE order_id = :id AND type = :type", [':id' => $order->id(), ':type' => 'shipping']);
-        if ($lid = $result->fetchField()) {
-          uc_order_update_line_item($lid,
-            $label,
-            $order->quote['rate']
-          );
-        }
-        else {
-          uc_order_line_item_add($order->id(), 'shipping',
-            $label,
-            $order->quote['rate']
-          );
-        }
-      }
-      // If there is no default option, then no valid quote was selected.
-      else {
-        unset($order->quote);
-      }
-
-      if (!isset($order->quote) && $op == 'process' && $quote_config->get('require_quote')) {
-        $form_state->setErrorByName('panes][quotes][quotes][quote_option', t('You must select a shipping option before continuing.'));
-        return FALSE;
-      }
-      else {
-        return TRUE;
-      }
-
-    case 'review':
-      $review = array();
-
-      $result = db_query("SELECT * FROM {uc_order_line_items} WHERE order_id = :id AND type = :type", [':id' => $order->id(), ':type' => 'shipping']);
-      if ($line_item = $result->fetchAssoc()) {
-        $review[] = array('title' => $line_item['title'], 'data' => array('#theme' => 'uc_price', '#price' => $line_item['amount']));
-      }
-
-      return $review;
-  }
-}
-
-/**
- * Form submission handler for uc_checkout_pane_quotes().
- *
- * @see uc_checkout_pane_quotes()
- */
-function uc_quote_checkout_pane_quotes_submit($form, FormStateInterface $form_state) {
-  $form_state->setRebuild();
-  $form_state->set('quote_requested', TRUE);
-}
-
-/**
  * Shipping quote order pane callback.
  *
  * @see uc_quote_order_pane_quotes_submit()
