diff --git a/payment/uc_payment/uc_payment.module b/payment/uc_payment/uc_payment.module
index c29c2d9..c28fb9d 100644
--- a/payment/uc_payment/uc_payment.module
+++ b/payment/uc_payment/uc_payment.module
@@ -303,8 +303,7 @@ function uc_payment_enter($order_id, $method, $amount, $uid = 0, $data = NULL, $
 
   // Ensure user has an account before payment is made.
   if (\Drupal::moduleHandler()->moduleExists('uc_cart')) {
-    $cart = \Drupal\uc_cart\Controller\Cart::create(\Drupal::getContainer());
-    $cart->completeSale($order);
+    \Drupal::service('uc_cart.manager')->completeSale($order);
   }
 
   \Drupal::moduleHandler()->invokeAll('uc_payment_entered', array($order, $method, $amount, $account, $data, $comment));
diff --git a/uc_cart/src/Cart.php b/uc_cart/src/Cart.php
new file mode 100644
index 0000000..739251b
--- /dev/null
+++ b/uc_cart/src/Cart.php
@@ -0,0 +1,202 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\uc_cart\Cart.
+ */
+
+namespace Drupal\uc_cart;
+
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Url;
+use Drupal\node\Entity\Node;
+use Drupal\uc_cart\Entity\CartItem;
+
+class Cart implements CartInterface {
+
+  /**
+   * The cart ID.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * Constructor.
+   *
+   * @param string $id
+   *   The cart ID.
+   */
+  public function __construct($id) {
+    $this->id = $id;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getId() {
+    return $this->id;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContents() {
+    $items = array();
+
+    $result = \Drupal::entityQuery('uc_cart_item')
+      ->condition('cart_id', $this->id)
+      ->sort('cart_item_id', 'ASC')
+      ->execute();
+
+    if (!empty($result)) {
+      $items = \Drupal::entityManager()->getStorage('uc_cart_item')->loadMultiple(array_keys($result));
+    }
+
+    // Allow other modules a chance to alter the fully loaded cart object.
+    \Drupal::moduleHandler()->alter('uc_cart', $items);
+
+    return $items;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addItem($nid, $qty = 1, $data = NULL, $msg = TRUE) {
+    $node = Node::load($nid);
+
+    if (is_null($data) || !isset($data['module'])) {
+      $data['module'] = 'uc_product';
+    }
+
+    // Invoke hook_uc_add_to_cart() to give other modules a chance to affect the process.
+    $result = \Drupal::moduleHandler()->invokeAll('uc_add_to_cart', array($nid, $qty, $data));
+    if (is_array($result) && !empty($result)) {
+      foreach ($result as $row) {
+        if ($row['success'] === FALSE) {
+          // Module implementing the hook does NOT want this item added!
+          if (isset($row['message']) && !empty($row['message'])) {
+            $message = $row['message'];
+          }
+          else {
+            $message = t('Sorry, that item is not available for purchase at this time.');
+          }
+          if (isset($row['silent']) && ($row['silent'] === TRUE)) {
+            return $this->getAddItemRedirect();
+          }
+          else {
+            drupal_set_message($message, 'error');
+          }
+          // Stay on this page.
+          $query = \Drupal::request()->query;
+          return Url::fromRoute('<current>', [], ['query' => UrlHelper::filterQueryParameters($query->all())]);
+        }
+      }
+    }
+
+    // Now we can go ahead and add the item because either:
+    //   1) No modules implemented hook_uc_add_to_cart(), or
+    //   2) All modules implementing that hook want this item added.
+    $result = \Drupal::entityQuery('uc_cart_item')
+      ->condition('cart_id', $this->id)
+      ->condition('nid', $nid)
+      ->condition('data', serialize($data))
+      ->execute();
+
+    if (empty($result)) {
+      // If the item isn't in the cart yet, add it.
+      $item_entity = CartItem::create(array(
+        'cart_id' => $this->id,
+        'nid' => $nid,
+        'qty' => $qty,
+        'data' => $data,
+      ));
+      $item_entity->save();
+      if ($msg) {
+        drupal_set_message(t('<strong>@product-title</strong> added to <a href="@url">your shopping cart</a>.', ['@product-title' => $node->label(), '@url' => \Drupal::url('uc_cart.cart')]));
+      }
+    }
+    else {
+      // If it is in the cart, update the item instead.
+      if ($msg) {
+        drupal_set_message(t('Your item(s) have been updated.'));
+      }
+      $item_entity = CartItem::load(current(array_keys($result)));
+      $qty += $item_entity->qty->value;
+      \Drupal::moduleHandler()->invoke($data['module'], 'uc_update_cart_item', array($nid, $data, min($qty, 999999), $this->id));
+    }
+
+    // Invalidate the cache.
+    Cache::invalidateTags(['uc_cart:' . $this->id]);
+
+    // Invalidate the cart order.
+    // @todo Remove this and cache the order object with a tag instead?
+    $session = \Drupal::service('session');
+    $session->set('uc_cart_order_rebuild', TRUE);
+
+    return $this->getAddItemRedirect();
+  }
+
+  /**
+   * Computes the destination Url for an add-to-cart action.
+   *
+   * Redirect Url is chosen in the following order:
+   *  - Query parameter "destination"
+   *  - Cart config variable "uc_cart.settings.add_item_redirect"
+   *
+   * @return \Drupal\Core\Url
+   *   A Url destination for redirection.
+   */
+  protected function getAddItemRedirect() {
+    // Check for destination= query string
+    $query = \Drupal::request()->query;
+    $destination = $query->get('destination');
+    if (!empty($destination)) {
+      return Url::fromUri('base:' . $destination);
+    }
+
+    // Save current Url to session before redirecting
+    // so we can go "back" here from the cart.
+    $session = \Drupal::service('session');
+    $session->set('uc_cart_last_url', Url::fromRoute('<current>')->toString());
+    $redirect = \Drupal::config('uc_cart.settings')->get('add_item_redirect');
+    if ($redirect != '<none>') {
+      return Url::fromUri('base:' . $redirect);
+    }
+    else {
+      return Url::fromRoute('<current>', [], ['query' => UrlHelper::filterQueryParameters($query->all())]);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function emptyCart() {
+    $result = \Drupal::entityQuery('uc_cart_item')
+      ->condition('cart_id', $this->id)
+      ->execute();
+
+    if (!empty($result)) {
+      $storage = \Drupal::entityManager()->getStorage('uc_cart_item');
+      $entities = $storage->loadMultiple(array_keys($result));
+      $storage->delete($entities);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isShippable() {
+    $items = $this->getContents();
+
+    foreach ($items as $item) {
+      if (uc_order_product_is_shippable($item)) {
+        return TRUE;
+      }
+    }
+
+    return FALSE;
+  }
+
+}
diff --git a/uc_cart/src/CartInterface.php b/uc_cart/src/CartInterface.php
new file mode 100644
index 0000000..c8b1352
--- /dev/null
+++ b/uc_cart/src/CartInterface.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\uc_cart\CartInterface.
+ */
+
+namespace Drupal\uc_cart;
+
+/**
+ * Represents a shopping cart.
+ */
+interface CartInterface {
+
+  /**
+   * Time in seconds after which a cart order is deemed abandoned.
+   */
+  const ORDER_TIMEOUT = 86400; // 24 hours
+
+  /**
+   * Time in seconds after which the checkout page is deemed abandoned.
+   */
+  const CHECKOUT_TIMEOUT = 1800; // 30 minutes
+
+  /**
+   * Returns the unique ID for the cart.
+   *
+   * @return string
+   *   The cart ID.
+   */
+  public function getId();
+
+  /**
+   * Returns the items in the shopping cart.
+   *
+   * @return \Drupal\uc_cart\Entity\CartItem[]
+   *   The items.
+   */
+  public function getContents();
+
+  /**
+   * Adds an item to the cart.
+   *
+   * @param int $nid
+   *   Node ID to add to cart.
+   * @param int $qty
+   *   Quantity to add to cart.
+   * @param array $data
+   *   Array of module-specific data to add to cart.
+   * @param bool $msg
+   *   Whether to display a message upon adding an item to the cart.
+   *
+   * @return \Drupal\Core\Url
+   *   A URL to redirect to.
+   */
+  public function addItem($nid, $qty = 1, $data = NULL, $msg = TRUE);
+
+  /**
+   * Empties a cart of its contents.
+   */
+  public function emptyCart();
+
+  /**
+   * Determines whether a cart contains shippable items or not.
+   *
+   * @return bool
+   *   TRUE if the cart contains at least one shippable item, FALSE otherwise.
+   */
+  public function isShippable();
+
+}
diff --git a/uc_cart/src/CartManager.php b/uc_cart/src/CartManager.php
new file mode 100644
index 0000000..524503d
--- /dev/null
+++ b/uc_cart/src/CartManager.php
@@ -0,0 +1,190 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\uc_cart\CartManager.
+ */
+
+namespace Drupal\uc_cart;
+
+use Drupal\Component\Utility\Xss;
+use Drupal\Core\Session\AccountProxyInterface;
+use Drupal\user\Entity\User;
+use Symfony\Component\HttpFoundation\Session\SessionInterface;
+
+/**
+ * Provides the cart manager service.
+ */
+class CartManager implements CartManagerInterface {
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountProxyInterface
+   */
+  protected $currentUser;
+
+  /**
+   * The session.
+   *
+   * @var \Symfony\Component\HttpFoundation\Session\SessionInterface
+   */
+  protected $session;
+
+  /**
+   * Constructor.
+   *
+   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
+   *   The current user.
+   * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session
+   *   The session.
+   */
+  public function __construct(AccountProxyInterface $current_user, SessionInterface $session) {
+    $this->currentUser = $current_user;
+    $this->session = $session;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get($id = NULL) {
+    $id = $id ?: $this->getId();
+
+    return new Cart($id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function emptyCart($id = NULL) {
+    $this->get($id)->emptyCart();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getId($create = TRUE) {
+    if ($this->currentUser->isAuthenticated()) {
+      return $this->currentUser->id();
+    }
+    elseif (!$this->session->has('uc_cart_id') && $create) {
+      $this->session->set('uc_cart_id', md5(uniqid(rand(), TRUE)));
+    }
+
+    return $this->session->has('uc_cart_id') ? $this->session->get('uc_cart_id') : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function completeSale($order, $login = FALSE) {
+    // Empty that cart...
+    $this->emptyCart();
+
+    // Force the order to load from the DB instead of the entity cache.
+    // @todo Remove this once uc_payment_enter() can modify order objects?
+    // @todo Should we be overwriting $order with this newly-loaded db_order?
+    $db_order = \Drupal::entityManager()->getStorage('uc_order')->loadUnchanged($order->id());
+    $order->data = $db_order->data;
+
+    // Ensure that user creation and triggers are only run once.
+    if (empty($order->data->complete_sale)) {
+      $this->completeSaleAccount($order);
+
+      // Move an order's status from "In checkout" to "Pending".
+      if ($order->getStateId() == 'in_checkout') {
+        $order->setStatusId(uc_order_state_default('post_checkout'));
+      }
+
+      $order->save();
+
+      // Invoke the checkout complete trigger and hook.
+      $account = $order->getUser();
+      \Drupal::moduleHandler()->invokeAll('uc_checkout_complete', array($order, $account));
+      // rules_invoke_event('uc_checkout_complete', $order);
+    }
+
+    $type = $order->data->complete_sale;
+
+    // Log in new users, if requested.
+    if ($type == 'new_user' && $login && $this->currentUser->isAnonymous()) {
+      $type = 'new_user_logged_in';
+      user_login_finalize($order->getUser());
+    }
+
+    $message = \Drupal::config('uc_cart.messages')->get($type);
+    $message = \Drupal::token()->replace($message, array('uc_order' => $order));
+
+    $variables['!new_username'] = isset($order->data->new_user_name) ? $order->data->new_user_name : '';
+    $variables['!new_password'] = isset($order->password) ? $order->password : t('Your password');
+    $message = strtr($message, $variables);
+
+    return array(
+      '#theme' => 'uc_cart_complete_sale',
+      '#message' => Xss::filterAdmin($message),
+      '#order' => $order,
+    );
+  }
+
+  /**
+   * Link a completed sale to a user.
+   *
+   * @param \Drupal\uc_order\Entity\Order $order
+   *   The order entity that has just been completed.
+   */
+  protected function completeSaleAccount($order) {
+    // Order already has a user ID, so the user was logged in during checkout.
+    if ($order->getUserId()) {
+      $order->data->complete_sale = 'logged_in';
+      return;
+    }
+
+    // Email address matches an existing account.
+    if ($account = user_load_by_mail($order->getEmail())) {
+      $order->setUserId($account->id());
+      $order->data->complete_sale = 'existing_user';
+      return;
+    }
+
+    // Set up a new user.
+    $cart_config = \Drupal::config('uc_cart.settings');
+    $fields = array(
+      'name' => uc_store_email_to_username($order->getEmail()),
+      'mail' => $order->getEmail(),
+      'init' => $order->getEmail(),
+      'pass' => user_password(),
+      'roles' => array(),
+      'status' => $cart_config->get('new_customer_status_active') ? 1 : 0,
+    );
+
+    // Override the username, if specified.
+    if (isset($order->data->new_user_name)) {
+      $fields['name'] = $order->data->new_user_name;
+    }
+
+    // Create the account.
+    $account = User::create($fields);
+    $account->save();
+
+    // Override the password, if specified.
+    if (isset($order->data->new_user_hash)) {
+      db_query('UPDATE {users_field_data} SET pass = :hash WHERE uid = :uid', [':hash' => $order->data->new_user_hash, ':uid' => $account->id()]);
+      $account->password = t('Your password');
+    }
+    else {
+      $account->password = $fields['pass'];
+      $order->password = $fields['pass'];
+    }
+
+    // Send the customer their account details if enabled.
+    if ($cart_config->get('new_customer_email')) {
+      $type = $cart_config->get('new_customer_status_active') ? 'register_no_approval_required' : 'register_pending_approval';
+      \Drupal::service('plugin.manager.mail')->mail('user', $type, $order->getEmail(), uc_store_mail_recipient_langcode($order->getEmail()), array('account' => $account), uc_store_email_from());
+    }
+
+    $order->setUserId($account->id());
+    $order->data->new_user_name = $fields['name'];
+    $order->data->complete_sale =  'new_user';
+  }
+
+}
diff --git a/uc_cart/src/CartManagerInterface.php b/uc_cart/src/CartManagerInterface.php
new file mode 100644
index 0000000..24f8c8c
--- /dev/null
+++ b/uc_cart/src/CartManagerInterface.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\uc_cart\CartManagerInterface.
+ */
+
+namespace Drupal\uc_cart;
+
+/**
+ * Defines a common interface for cart managers.
+ */
+interface CartManagerInterface {
+
+  /**
+   * Loads a cart object.
+   *
+   * @param string|NULL $id
+   *   (optional) The ID of the cart to load, or NULL to load the current cart.
+   *
+   * @return CartInterface
+   *   An object representing the cart.
+   */
+  public function get($id = NULL);
+
+  /**
+   * Empties a cart.
+   *
+   * @param int $id
+   *   The ID of the cart to empty.
+   */
+  public function emptyCart($id);
+
+  /**
+   * Completes a sale, including adjusting order status and creating user account.
+   *
+   * @param \Drupal\uc_order\Entity\Order $order
+   *   The order entity that has just been completed.
+   * @param bool $login
+   *   Whether or not to login a new user when this function is called.
+   *
+   * @return
+   *   The HTML text of the default order completion page.
+   */
+  public function completeSale($order, $login = FALSE);
+
+}
diff --git a/uc_cart/src/Controller/Cart.php b/uc_cart/src/Controller/Cart.php
deleted file mode 100644
index abfd440..0000000
--- a/uc_cart/src/Controller/Cart.php
+++ /dev/null
@@ -1,397 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\uc_cart\Controller\Cart.
- */
-
-namespace Drupal\uc_cart\Controller;
-
-use Drupal\Core\Cache\Cache;
-use Drupal\Core\Controller\ControllerBase;
-use Drupal\Core\Url;
-use Drupal\Component\Utility\UrlHelper;
-use Drupal\Component\Utility\Xss;
-
-
-
-/**
- * Handles all things concerning Ubercart's shopping cart.
- *
- * The Ubercart cart system functions much like the e-commerce cart at its base
- * level... in fact, most carts do.  This module handles the cart display,
- * adding items to a cart, and checking out.  The module enables the cart,
- * products, and checkout to be extensible.
- */
-class Cart extends ControllerBase implements CartInterface {
-
-  /**
-   * Time in seconds after which a cart order is deemed abandoned.
-   */
-  const ORDER_TIMEOUT = 86400; // 24 hours
-
-  /**
-   * Time in seconds after which the checkout page is deemed abandoned.
-   */
-  const CHECKOUT_TIMEOUT = 1800; // 30 minutes
-
-
-  /**
-   * {@inheritdoc}
-   */
-  public function completeSale($order, $login = FALSE) {
-    // Empty that cart...
-    $this->emptyCart();
-
-    // Force the order to load from the DB instead of the entity cache.
-    // @todo Remove this once uc_payment_enter() can modify order objects?
-    // @todo Should we be overwriting $order with this newly-loaded db_order?
-    $db_order = $this->entityManager()->getStorage('uc_order')->loadUnchanged($order->id());
-    $order->data = $db_order->data;
-
-    // Ensure that user creation and triggers are only run once.
-    if (empty($order->data->complete_sale)) {
-      $this->completeSaleAccount($order);
-
-      // Move an order's status from "In checkout" to "Pending".
-      if ($order->getStateId() == 'in_checkout') {
-        $order->setStatusId(uc_order_state_default('post_checkout'));
-      }
-
-      $order->save();
-
-      // Invoke the checkout complete trigger and hook.
-      $account = $order->getUser();
-      $this->moduleHandler()->invokeAll('uc_checkout_complete', array($order, $account));
-      // rules_invoke_event('uc_checkout_complete', $order);
-    }
-
-    $type = $order->data->complete_sale;
-
-    // Log in new users, if requested.
-    if ($type == 'new_user' && $login && $this->currentUser()->isAnonymous()) {
-      $type = 'new_user_logged_in';
-      user_login_finalize($order->getUser());
-    }
-
-    $message = $this->config('uc_cart.messages')->get($type);
-    $message = \Drupal::token()->replace($message, array('uc_order' => $order));
-
-    $variables['!new_username'] = isset($order->data->new_user_name) ? $order->data->new_user_name : '';
-    $variables['!new_password'] = isset($order->password) ? $order->password : t('Your password');
-    $message = strtr($message, $variables);
-
-    return array(
-      '#theme' => 'uc_cart_complete_sale',
-      '#message' => Xss::filterAdmin($message),
-      '#order' => $order,
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function completeSaleAccount($order) {
-    // Order already has a user ID, so the user was logged in during checkout.
-    if ($order->getUserId()) {
-      $order->data->complete_sale = 'logged_in';
-      return;
-    }
-
-    // Email address matches an existing account.
-    if ($account = user_load_by_mail($order->getEmail())) {
-      $order->setUserId($account->id());
-      $order->data->complete_sale = 'existing_user';
-      return;
-    }
-
-    // Set up a new user.
-    $cart_config = $this->config('uc_cart.settings');
-    $fields = array(
-      'name' => uc_store_email_to_username($order->getEmail()),
-      'mail' => $order->getEmail(),
-      'init' => $order->getEmail(),
-      'pass' => user_password(),
-      'roles' => array(),
-      'status' => $cart_config->get('new_customer_status_active') ? 1 : 0,
-    );
-
-    // Override the username, if specified.
-    if (isset($order->data->new_user_name)) {
-      $fields['name'] = $order->data->new_user_name;
-    }
-
-    // Create the account.
-    $account = \Drupal\user\Entity\User::create($fields);
-    $account->save();
-
-    // Override the password, if specified.
-    if (isset($order->data->new_user_hash)) {
-      db_query('UPDATE {users_field_data} SET pass = :hash WHERE uid = :uid', [':hash' => $order->data->new_user_hash, ':uid' => $account->id()]);
-      $account->password = t('Your password');
-    }
-    else {
-      $account->password = $fields['pass'];
-      $order->password = $fields['pass'];
-    }
-
-    // Send the customer their account details if enabled.
-    if ($cart_config->get('new_customer_email')) {
-      $type = $cart_config->get('new_customer_status_active') ? 'register_no_approval_required' : 'register_pending_approval';
-      \Drupal::service('plugin.manager.mail')->mail('user', $type, $order->getEmail(), uc_store_mail_recipient_langcode($order->getEmail()), array('account' => $account), uc_store_email_from());
-    }
-
-    $order->setUserId($account->id());
-    $order->data->new_user_name = $fields['name'];
-    $order->data->complete_sale =  'new_user';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getId($create = TRUE) {
-    $user = $this->currentUser();
-    $session = \Drupal::service('session');
-
-    if ($user->isAuthenticated()) {
-      return $user->id();
-    }
-    elseif (!$session->has('uc_cart_id') && $create) {
-      $session->set('uc_cart_id', md5(uniqid(rand(), TRUE)));
-    }
-
-    return $session->has('uc_cart_id') ? $session->get('uc_cart_id') : FALSE;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getContents($cid = NULL, $action = NULL) {
-    static $items = array();
-
-    $cid = $cid ? $cid : $this->getId(FALSE);
-
-    // If we didn't get a cid, return empty.
-    if (!$cid) {
-      return array();
-    }
-
-    if (!isset($items[$cid]) || $action == 'rebuild') {
-      $items[$cid] = array();
-
-      $result = \Drupal::entityQuery('uc_cart_item')
-        ->condition('cart_id', $cid)
-        ->sort('cart_item_id', 'ASC')
-        ->execute();
-
-      if (!empty($result)) {
-        $storage = $this->entityManager()->getStorage('uc_cart_item');
-        $storage->resetCache(array_keys($result));
-        $items[$cid] = $storage->loadMultiple(array_keys($result));
-      }
-
-      // Allow other modules a chance to alter the fully loaded cart object.
-      $this->moduleHandler()->alter('uc_cart', $items[$cid]);
-
-      if ($action == 'rebuild') {
-        // Mark the current cart order (if any) as needing to be rebuilt.  We only
-        // do this if the cart is being explicitly rebuilt (i.e. after an item is
-        // added, removed or altered).
-        $session = \Drupal::service('session');
-        $session->set('uc_cart_order_rebuild', TRUE);
-
-        // When there are no longer any items in the cart, the anonymous cart ID is
-        // no longer required. To guard against unsetting the session ID in the
-        // middle of an uc_cart_add_item() call, we only do this on rebuild.
-        // See issue 858816 for details.
-        if (empty($items[$cid]) && $session->has('uc_cart_id') && $session->get('uc_cart_id') == $cid) {
-          $session->remove('uc_cart_id');
-        }
-      }
-    }
-
-    return $items[$cid];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function addItem($nid, $qty = 1, $data = NULL, $cid = NULL, $msg = TRUE, $check_redirect = TRUE, $rebuild = TRUE) {
-    $cid = $cid ? $cid : $this->getId();
-    $node = node_load($nid);
-
-    if (is_null($data) || !isset($data['module'])) {
-      $data['module'] = 'uc_product';
-    }
-
-    // Invoke hook_uc_add_to_cart() to give other modules a chance to affect the process.
-    $result = $this->moduleHandler()->invokeAll('uc_add_to_cart', array($nid, $qty, $data));
-    if (is_array($result) && !empty($result)) {
-      foreach ($result as $row) {
-        if ($row['success'] === FALSE) {
-          // Module implementing the hook does NOT want this item added!
-          if (isset($row['message']) && !empty($row['message'])) {
-            $message = $row['message'];
-          }
-          else {
-            $message = t('Sorry, that item is not available for purchase at this time.');
-          }
-          if (isset($row['silent']) && ($row['silent'] === TRUE)) {
-            if ($check_redirect) {
-              return $this->getAddItemRedirect();
-            }
-          }
-          else {
-            drupal_set_message($message, 'error');
-          }
-          // Stay on this page.
-          $query = \Drupal::request()->query;
-          return Url::fromRoute('<current>', [], ['query' => UrlHelper::filterQueryParameters($query->all())]);
-        }
-      }
-    }
-
-    // Now we can go ahead and add the item because either:
-    //   1) No modules implemented hook_uc_add_to_cart(), or
-    //   2) All modules implementing that hook want this item added.
-    $result = \Drupal::entityQuery('uc_cart_item')
-      ->condition('cart_id', $cid)
-      ->condition('nid', $nid)
-      ->condition('data', serialize($data))
-      ->execute();
-
-    if (empty($result)) {
-      // If the item isn't in the cart yet, add it.
-      $item_entity = \Drupal\uc_cart\Entity\CartItem::create(array(
-        'cart_id' => $cid,
-        'nid' => $nid,
-        'qty' => $qty,
-        'data' => $data,
-      ));
-      $item_entity->save();
-      if ($msg) {
-        drupal_set_message(t('<strong>@product-title</strong> added to <a href="@url">your shopping cart</a>.', ['@product-title' => $node->label(), '@url' => $this->url('uc_cart.cart')]));
-      }
-    }
-    else {
-      // If it is in the cart, update the item instead.
-      if ($msg) {
-        drupal_set_message(t('Your item(s) have been updated.'));
-      }
-      $item_entity = \Drupal\uc_cart\Entity\CartItem::load(current(array_keys($result)));
-      $qty += $item_entity->qty->value;
-      $this->moduleHandler()->invoke($data['module'], 'uc_update_cart_item', array($nid, $data, min($qty, 999999), $cid));
-    }
-
-    // Invalidate the cache.
-    Cache::invalidateTags(['uc_cart:' . $cid]);
-
-    // If specified, rebuild the cached cart items array.
-    if ($rebuild) {
-      $this->getContents($cid, 'rebuild');
-    }
-
-    // If specified, compute a Url to redirect to.
-    if ($check_redirect) {
-      return $this->getAddItemRedirect();
-    }
-    else {
-      // Just to be clear about it...
-      return NULL;
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getAddItemRedirect() {
-    // Check for destination= query string
-    $query = \Drupal::request()->query;
-    $destination = $query->get('destination');
-    if (!empty($destination)) {
-      return Url::fromUri('base:' . $destination);
-    }
-
-    // Save current Url to session before redirecting
-    // so we can go "back" here from the cart.
-    $session = \Drupal::service('session');
-    $session->set('uc_cart_last_url', Url::fromRoute('<current>')->toString());
-    $redirect = $this->config('uc_cart.settings')->get('add_item_redirect');
-    if ($redirect != '<none>') {
-      return Url::fromUri('base:' . $redirect);
-    }
-    else {
-      return Url::fromRoute('<current>', [], ['query' => UrlHelper::filterQueryParameters($query->all())]);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function emptyCart($cart_id = NULL) {
-    $cart_id = $cart_id ? $cart_id : $this->getId(FALSE);
-
-    if (!$cart_id) {
-      return;
-    }
-
-    $result = \Drupal::entityQuery('uc_cart_item')
-      ->condition('cart_id', $cart_id)
-      ->execute();
-
-    if (!empty($result)) {
-      $storage = $this->entityManager()->getStorage('uc_cart_item');
-      $entities = $storage->loadMultiple(array_keys($result));
-      $storage->delete($entities);
-    }
-
-    // Remove cached cart.
-    $this->getContents($cart_id, 'rebuild');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function isShippable($cart_id = NULL) {
-    $items = $this->getContents($cart_id);
-
-    foreach ($items as $item) {
-      if (uc_order_product_is_shippable($item)) {
-        return TRUE;
-      }
-    }
-
-    return FALSE;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getCacheContexts() {
-    // Carts vary by session.
-    return ['session'];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getCacheTags() {
-    $tags = [];
-
-    // If a cart ID is available, use it as the cache tag.
-    if ($cid = $this->getId(FALSE)) {
-      $tags[] = 'uc_cart:' . $cid;
-    }
-
-    return $tags;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getCacheMaxAge() {
-    // Carts can be cached forever, the tag will be used to invalidate.
-    return Cache::PERMANENT;
-  }
-
-}
diff --git a/uc_cart/src/Controller/CartController.php b/uc_cart/src/Controller/CartController.php
index 4b21cd4..f4a90f9 100644
--- a/uc_cart/src/Controller/CartController.php
+++ b/uc_cart/src/Controller/CartController.php
@@ -8,6 +8,8 @@
 namespace Drupal\uc_cart\Controller;
 
 use Drupal\Core\Controller\ControllerBase;
+use Drupal\uc_cart\CartManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Controller routines for the shopping cart.
@@ -15,6 +17,32 @@ use Drupal\Core\Controller\ControllerBase;
 class CartController extends ControllerBase {
 
   /**
+   * The cart manager.
+   *
+   * @var \Drupal\uc_cart\CartManager
+   */
+  protected $cartManager;
+
+  /**
+   * Constructs a CartController.
+   *
+   * @param \Drupal\uc_cart\CartManagerInterface $cart_manager
+   *   The cart manager.
+   */
+  public function __construct(CartManagerInterface $cart_manager) {
+    $this->cartManager = $cart_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('uc_cart.manager')
+    );
+  }
+
+  /**
    * Displays the cart view page.
    *
    * Show the products in the cart with a form to adjust cart contents or go to
@@ -22,7 +50,7 @@ class CartController extends ControllerBase {
    */
   public function listing() {
     // Load the array of shopping cart items.
-    $cart = Cart::create(\Drupal::getContainer());
+    $cart = $this->cartManager->get();
     $items = $cart->getContents();
 
     // Display the empty cart page if there are no items in the cart.
diff --git a/uc_cart/src/Controller/CartInterface.php b/uc_cart/src/Controller/CartInterface.php
deleted file mode 100644
index d659af3..0000000
--- a/uc_cart/src/Controller/CartInterface.php
+++ /dev/null
@@ -1,121 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\uc_cart\Controller\CartInterface.
- */
-
-namespace Drupal\uc_cart\Controller;
-
-use Drupal\Core\Cache\CacheableDependencyInterface;
-
-/**
- * Handles all things concerning Ubercart's shopping cart.
- *
- * The Ubercart cart system functions much like the e-commerce cart at its base
- * level... in fact, most carts do.  This module handles the cart display,
- * adding items to a cart, and checking out.  The module enables the cart,
- * products, and checkout to be extensible.
- */
-interface CartInterface extends CacheableDependencyInterface {
-
-  /**
-   * Completes a sale, including adjusting order status and creating user account.
-   *
-   * @param \Drupal\uc_order\Entity\Order $order
-   *   The order entity that has just been completed.
-   * @param bool $login
-   *   Whether or not to login a new user when this function is called.
-   *
-   * @return
-   *   The HTML text of the default order completion page.
-   */
-  public function completeSale($order, $login);
-
-  /**
-   * Link a completed sale to a user.
-   *
-   * @param \Drupal\uc_order\Entity\Order $order
-   *   The order entity that has just been completed.
-   */
-  public function completeSaleAccount($order);
-
-  /**
-   * Returns the unique cart ID for the current user.
-   *
-   * @param bool $create
-   *   If TRUE, a cart ID will be generated if none is set.
-   *
-   * @return
-   *   The cart ID. If $create is FALSE, returns FALSE if no cart exists.
-   */
-  public function getId($create = TRUE);
-
-  /**
-   * Grabs the items in a shopping cart for a user.
-   *
-   * @param $cid
-   *   (optional) The cart ID to load, or NULL to load the current user's cart.
-   * @param $action
-   *   (optional) Carts are statically cached by default. If set to "rebuild",
-   *   the cache will be ignored and the cart reloaded from the database.
-   *
-   * @return
-   *   An array of cart items.
-   */
-  public function getContents($cid, $action);
-
-  /**
-   * Adds an item to a user's cart.
-   *
-   * @param int $nid
-   *   Node ID to add to cart.
-   * @param int $qty
-   *   Quantity to add to cart.
-   * @param array $data
-   *   Array of module-specific data to add to cart.
-   * @param int $cid
-   *   ID of user's cart.
-   * @param string $msg
-   *   Message to display upon adding an item to the cart.
-   * @param bool $check_redirect
-   *   TRUE to return a redirect URL.
-   * @param bool $rebuild
-   *   TRUE to rebuild the cart item cache after adding an item.
-   *
-   * @return null|\Drupal\Core\Url
-   *   If $check_redirect is TRUE, a Url to redirect to. Otherwise null.
-   */
-  public function addItem($nid, $qty, $data, $cid, $msg, $check_redirect, $rebuild);
-
-  /**
-   * Computes the destination Url for an add-to-cart action.
-   *
-   * Redirect Url is chosen in the following order:
-   *  - Query parameter "destination"
-   *  - Cart config variable "uc_cart.settings.add_item_redirect"
-   *
-   * @return \Drupal\Core\Url
-   *   A Url destination for redirection.
-   */
-  public function getAddItemRedirect();
-
-  /**
-   * Empties a cart of its contents.
-   *
-   * @param $cart_id
-   *   The ID of the cart, or NULL to empty the current cart.
-   */
-  public function emptyCart($cart_id);
-
-  /**
-   * Determines whether a cart contains shippable items or not.
-   *
-   * @param integer $cart_id
-   *   The ID of the cart.
-   *
-   * @return bool
-   */
-  public function isShippable($cart_id);
-
-}
diff --git a/uc_cart/src/Controller/CheckoutController.php b/uc_cart/src/Controller/CheckoutController.php
index b46e3f1..9f021c0 100644
--- a/uc_cart/src/Controller/CheckoutController.php
+++ b/uc_cart/src/Controller/CheckoutController.php
@@ -9,6 +9,8 @@ namespace Drupal\uc_cart\Controller;
 
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\uc_cart\CartInterface;
+use Drupal\uc_cart\CartManagerInterface;
 use Drupal\uc_cart\Plugin\CheckoutPaneManager;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -26,21 +28,23 @@ class CheckoutController extends ControllerBase implements ContainerInjectionInt
   protected $checkoutPaneManager;
 
   /**
-   * The cart controller.
+   * The cart manager.
    *
-   * @var \Drupal\uc_cart\Controller\Cart
+   * @var \Drupal\uc_cart\CartManager
    */
-  protected $cart;
+  protected $cartManager;
 
   /**
    * Constructs a CheckoutController.
    *
    * @param \Drupal\uc_cart\Plugin\CheckoutPaneManager $checkout_pane_manager
    *   The checkout pane plugin manager.
+   * @param \Drupal\uc_cart\CartManagerInterface $cart_manager
+   *   The cart manager.
    */
-  public function __construct(CheckoutPaneManager $checkout_pane_manager, CartInterface $cart) {
+  public function __construct(CheckoutPaneManager $checkout_pane_manager, CartManagerInterface $cart_manager) {
     $this->checkoutPaneManager = $checkout_pane_manager;
-    $this->cart = $cart;
+    $this->cartManager = $cart_manager;
   }
 
   /**
@@ -49,7 +53,7 @@ class CheckoutController extends ControllerBase implements ContainerInjectionInt
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('plugin.manager.uc_cart.checkout_pane'),
-      Cart::create($container)
+      $container->get('uc_cart.manager')
     );
   }
 
@@ -59,7 +63,7 @@ class CheckoutController extends ControllerBase implements ContainerInjectionInt
   public function checkout() {
     $cart_config = $this->config('uc_cart.settings');
 
-    $items = $this->cart->getContents();
+    $items = $this->cartManager->get()->getContents();
     if (count($items) == 0 || !$cart_config->get('checkout_enabled')) {
       return $this->redirect('uc_cart.cart');
     }
@@ -82,8 +86,8 @@ class CheckoutController extends ControllerBase implements ContainerInjectionInt
         // there has been no activity for 10 minutes (to prevent identity theft).
         if ($order->getStateId() != 'in_checkout' ||
             ($this->currentUser()->isAuthenticated() && $this->currentUser()->id() != $order->getUserId()) ||
-            $order->modified->value < REQUEST_TIME - Cart::CHECKOUT_TIMEOUT) {
-          if ($order->getStateId() == 'in_checkout' && $order->modified->value < REQUEST_TIME - Cart::CHECKOUT_TIMEOUT) {
+            $order->modified->value < REQUEST_TIME - CartInterface::CHECKOUT_TIMEOUT) {
+          if ($order->getStateId() == 'in_checkout' && $order->modified->value < REQUEST_TIME - CartInterface::CHECKOUT_TIMEOUT) {
             // Mark expired orders as abandoned.
             $order->setStatusId('abandoned')->save();
           }
@@ -230,7 +234,7 @@ class CheckoutController extends ControllerBase implements ContainerInjectionInt
     }
 
     $cart_config = $this->config('uc_cart.settings');
-    $build = $this->cart->completeSale($order, $cart_config->get('new_customer_login'));
+    $build = $this->cartManager->completeSale($order, $cart_config->get('new_customer_login'));
     $session->remove('cart_order');
     unset($_SESSION['uc_checkout'][$order->id()]);
 
diff --git a/uc_cart/src/Form/CartForm.php b/uc_cart/src/Form/CartForm.php
index 43fa391..c970972 100644
--- a/uc_cart/src/Form/CartForm.php
+++ b/uc_cart/src/Form/CartForm.php
@@ -12,7 +12,7 @@ use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Url;
-use Drupal\uc_cart\Controller\CartInterface;
+use Drupal\uc_cart\CartInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
diff --git a/uc_cart/src/Form/EmptyCartForm.php b/uc_cart/src/Form/EmptyCartForm.php
index 6eb7bd8..cd3ea5b 100644
--- a/uc_cart/src/Form/EmptyCartForm.php
+++ b/uc_cart/src/Form/EmptyCartForm.php
@@ -10,7 +10,6 @@ namespace Drupal\uc_cart\Form;
 use Drupal\Core\Form\ConfirmFormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
-use Drupal\uc_cart\Controller\Cart;
 
 /**
  * Confirm that the customer wants to empty their cart.
@@ -42,8 +41,7 @@ class EmptyCartForm extends ConfirmFormBase {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $cart = Cart::create(\Drupal::getContainer());
-    $cart->emptyCart();
+    \Drupal::service('uc_cart.manager')->emptyCart();
     $form_state->setRedirect('uc_cart.cart');
   }
 
diff --git a/uc_cart/src/Plugin/Block/CartBlock.php b/uc_cart/src/Plugin/Block/CartBlock.php
index d2b7557..d5fbce4 100644
--- a/uc_cart/src/Plugin/Block/CartBlock.php
+++ b/uc_cart/src/Plugin/Block/CartBlock.php
@@ -8,11 +8,9 @@
 namespace Drupal\uc_cart\Plugin\Block;
 
 use Drupal\Core\Block\BlockBase;
-use Drupal\Core\Cache\Cache;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Url;
-use Drupal\uc_cart\Controller\Cart;
 
 /**
  * Provides the shopping cart block.
@@ -92,7 +90,7 @@ class CartBlock extends BlockBase {
    * {@inheritdoc}
    */
   public function build() {
-    $cart = Cart::create(\Drupal::getContainer());
+    $cart = \Drupal::service('uc_cart.manager')->get();
     $product_count = count($cart->getContents());
 
     // Display nothing if the block is set to hide on empty and there are no
diff --git a/uc_cart/src/Tests/CartCheckoutTest.php b/uc_cart/src/Tests/CartCheckoutTest.php
index 9c3594b..34eca82 100644
--- a/uc_cart/src/Tests/CartCheckoutTest.php
+++ b/uc_cart/src/Tests/CartCheckoutTest.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\uc_cart\Tests;
 
-use Drupal\uc_cart\Controller\Cart;
+use Drupal\uc_cart\CartInterface;
 use Drupal\uc_store\Tests\UbercartTestBase;
 
 /**
@@ -22,7 +22,18 @@ class CartCheckoutTest extends UbercartTestBase {
   /** Authenticated but unprivileged user. */
   protected $customer;
 
-  /** Instance of the Cart controller. */
+  /**
+   * The cart manager.
+   *
+   * @var \Drupal\uc_cart\CartManagerInterface
+   */
+  protected $cartManager;
+
+  /**
+   * The test user's cart.
+   *
+   * @var CartInterface
+   */
   protected $cart;
 
 
@@ -30,7 +41,8 @@ class CartCheckoutTest extends UbercartTestBase {
     parent::setUp();
 
     // Create a simple customer user account.
-    $this->cart = Cart::create(\Drupal::getContainer());
+    $this->cartManager = \Drupal::service('uc_cart.manager');
+    $this->cart = $this->cartManager->get();
 
     // Create a simple customer user account.
     $this->customer = $this->drupalCreateUser();
@@ -85,8 +97,6 @@ class CartCheckoutTest extends UbercartTestBase {
     foreach ($items as $item) {
       $item->delete();
     }
-    // @TODO: remove the need for this
-    $this->cart->getContents(NULL, 'rebuild');
 
     $items = $this->cart->getContents();
     $this->assertEqual(count($items), 0, 'Cart is empty after removal.');
@@ -387,7 +397,7 @@ class CartCheckoutTest extends UbercartTestBase {
     $order_data = array('primary_email' => 'simpletest@ubercart.org');
     $order = $this->createOrder($order_data);
     uc_payment_enter($order->id(), 'SimpleTest', $order->getTotal());
-    $output = $this->cart->completeSale($order);
+    $output = $this->cartManager->completeSale($order);
 
     // Check that a new account was created.
     $this->assertTrue(strpos($output['#message'], 'new account has been created') !== FALSE, 'Checkout message mentions new account.');
@@ -406,7 +416,7 @@ class CartCheckoutTest extends UbercartTestBase {
     // Different user, sees the checkout page first.
     $order_data = array('primary_email' => 'simpletest2@ubercart.org');
     $order = $this->createOrder($order_data);
-    $output = $this->cart->completeSale($order, TRUE);
+    $output = $this->cartManager->completeSale($order, TRUE);
     uc_payment_enter($order->id(), 'SimpleTest', $order->getTotal());
 
     // 3 e-mails: new account, customer invoice, admin invoice
@@ -422,7 +432,7 @@ class CartCheckoutTest extends UbercartTestBase {
 
     // Same user, new order.
     $order = $this->createOrder($order_data);
-    $output = $this->cart->completeSale($order, TRUE);
+    $output = $this->cartManager->completeSale($order, TRUE);
     uc_payment_enter($order->id(), 'SimpleTest', $order->getTotal());
 
     // Check that no new account was created.
@@ -471,7 +481,7 @@ class CartCheckoutTest extends UbercartTestBase {
       // @todo: Can we set modified through the Entity API rather than DBTNG?
       db_update('uc_orders')
         ->fields(array(
-            'modified' => time() - Cart::ORDER_TIMEOUT - 1,
+            'modified' => time() - CartInterface::ORDER_TIMEOUT - 1,
           ))
         ->condition('order_id', $order_id)
         ->execute();
diff --git a/uc_cart/uc_cart.module b/uc_cart/uc_cart.module
index d5f01fb..abe5b1d 100644
--- a/uc_cart/uc_cart.module
+++ b/uc_cart/uc_cart.module
@@ -12,20 +12,20 @@
 
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Render\Element;
-use Drupal\uc_cart\Controller\Cart;
+use Drupal\uc_cart\CartInterface;
 
 /**
  * Implements hook_cron().
  */
 function uc_cart_cron() {
+  $cart_manager = \Drupal::service('uc_cart.manager');
   $cart_config = \Drupal::config('uc_cart.settings');
   // Empty anonymous carts.
   $time = strtotime($cart_config->get('anon_duration') . ' ' . $cart_config->get('anon_unit') . ' ago');
   $result = db_query('SELECT DISTINCT cart_id FROM {uc_cart_products} WHERE changed <= :changed', [':changed' => $time]);
-  $cart = Cart::create(\Drupal::getContainer());
   foreach ($result as $row) {
     if (strlen($row->cart_id) >= 22) {
-      $cart->emptyCart($row->cart_id);
+      $cart_manager->emptyCart($row->cart_id);
     }
   }
 
@@ -34,13 +34,13 @@ function uc_cart_cron() {
   $result = db_query('SELECT DISTINCT cart_id FROM {uc_cart_products} WHERE changed <= :changed', [':changed' => $time]);
   foreach ($result as $row) {
     if (strlen($row->cart_id) < 22) {
-      $cart->emptyCart($row->cart_id);
+      $cart_manager->emptyCart($row->cart_id);
     }
   }
 
   // Update status of abandoned orders.
   $result = db_query('SELECT order_id FROM {uc_orders} WHERE order_status = :status AND modified < :time',
-    [':status' => 'in_checkout', ':time' => REQUEST_TIME - Cart::ORDER_TIMEOUT])->fetchCol();
+    [':status' => 'in_checkout', ':time' => REQUEST_TIME - CartInterface::ORDER_TIMEOUT])->fetchCol();
   foreach ($result as $order_id) {
     \Drupal\uc_order\Entity\Order::load($order_id)
       ->setStatusId('abandoned')
@@ -161,7 +161,7 @@ function template_preprocess_uc_cart_checkout_buttons(&$variables) {
  * Preprocesses the cart block output to include the icon.
  */
 function uc_cart_preprocess_block(&$variables) {
-  $cart = Cart::create(\Drupal::getContainer());
+  $cart = \Drupal::service('uc_cart.manager')->get();
   if ($variables['plugin_id'] == 'uc_cart' && $variables['label']) {
     $variables['label'] = array(
       '#theme' => 'uc_cart_block_title',
@@ -202,17 +202,18 @@ function uc_cart_user_login($account) {
     // No anonymous cart, so nothing to do here.
     return;
   }
-  $cart_id = $session->get('uc_cart_id');
 
   // If there are items in the anonymous cart, consolidate them.
-  $cart = Cart::create(\Drupal::getContainer());
-  if ($items = $cart->getContents($cart_id)) {
+  $cart_manager = \Drupal::service('uc_cart.manager');
+  $anonymous_cart = $cart_manager->get($session->get('uc_cart_id'));
+  if ($items = $anonymous_cart->getContents()) {
     // Remove the anonymous cart items.
-    $cart->emptyCart($cart_id);
+    $anonymous_cart->emptyCart();
 
     // Merge the anonymous items into the user cart.
+    $user_cart = $cart_manager->get($account->id());
     foreach ($items as $item) {
-      $cart->addItem($item->nid->target_id, $item->qty->value, $item->data->first()->toArray(), $account->id(), FALSE, FALSE, FALSE);
+      $user_cart->addItem($item->nid->target_id, $item->qty->value, $item->data->first()->toArray(), FALSE);
     }
 
     // Unset the anonymous cart ID, it's no longer needed.
diff --git a/uc_cart/uc_cart.services.yml b/uc_cart/uc_cart.services.yml
index bb2a36f..d4079be 100644
--- a/uc_cart/uc_cart.services.yml
+++ b/uc_cart/uc_cart.services.yml
@@ -6,3 +6,6 @@ services:
     class: Drupal\uc_cart\CartBreadcrumbBuilder
     tags:
       - { name: breadcrumb_builder, priority: 100 }
+  uc_cart.manager:
+    class: Drupal\uc_cart\CartManager
+    arguments: ['@current_user', '@session']
diff --git a/uc_cart_links/src/Form/CartLinksForm.php b/uc_cart_links/src/Form/CartLinksForm.php
index aead03a..ba72221 100644
--- a/uc_cart_links/src/Form/CartLinksForm.php
+++ b/uc_cart_links/src/Form/CartLinksForm.php
@@ -13,7 +13,6 @@ use Drupal\Core\Form\ConfirmFormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
 use Symfony\Component\HttpFoundation\RedirectResponse;
-use Drupal\uc_cart\Controller\Cart;
 
 /**
  * Preprocesses a cart link, confirming with the user for destructive actions.
@@ -80,7 +79,7 @@ class CartLinksForm extends ConfirmFormBase {
     }
 
     // Confirm with the user if the form contains a destructive action.
-    $cart = Cart::create(\Drupal::getContainer());
+    $cart = \Drupal::service('uc_cart.manager')->get();
     $items = $cart->getContents();
     if ($cart_links_config->get('empty') && !empty($items)) {
       $actions = explode('-', urldecode($this->actions));
@@ -105,11 +104,10 @@ class CartLinksForm extends ConfirmFormBase {
     $cart_links_config = $this->config('uc_cart_links.settings');
 
     $actions = explode('-', urldecode($this->actions));
-    $rebuild_cart = FALSE;
     $messages = array();
     $id = $this->t('(not specified)');
 
-    $cart = Cart::create(\Drupal::getContainer());
+    $cart = \Drupal::service('uc_cart.manager')->get();
     foreach ($actions as $action) {
       switch (Unicode::substr($action, 0, 1)) {
         // Set the ID of the Cart Link.
@@ -186,8 +184,7 @@ class CartLinksForm extends ConfirmFormBase {
                   );
                 }
               }
-              $cart->addItem($p['nid'], $p['qty'], $p['data'] + \Drupal::moduleHandler()->invokeAll('uc_add_to_cart_data', array($p)), NULL, $msg, FALSE, FALSE);
-              $rebuild_cart = TRUE;
+              $cart->addItem($p['nid'], $p['qty'], $p['data'] + \Drupal::moduleHandler()->invokeAll('uc_add_to_cart_data', array($p)), $msg);
             }
             else {
               $this->logger('uc_cart_link')->error('Cart Link on %url tried to add an unpublished product to the cart.', array('%url' => $this->getRequest()->server->get('HTTP_REFERER')));
@@ -222,11 +219,6 @@ class CartLinksForm extends ConfirmFormBase {
           }
           break;
       }
-
-      // Rebuild the cart cache if necessary.
-      if ($rebuild_cart) {
-        $cart->getContents(NULL, 'rebuild');
-      }
     }
 
     if ($cart_links_config->get('track')) {
diff --git a/uc_product/src/Form/BuyItNowForm.php b/uc_product/src/Form/BuyItNowForm.php
index 56044a0..1b73e4d 100644
--- a/uc_product/src/Form/BuyItNowForm.php
+++ b/uc_product/src/Form/BuyItNowForm.php
@@ -11,7 +11,6 @@ use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
 use Drupal\node\NodeInterface;
-use Drupal\uc_cart\Controller\Cart;
 
 /**
  * Defines a simple form for adding a product to the cart.
@@ -61,8 +60,8 @@ class BuyItNowForm extends FormBase {
     if (!$form_state->getRedirect()) {
       $data = \Drupal::moduleHandler()->invokeAll('uc_add_to_cart_data', array($form_state->getValues()));
       $msg = $this->config('uc_cart.settings')->get('add_item_msg');
-      $cart = Cart::create(\Drupal::getContainer());
-      $redirect = $cart->addItem($form_state->getValue('nid'), $form_state->getValue('qty'), $data, NULL, $msg);
+      $cart = \Drupal::service('uc_cart.manager')->get();
+      $redirect = $cart->addItem($form_state->getValue('nid'), $form_state->getValue('qty'), $data, $msg);
       if (isset($redirect)) {
         $form_state->setRedirectUrl($redirect);
       }
diff --git a/uc_product/uc_product.module b/uc_product/uc_product.module
index 1b41ea1..64ffb32 100644
--- a/uc_product/uc_product.module
+++ b/uc_product/uc_product.module
@@ -617,12 +617,10 @@ function uc_product_uc_cart_display($item) {
  * Implements hook_uc_update_cart_item().
  */
 function uc_product_uc_update_cart_item($nid, $data = array(), $qty, $cid = NULL) {
-  if (!$nid) return NULL;
-  $cart = \Drupal\uc_cart\Controller\Cart::create(\Drupal::getContainer());
-  $cid = !(is_null($cid) || empty($cid)) ? $cid : $cart->getId();
+  $cart = \Drupal::service('uc_cart.manager')->get($cid);
 
   $result = \Drupal::entityQuery('uc_cart_item')
-    ->condition('cart_id', $cid)
+    ->condition('cart_id', $cart->getId())
     ->condition('nid', $nid)
     ->condition('data', serialize($data))
     ->execute();
diff --git a/uc_product_kit/uc_product_kit.module b/uc_product_kit/uc_product_kit.module
index 83b63ce..e07de97 100644
--- a/uc_product_kit/uc_product_kit.module
+++ b/uc_product_kit/uc_product_kit.module
@@ -734,7 +734,7 @@ function uc_product_kit_add_to_cart_form_submit($form, FormStateInterface $form_
     $node = node_load($form_state->getValue('nid'));
     drupal_set_message(t('<strong>!product-title</strong> added to <a href=":url">your shopping cart</a>.', ['!product-title' => $node->label(), ':url' => \Drupal::url('uc_cart.cart')]));
   }
-  $cart = \Drupal\uc_cart\Controller\Cart::create(\Drupal::getContainer());
+  $cart = \Drupal::service('uc_cart.manager')->get();
   $redirect = $cart->addItem($form_state->getValue('nid'), $form_state->getValue('qty'), $form_state->getValues());
   if (isset($redirect)) {
     $form_state->setRedirectUrl($redirect);
@@ -787,8 +787,8 @@ function uc_product_kit_buy_it_now_form_submit($form, FormStateInterface $form_s
     }
   }
   $msg = \Drupal::config('uc_cart.settings')->get('add_item_msg');
-  $cart = \Drupal\uc_cart\Controller\Cart::create(\Drupal::getContainer());
-  $path = $cart->addItem($form_state->getValue('nid'), 1, $form_state->getValues(), NULL, $msg);
+  $cart = \Drupal::service('uc_cart.manager')->get();
+  $redirect = $cart->addItem($form_state->getValue('nid'), 1, $form_state->getValues(), $msg);
   if (isset($redirect)) {
     $form_state->setRedirectUrl($redirect);
   }
@@ -800,8 +800,7 @@ function uc_product_kit_buy_it_now_form_submit($form, FormStateInterface $form_s
 function uc_product_kit_uc_add_to_cart($nid, $qty, $kit_data) {
   $node = node_load($nid);
   if ($node->getType() == 'product_kit') {
-    $cart_manager = \Drupal\uc_cart\Controller\Cart::create(\Drupal::getContainer());
-    $cart = $cart_manager->getContents();
+    $cart = \Drupal::service('uc_cart.manager')->get();
     $unique = uniqid('', TRUE);
     $update = array();
     $product_data = array();
@@ -809,7 +808,7 @@ function uc_product_kit_uc_add_to_cart($nid, $qty, $kit_data) {
     foreach ($node->products as $product) {
       $data = array('kit_id' => $node->id(), 'module' => 'uc_product_kit') + \Drupal::moduleHandler()->invokeAll('uc_add_to_cart_data', array($kit_data['products'][$product->id()]));
       $product_data[$product->id()] = $data;
-      foreach ($cart as $item) {
+      foreach ($cart->getContents() as $item) {
         if ($item->nid == $product->id() && isset($item->data['kit_id']) && $item->data['kit_id'] == $node->id()) {
           // There is something in the cart like the product kit. Update
           // by default, but check that it's possible.
@@ -830,19 +829,16 @@ function uc_product_kit_uc_add_to_cart($nid, $qty, $kit_data) {
       foreach ($node->products as $product) {
         $data = $product_data[$product->id()];
         $data['unique_id'] = $unique;
-        $cart_manager->addItem($product->id(), $product->qty * $qty, $data, NULL, FALSE, FALSE, FALSE);
+        $cart->addItem($product->id(), $product->qty * $qty, $data, FALSE);
       }
     }
     else {
       foreach ($node->products as $product) {
         $data = $product_data[$product->id()];
-        $cart_manager->addItem($product->id(), $product->qty * $qty, $data, NULL, FALSE, FALSE, FALSE);
+        $cart->addItem($product->id(), $product->qty * $qty, $data, FALSE);
       }
     }
 
-    // Rebuild the cart items cache.
-    $cart_manager->getContents(NULL, 'rebuild');
-
     return array(array('success' => FALSE, 'silent' => TRUE, 'message' => ''));
   }
 }
@@ -1005,10 +1001,6 @@ function uc_product_kit_uc_cart_display($item) {
  * Handles individual products or entire kits.
  */
 function uc_product_kit_uc_update_cart_item($nid, $data = array(), $qty, $cid = NULL) {
-  if (!$nid) return NULL;
-  $cart = \Drupal\uc_cart\Controller\Cart::create(\Drupal::getContainer());
-  $cid = !(is_null($cid) || empty($cid)) ? $cid : $cart->getId();
-
   if (isset($data['kit_id'])) {
     // Product was listed individually.
     uc_product_uc_update_cart_item($nid, $data, $qty, $cid);
