diff --git a/payment/lib/Drupal/payment/Annotations/LineItem.php b/payment/lib/Drupal/payment/Annotations/LineItem.php
new file mode 100644
index 0000000..c1f9a73
--- /dev/null
+++ b/payment/lib/Drupal/payment/Annotations/LineItem.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\payment\Annotations\LineItem.
+ */
+
+namespace Drupal\payment\Annotations;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a payment line item plugin annotation.
+ *
+ * @Annotation
+ */
+class LineItem extends Plugin {
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The translated human-readable plugin name.
+   *
+   * @var string
+   */
+  public $label;
+}
diff --git a/payment/lib/Drupal/payment/Annotations/PaymentStatus.php b/payment/lib/Drupal/payment/Annotations/PaymentStatus.php
new file mode 100644
index 0000000..29846c8
--- /dev/null
+++ b/payment/lib/Drupal/payment/Annotations/PaymentStatus.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\payment\Annotations\PaymentStatus.
+ */
+
+namespace Drupal\payment\Annotations;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a payment method plugin annotation.
+ *
+ * @Annotation
+ */
+class PaymentStatus extends Plugin {
+
+  /**
+   * The translated human-readable plugin name (optional).
+   *
+   * @var string
+   */
+  public $description = '';
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The translated human-readable plugin name.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  public $parentID;
+}
diff --git a/payment/lib/Drupal/payment/Plugin/Core/entity/Payment.php b/payment/lib/Drupal/payment/Plugin/Core/entity/Payment.php
new file mode 100644
index 0000000..0d1b63e
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/Core/entity/Payment.php
@@ -0,0 +1,382 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\payment\Plugin\Core\Entity\Payment.
+ */
+
+namespace Drupal\payment\Plugin\Core\Entity;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Entity\Annotation\EntityType;
+use Drupal\Core\Entity\EntityNG;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\payment\Plugin\Core\entity\PaymentInterface;
+use Drupal\payment\Plugin\payment\line_item\LineItemInterface;
+use Drupal\payment\Plugin\payment\status\PaymentStatusInterface;
+use Symfony\Component\Validator\ConstraintViolation;
+
+/**
+ * Defines a payment entity.
+ *
+ * @EntityType(
+ *   base_table = "payment",
+ *   controllers = {
+ *     "access" = "Drupal\payment\Plugin\Core\entity\PaymentAccessController",
+ *     "storage" = "Drupal\payment\Plugin\Core\entity\PaymentStorageController",
+ *   },
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "uuid" = "uuid",
+ *   },
+ *   fieldable = TRUE,
+ *   id = "payment",
+ *   label = @Translation("Payment"),
+ *   module = "payment"
+ * )
+ */
+class Payment extends EntityNG implements PaymentInterface {
+
+  /**
+   * Line items.
+   *
+   * @var array
+   *   Keys are line item machine names. Values are
+   *   Drupal\payment\Plugin\payment\line_item\LineItemInterface instances.
+   */
+  protected $lineItems = array();
+
+  /**
+   * Payment statuses.
+   *
+   * @var array
+   *   Values are Drupal\payment\Plugin\payment\status\PaymentStatusInterface
+   *   instances.
+   */
+  protected $statuses = array();
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $values, $entity_type) {
+    global $user;
+
+    parent::__construct($values, $entity_type);
+    if (is_null($this->getOwnerId())) {
+      $this->setOwnerId($user->id());
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function label($langcode = NULL) {
+    // @todo Delegate this to the context plugin, once contexts have been
+    // converted to plugins.
+    return t('Payment !id', array(
+      '!id' => $this->id(),
+    ));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setPaymentContext($context) {
+    $this->set('paymentContext', $context);
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPaymentContext() {
+    return $this->get('paymentContext')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setCurrencyCode($currencyCode) {
+    $this->set('currencyCode', $currencyCode);
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCurrencyCode() {
+    return $this->get('currencyCode')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setFinishCallback($callback) {
+    $this->finishCallback[0]->setValue($callback);
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFinishCallback() {
+    return $this->finishCallback[0]->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setLineItems(array $line_items) {
+    foreach ($line_items as $line_item) {
+      $this->setLineItem($line_item);
+    }
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setLineItem(LineItemInterface $line_item) {
+    $line_item->setPaymentId($this->id());
+    $this->lineItems[$line_item->getName()] = $line_item;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLineItems() {
+    return $this->lineItems;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLineItem($name) {
+    return isset($this->lineItems[$name]) ? $this->lineItems[$name] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLineItemsByType($pluginId) {
+    $line_items = array();
+    foreach ($this->getLineItems() as $line_item) {
+      if ($line_item->getPluginId() == $plugin_id) {
+        $line_items[$line_item->getName()] = $line_item;
+      }
+    }
+
+    return $line_items;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setStatuses(array $statuses) {
+    foreach ($statuses as $status) {
+      $this->setStatus($status, FALSE);
+    }
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setStatus(PaymentStatusInterface $status, $notify = TRUE) {
+    $previousStatus = $this->getStatus();
+    $status->setPaymentId($this->id());
+    // Prevent duplicate statuses.
+    if (!$this->getStatus() || $this->getStatus()->getPluginId() != $status->getPluginId()) {
+      $this->statuses[] = $status;
+    }
+    if ($notify) {
+      $handler = \Drupal::moduleHandler();
+      foreach ($handler->getImplementations('payment_status_change') as $moduleName) {
+        $handler->invoke($moduleName, 'payment_status_change', $this, $previousStatus);
+        // If a hook invocation has added another log item, a new loop with
+        // invocations has already been executed and we don't need to continue
+        // with this one.
+        if ($this->getStatus()->getPluginId() != $status->getPluginId()) {
+          return;
+        }
+      }
+      if ($handler->moduleExists('rules')) {
+        rules_invoke_event('payment_status_change', $this, $previousStatus);
+      }
+    }
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getStatuses() {
+    return $this->statuses;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getStatus() {
+    return end($this->statuses);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setPaymentMethodId($id) {
+    $this->paymentMethodId[0]->setValue($id);
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPaymentMethodId() {
+    return $this->paymentMethodId[0]->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPaymentMethod() {
+    return entity_load('payment_method', $this->getPaymentMethodId());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOwnerId($id) {
+    $this->ownerId[0]->setValue($id);
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOwnerId() {
+    return $this->ownerId[0]->get('target_id')->getValue();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOwner() {
+    return $this->ownerId[0]->get('entity')->getValue();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAmount() {
+    $total = 0;
+    foreach ($this->getLineItems() as $lineItem) {
+      $total += $lineItem->totalAmount();
+    }
+
+    return $total;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAvailablePaymentMethods(array $paymentMethods = array()) {
+    if (!$paymentMethods) {
+      $paymentMethods = entity_load_multiple('payment_method');
+    }
+    $available = array();
+    foreach ($paymentMethods as $paymentMethod) {
+      try {
+        $paymentMethod->validatePayment($this);
+        $available[$paymentMethod->id()] = $paymentMethod;
+      }
+      catch (PaymentValidationException $e) {
+      }
+    }
+
+    return $available;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function finish() {
+    $this->save();
+    $handler = \Drupal::moduleHandler();
+    $handler->invokeAll('payment_pre_finish', $this);
+    if ($handler->moduleExists('rules')) {
+      rules_invoke_event('payment_pre_finish', $this);
+    }
+    call_user_func($this->finish_callback, $this);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    $handler = \Drupal::moduleHandler();
+    $manager = \Drupal::service('plugin.manager.payment.status');
+    // Preprocess the payment.
+    $handler->invokeAll('payment_pre_execute', $this);
+    if ($handler->moduleExists('rules')) {
+      rules_invoke_event('payment_pre_execute', $this);
+    }
+    // Execute the payment.
+    if (count($this->validate())) {
+      $this->setStatus($manager->createInstance('payment_failed'));
+    }
+    else {
+      $this->setStatus($manager->createInstance('payment_pending'));
+      $this->getPaymentMethod()->executePayment($this);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate() {
+    $violations = parent::validate();
+    // Do not call the payment method if it does not exist, in which case a
+    // violation will already have been added.
+    if ($this->getPaymentMethodId()) {
+      try {
+        $this->getPaymentMethod()->validatePayment($this);
+      }
+      catch (PaymentValidationException $exception) {
+        $violations->add(new ConstraintViolation($exception->getMessage(), $exception->getMessage(), array(), $this, 'paymentMethodId', $this->getPaymentMethodId()));
+      }
+    }
+
+    return $violations;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postSave(EntityStorageControllerInterface $controller, $update = TRUE) {
+    $controller->saveLineItems(array(
+      $this->id() => $this->getLineItems(),
+    ));
+    $controller->savePaymentStatuses(array(
+      $this->id() => $this->getStatuses(),
+  ));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function postDelete(EntityStorageControllerInterface $controller, array $entities) {
+    $controller->deleteLineItems(array_keys($entities));
+    $controller->deletePaymentStatuses(array_keys($entities));
+  }
+}
diff --git a/payment/lib/Drupal/payment/Plugin/Core/entity/PaymentAccessController.php b/payment/lib/Drupal/payment/Plugin/Core/entity/PaymentAccessController.php
new file mode 100644
index 0000000..039e226
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/Core/entity/PaymentAccessController.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\payment\Plugin\Core\entity\PaymentMethodAccessController.
+ */
+
+namespace Drupal\payment\Plugin\Core\entity;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityAccessController;
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Defines the default list controller for ConfigEntity objects.
+ */
+class PaymentAccessController extends EntityAccessController {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function checkAccess(EntityInterface $payment, $operation, $langcode, AccountInterface $account) {
+    switch ($operation) {
+      case 'create':
+        // We let other modules decide whether users have access to create
+        // new payments. There is no corresponding permission for this operation.
+        return TRUE;
+      default:
+        return user_access('payment.payment.' . $operation . '.any', $account) || user_access('payment.payment.' . $operation . '.own', $account) && $account->id() == $payment->getOwnerId();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getCache(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
+    // Disable the cache, because the intensive operations are cached in
+    // user_access() already and the results of all other operations are too
+    // volatile to be cached.
+  }
+}
diff --git a/payment/lib/Drupal/payment/Plugin/Core/entity/PaymentInterface.php b/payment/lib/Drupal/payment/Plugin/Core/entity/PaymentInterface.php
new file mode 100644
index 0000000..6d7122f
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/Core/entity/PaymentInterface.php
@@ -0,0 +1,237 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\payment\Plugin\Core\entity\PaymentInterface.
+ */
+
+namespace Drupal\payment\Plugin\Core\entity;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Executable\ExecutableInterface;
+use Drupal\payment\Plugin\Core\entity\PaymentMethodInterface;
+use Drupal\payment\Plugin\payment\line_item\LineItemInterface;
+use Drupal\payment\Plugin\payment\status\PaymentStatusInterface;
+
+/**
+ * Defines a payment entity type .
+ */
+interface PaymentInterface extends EntityInterface, ExecutableInterface {
+
+  /**
+   * Sets the machine name of the context that created this Payment, such as a
+   * payment form, or a module.
+   *
+   * @param string $context
+   *
+   * @return \Drupal\payment\Plugin\Core\entity\PaymentInterface
+   */
+  public function setPaymentContext($context);
+
+  /**
+   * Gets the machine name of the context that created this Payment, such as a
+   * payment form, or a module.
+   *
+   * @return string
+   */
+  public function getPaymentContext();
+
+  /**
+   * Sets the ISO 4217 currency code of the payment amount.
+   *
+   * @param string $currencyCode
+   *
+   * @return \Drupal\payment\Plugin\Core\entity\PaymentInterface
+   */
+  public function setCurrencyCode($currencyCode);
+
+  /**
+   * Gets the ISO 4217 currency code of the payment amount.
+   *
+   * @return string
+   */
+  public function getCurrencyCode();
+
+  /**
+   * Sets the name of the function to call when payment execution is completed,
+   * regardless of the payment status. It receives one argument:
+   * - $payment Payment
+   *   The Payment object.
+   * The callback does not need to return anything and is free to redirect the
+   * user or display something.
+   * Use Payment::context_data to pass on arbitrary data to the finish callback.
+   *
+   * @param string $callback
+   *
+   * @return \Drupal\payment\Plugin\Core\entity\PaymentInterface
+   */
+  public function setFinishCallback($callback);
+
+  /**
+   * Gets the name of the function to call when payment execution is completed,
+   * regardless of the payment status. It receives one argument:
+   * - $payment Payment
+   *   The Payment object.
+   * The callback does not need to return anything and is free to redirect the
+   * user or display something.
+   * Use Payment::context_data to pass on arbitrary data to the finish callback.
+   *
+   * @return string|null
+   */
+  public function getFinishCallback();
+
+  /**
+   * Sets line items.
+   *
+   * @param array $line_items
+   *   Values are \Drupal\payment\Plugin\payment\line_item\LineItemInterface
+   *   objects.
+   *
+   * @return \Drupal\payment\Plugin\Core\entity\PaymentInterface
+   */
+  public function setLineItems(array $line_items);
+
+  /**
+   * Sets a line item.
+   *
+   * @param \Drupal\payment\Plugin\payment\line_item\LineItemInterface $line_item
+   *
+   * @return \Drupal\payment\Plugin\Core\entity\PaymentInterface
+   */
+  public function setLineItem(LineItemInterface $line_item);
+
+  /**
+   * Gets all line items.
+   *
+   * @return array
+   */
+  public function getLineItems();
+
+  /**
+   * Gets a line item.
+   *
+   * @param string $name
+   *   The line item's machine name.
+   *
+   * @return PaymentLineItem
+   */
+  public function getLineItem($name);
+
+  /**
+   * Gets line items by plugin type.
+   *
+   * @param string $plugin_id
+   *   The line item plugin's ID.
+   *
+   * @return array
+   *   Values are \Drupal\payment\Plugin\payment\line_item\LineItemInterface
+   *   objects.
+   */
+  public function getLineItemsByType($type);
+
+  /**
+   * Sets all statuses.
+   *
+   * @param array $statuses
+   *   \Drupal\payment\Plugin\payment\status\PaymentStatusInterface objects.
+   *
+   * @return \Drupal\payment\Plugin\Core\entity\PaymentInterface
+   */
+  public function setStatuses(array $statuses);
+
+  /**
+   * Sets a status.
+   *
+   * @param \Drupal\payment\Plugin\payment\status\PaymentStatusInterface $status
+   * @param bool $notify
+   *   Whether or not to trigger a notification event.
+   *
+   * @return \Drupal\payment\Plugin\Core\entity\PaymentInterface
+   */
+  public function setStatus(PaymentStatusInterface $status, $notify = TRUE);
+
+  /**
+   * Gets all statuses.
+   *
+   * @return array
+   */
+  public function getStatuses();
+
+  /**
+   * Gets the status.
+   *
+   * @return \Drupal\payment\Plugin\payment\status\PaymentStatusInterface
+   */
+  public function getStatus();
+
+  /**
+   * Sets the ID of the payment method entity.
+   *
+   * @param string $name
+   *
+   * @return \Drupal\payment\Plugin\Core\entity\PaymentInterface
+   */
+  public function setPaymentMethodId($id);
+
+  /**
+   * Gets the ID of the payment method entity.
+   *
+   * @return string|null
+   */
+  public function getPaymentMethodId();
+
+  /**
+   * Gets the payment method entity.
+   *
+   * @return \Drupal\payment\Plugin\Core\entity\PaymentMethodInterface
+   */
+  public function getPaymentMethod();
+
+  /**
+   * Sets the ID of the user who owns this payment.
+   *
+   * @param int $uid
+   *
+   * @return \Drupal\payment\Plugin\Core\entity\PaymentInterface
+   */
+  public function setOwnerId($id);
+
+  /**
+   * Gets the ID of the user who owns this payment.
+   *
+   * @return int
+   */
+  public function getOwnerId();
+
+  /**
+   * Gets the owner.
+   *
+   * @return \Drupal\user\UserInterface
+   */
+  public function getOwner();
+
+  /**
+   * Gets the payment amount.
+   *
+   * @return float
+   */
+  public function getAmount();
+
+  /**
+   * Gets the available payment methods.
+   *
+   * @param array $paymentMethods
+   *   An array with \Drupal\payment\Plugin\Core\entity\PaymentMethodInterface
+   *   objects to optionally limit the check to.
+   *
+   * @return array
+   *   An array with \Drupal\payment\Plugin\Core\entity\PaymentMethodInterface
+   *   objects.
+   */
+  public function getAvailablePaymentMethods(array $paymentMethods = array());
+
+  /**
+   * Finishes the payment and resumes context workflow.
+   */
+  public function finish();
+}
diff --git a/payment/lib/Drupal/payment/Plugin/Core/entity/PaymentStorageController.php b/payment/lib/Drupal/payment/Plugin/Core/entity/PaymentStorageController.php
new file mode 100644
index 0000000..88e1b28
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/Core/entity/PaymentStorageController.php
@@ -0,0 +1,214 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\payment\Plugin\Core\entity\PaymentStorageController.
+ */
+
+namespace Drupal\payment\Plugin\Core\entity;
+
+use Drupal\Core\Entity\DatabaseStorageControllerNG;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Handles storage for payment entities.
+ */
+class PaymentStorageController extends DatabaseStorageControllerNG implements PaymentStorageControllerInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  function create(array $values) {
+    $payment = parent::create($values);
+    $payment->setStatus(\Drupal::service('plugin.manager.payment.status')->createInstance('payment_created'));
+
+    return $payment;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function attachLoad(&$payments, $load_revision = FALSE) {
+    $line_items = $this->loadLineItems(array_keys($payments));
+    foreach ($line_items as $payment_id => $entity_line_items) {
+      $payments[$payment_id]->lineItems = $entity_line_items;
+    }
+    $statuses = $this->loadPaymentStatuses(array_keys($payments));
+    foreach ($statuses as $payment_id => $entity_statuses) {
+      $payments[$payment_id]->statuses = $entity_statuses;
+    }
+    parent::attachLoad($payments, $load_revision);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function baseFieldDefinitions() {
+    $fields = parent::baseFieldDefinitions();
+    $fields['paymentContext'] = array(
+      'label' => t('Context'),
+      'type' => 'string_field',
+    );
+    $fields['currencyCode'] = array(
+      'label' => t('Currency code'),
+      'settings' => array(
+        'default_value' => 'XXX',
+      ),
+      'type' => 'string_field',
+    );
+    $fields['finishCallback'] = array(
+      'label' => t('Finish callback'),
+      'type' => 'string_field',
+    );
+    $fields['id'] = array(
+      'label' => t('Payment ID'),
+      'type' => 'integer_field',
+      'read-only' => TRUE,
+    );
+    $fields['paymentMethodId'] = array(
+      'label' => t('Payment method ID'),
+      'type' => 'integer_field',
+    );
+    $fields['ownerId'] = array(
+      'label' => t('Owner'),
+      'type' => 'entity_reference_field',
+      'settings' => array(
+        'target_type' => 'user',
+        'default_value' => 0,
+      ),
+    );
+    $fields['uuid'] = array(
+      'label' => t('UUID'),
+      'read-only' => TRUE,
+      'type' => 'uuid_field',
+    );
+
+    return $fields;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function mapToStorageRecord(EntityInterface $entity) {
+    $record = new \stdClass();
+    $record->context = $entity->getPaymentContext();
+    $record->currency_code = $entity->getCurrencyCode();
+    $record->finish_callback = $entity->getFinishCallback();
+    $record->id = $entity->id();
+    $record->payment_method_id = $entity->getPaymentMethodId();
+    $record->first_payment_status_id = current($entity->getStatuses())->getId();
+    $record->last_payment_status_id = $entity->getStatus()->getId();
+    $record->owner_id = $entity->getOwnerId();
+
+    return $record;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadLineItems(array $ids) {
+    $manager = \Drupal::service('plugin.manager.payment.line_item');
+    $result = db_select('payment_line_item', 'pli')
+      ->fields('pli')
+      ->condition('payment_id', $ids)
+      ->execute();
+    $line_items = array();
+    while ($line_item_data = $result->fetchAssoc()) {
+      $plugin_id = $line_item_data['plugin_id'];
+      $line_item = $manager->createInstance($plugin_id, array(
+        'amount' => (float) $line_item_data['amount'],
+        'name' => $line_item_data['name'],
+        'paymentId' => (int) $line_item_data['payment_id'],
+        'quantity' => (int) $line_item_data['quantity'],
+      ));
+      $line_items[$line_item->getPaymentId()][$line_item->getName()] = $line_item;
+    }
+
+    return $line_items;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function saveLineItems(array $line_items) {
+    $this->deleteLineItems(array_keys($line_items));
+    $query = db_insert('payment_line_item')
+      ->fields(array('amount', 'amount_total', 'name', 'payment_id', 'plugin_id', 'quantity'));
+    foreach ($line_items as $payment_id => $entity_line_items) {
+      foreach ($entity_line_items as $line_item) {
+        $line_item->setPaymentId($payment_id);
+        $query->values(array(
+          'amount' => $line_item->getAmount(),
+          'amount_total' => $line_item->getTotalAmount(),
+          'name' => $line_item->getName(),
+          'payment_id' => $line_item->getPaymentId(),
+          'plugin_id' => $line_item->getPluginId(),
+          'quantity' => $line_item->getQuantity(),
+        ));
+      }
+    }
+    $query->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteLineItems(array $ids) {
+    db_delete('payment_line_item')
+      ->condition('payment_id', $ids)
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadPaymentStatuses(array $ids) {
+    $manager = \Drupal::service('plugin.manager.payment.status');
+    $result = db_select('payment_status', 'ps')
+      ->fields('ps')
+      ->condition('payment_id', $ids)
+      ->orderBy('id', 'ASC')
+      ->execute();
+    $statuses = array();
+    while ($status_data = $result->fetchAssoc()) {
+      $plugin_id = $status_data['plugin_id'];
+      $status = $manager->createInstance($plugin_id, array(
+        'created' => (int) $status_data['created'],
+        'paymentId' => (int) $status_data['payment_id'],
+      ));
+      $statuses[$status->getPaymentId()][] = $status;
+    }
+
+    return $statuses;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function savePaymentStatuses(array $statuses) {
+    foreach ($statuses as $payment_id => $entity_statuses) {
+      foreach ($entity_statuses as $status) {
+        // Statuses cannot be edited, so only save the ones without an ID.
+        if (!$status->getId()) {
+          $status->setPaymentId($payment_id);
+          $record = array(
+            'created' => $status->getCreated(),
+            'payment_id' => $status->getPaymentId(),
+            'plugin_id' => $status->getPluginId(),
+          );
+          drupal_write_record('payment_status', $record);
+          $status->setId($record['id']);
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deletePaymentStatuses(array $ids) {
+    db_delete('payment_status')
+      ->condition('payment_id', $ids)
+      ->execute();
+  }
+}
diff --git a/payment/lib/Drupal/payment/Plugin/Core/entity/PaymentStorageControllerInterface.php b/payment/lib/Drupal/payment/Plugin/Core/entity/PaymentStorageControllerInterface.php
new file mode 100644
index 0000000..76ed5de
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/Core/entity/PaymentStorageControllerInterface.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\payment\Plugin\Core\entity\PaymentStorageControllerInterface.
+ */
+
+namespace Drupal\payment\Plugin\Core\entity;
+
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+
+/**
+ * A storage controller for payment entities.
+ */
+interface PaymentStorageControllerInterface extends EntityStorageControllerInterface {
+
+  /**
+   * Loads payment line items.
+   *
+   * @param array $ids
+   *   Payment IDs.
+   *
+   * @return array
+   *   Keys are payment IDs, values are arrays of which keys are line item names
+   *   and values are \Drupal\payment\Plugin\payment\line_item\LineItemInterface
+   *   objects.
+   */
+  public function loadLineItems(array $ids);
+
+  /**
+   * Saves payment line items.
+   *
+   * @param array $lineItems
+   *   Keys are payment IDs, values are arrays of which keys are line item names
+   *   and values are \Drupal\payment\Plugin\payment\line_item\LineItemInterface
+   *   objects.
+   */
+  public function saveLineItems(array $lineItems);
+
+  /**
+   * Deletes payment line items.
+   *
+   * @param array $ids
+   *   Keys are payment IDs. Values are line item names.
+   */
+  public function deleteLineItems(array $ids);
+
+  /**
+   * Loads payment statuses.
+   *
+   * @param array $ids
+   *   Payment IDs.
+   *
+   * @return array
+   *   Keys are payment IDs, values are arrays of
+   *   \Drupal\payment\Plugin\payment\status\PaymentStatusInterface objects.
+   */
+  public function loadPaymentStatuses(array $ids);
+
+  /**
+   * Saves payment statuses.
+   *
+   * @param array $lineItems
+   *   Keys are payment IDs, values are arrays of
+   *   \Drupal\payment\Plugin\payment\status\PaymentStatusInterface objects.
+   */
+  public function savePaymentStatuses(array $statuses);
+
+  /**
+   * Deletes payment statuses.
+   *
+   * @param array $ids
+   *   Payment IDs.
+   */
+  public function deletePaymentStatuses(array $ids);
+}
diff --git a/payment/lib/Drupal/payment/Plugin/payment/PaymentMethod/Unavailable.php b/payment/lib/Drupal/payment/Plugin/payment/PaymentMethod/Unavailable.php
index 6d276c9..2db6aea 100644
--- a/payment/lib/Drupal/payment/Plugin/payment/PaymentMethod/Unavailable.php
+++ b/payment/lib/Drupal/payment/Plugin/payment/PaymentMethod/Unavailable.php
@@ -36,7 +36,7 @@ class Unavailable extends Base {
    * {@inheritdoc}.
    */
   public function executePayment(Payment $payment) {
-    $payment->setStatus(new PaymentStatusItem(PAYMENT_STATUS_UNKNOWN));
+    $payment->setStatus(\Drupal::service('plugin.manager.payment.status')->createInstance('payment_unknown'));
   }
 
   /**
diff --git a/payment/lib/Drupal/payment/Plugin/payment/line_item/Base.php b/payment/lib/Drupal/payment/Plugin/payment/line_item/Base.php
new file mode 100644
index 0000000..9a2ebad
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/payment/line_item/Base.php
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\payment\line_item\Base.
+ */
+
+namespace Drupal\payment\Plugin\payment\line_item;
+
+use Drupal\Component\Plugin\PluginBase;
+use Drupal\payment\Plugin\payment\line_item\LineItemInterface;
+
+/**
+ * A base line item.
+ */
+abstract class Base extends PluginBase implements LineItemInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition) {
+    $configuration += array(
+      'amount' => 0,
+      'name' => NULL,
+      'paymentId' => 0,
+      'quantity' => 1,
+    );
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setAmount($amount) {
+    $this->configuration['amount'] = $amount;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAmount() {
+    return $this->configuration['amount'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function getTotalAmount() {
+    return $this->getAmount() * $this->getQuantity();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setName($name) {
+    $this->configuration['name'] = $name;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPaymentId() {
+    return $this->configuration['paymentId'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setPaymentId($id) {
+    $this->configuration['paymentId'] = $id;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return $this->configuration['name'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setQuantity($quantity) {
+    $this->configuration['quantity'] = $quantity;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuantity() {
+    return $this->configuration['quantity'];
+  }
+}
diff --git a/payment/lib/Drupal/payment/Plugin/payment/line_item/Basic.php b/payment/lib/Drupal/payment/Plugin/payment/line_item/Basic.php
new file mode 100644
index 0000000..cdafc6e
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/payment/line_item/Basic.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\payment\line_item\Basic.
+ */
+
+namespace Drupal\payment\Plugin\payment\line_item;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\payment\Annotations\LineItem;
+use Drupal\payment\Plugin\payment\line_item\Base;
+
+/**
+ * A basic line item.
+ *
+ * @LineItem(
+ *   id = "payment_basic",
+ *   label = @Translation("Basic")
+ * )
+ */
+class Basic extends Base {
+}
diff --git a/payment/lib/Drupal/payment/Plugin/payment/line_item/LineItemInterface.php b/payment/lib/Drupal/payment/Plugin/payment/line_item/LineItemInterface.php
new file mode 100644
index 0000000..8631360
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/payment/line_item/LineItemInterface.php
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\plugin\payment\line_item\LineItemInterface.
+ */
+
+namespace Drupal\payment\plugin\payment\line_item;
+
+use Drupal\Component\Plugin\PluginInspectionInterface;
+use Drupal\Core\TypedData\ComplexDataInterface;
+
+/**
+ * A payment line item.
+ */
+interface LineItemInterface extends PluginInspectionInterface {
+
+  /**
+   * Sets the amount.
+   *
+   * @param float $amount
+   *
+   * @return \Drupal\payment\Plugin\payment\line_item\LineItemInterface
+   */
+  public function setAmount($amount);
+
+  /**
+   * Gets the amount.
+   *
+   * @return float
+   */
+  public function getAmount();
+
+  /**
+   * Return this line item's total amount.
+   *
+   * @return float
+   */
+  function getTotalAmount();
+
+  /**
+   * Sets the machine name.
+   *
+   * @param string $name
+   *
+   * @return \Drupal\payment\Plugin\payment\line_item\LineItemInterface
+   */
+  public function setName($name);
+
+  /**
+   * Gets the machine name.
+   *
+   * @return string
+   */
+  public function getName();
+
+  /**
+   * Sets the payment ID.
+   *
+   * @param integer $id
+   *
+   * @return \Drupal\payment\Plugin\payment\line_item\LineItemInterface
+   */
+  public function setPaymentId($id);
+
+  /**
+   * Gets the payment ID.
+   *
+   * @return integer
+   */
+  public function getPaymentId();
+
+  /**
+   * Sets the quantity.
+   *
+   * @param int $quantity
+   *
+   * @return \Drupal\payment\Plugin\payment\line_item\LineItemInterface
+   */
+  public function setQuantity($quantity);
+
+  /**
+   * Gets the quantity.
+   *
+   * @return int
+   */
+  public function getQuantity();
+}
diff --git a/payment/lib/Drupal/payment/Plugin/payment/line_item/Manager.php b/payment/lib/Drupal/payment/Plugin/payment/line_item/Manager.php
new file mode 100644
index 0000000..33b135e
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/payment/line_item/Manager.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\payment\line_item\Manager.
+ */
+
+namespace Drupal\payment\Plugin\payment\line_item;
+
+use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Component\Plugin\Factory\DefaultFactory;
+use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
+use Drupal\Core\Plugin\Discovery\AlterDecorator;
+use Drupal\Core\Plugin\Discovery\CacheDecorator;
+
+/**
+ * Manages discovery and instantiation of payment line item plugins.
+ *
+ * @see \Drupal\payment\Plugin\payment\line_item\LineItemInterface
+ */
+class Manager extends PluginManagerBase {
+
+  /**
+   * Constructor.
+   *
+   * @param array $namespaces
+   *   An array of paths keyed by their corresponding namespaces.
+   */
+  public function __construct(\Traversable $namespaces) {
+    $annotation_namespaces = array(
+      'Drupal\payment\Annotations' => drupal_get_path('module', 'payment') . '/lib',
+    );
+    $this->discovery = new AnnotatedClassDiscovery('payment/line_item', $namespaces, $annotation_namespaces, 'Drupal\payment\Annotations\LineItem');
+    $this->discovery = new AlterDecorator($this->discovery, 'payment_line_item');
+    $this->discovery = new CacheDecorator($this->discovery, 'payment_line_item');
+    $this->factory = new DefaultFactory($this->discovery);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createInstance($plugin_id, array $configuration = array()) {
+    // If a plugin is missing, use the default.
+    try {
+      return parent::createInstance($plugin_id, $configuration);
+    }
+    catch (PluginException $e) {
+      return parent::createInstance('payment_basic', $configuration);
+    }
+  }
+}
diff --git a/payment/lib/Drupal/payment/Plugin/payment/status/AuthorizationFailed.php b/payment/lib/Drupal/payment/Plugin/payment/status/AuthorizationFailed.php
new file mode 100644
index 0000000..9da8557
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/payment/status/AuthorizationFailed.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\payment\status\AuthorizationFailed.
+ */
+
+namespace Drupal\payment\Plugin\payment\status;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\payment\Annotations\PaymentStatus;
+use Drupal\payment\Plugin\payment\status\Base;
+
+/**
+ * A payment that failed authorization.
+ *
+ * @PaymentStatus(
+ *   id = "payment_authorization_failed",
+ *   label = @Translation("Authorization failed"),
+ *   parentId = "payment_failed"
+ * )
+ */
+class AuthorizationFailed extends Base {
+}
diff --git a/payment/lib/Drupal/payment/Plugin/payment/status/Base.php b/payment/lib/Drupal/payment/Plugin/payment/status/Base.php
new file mode 100644
index 0000000..ece1e6d
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/payment/status/Base.php
@@ -0,0 +1,134 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\payment\status\Base.
+ */
+
+namespace Drupal\payment\Plugin\payment\status;
+
+use Drupal\Component\Plugin\PluginBase;
+use Drupal\payment\Plugin\payment\status\PaymentStatusInterface;
+
+/**
+ * A base payment status.
+ */
+abstract class Base extends PluginBase implements PaymentStatusInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition) {
+    $configuration += array(
+      'created' => NULL,
+      'id' => 0,
+      'paymentId' => 0,
+    );
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    if (!$this->getCreated()) {
+      $this->setCreated(time());
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setCreated($created) {
+    $this->configuration['created'] = $created;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCreated() {
+    return $this->configuration['created'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setPaymentId($paymentId) {
+    $this->configuration['paymentId'] = $paymentId;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPaymentId() {
+    return $this->configuration['paymentId'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setId($id) {
+    $this->configuration['id'] = $id;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getId() {
+    return $this->configuration['id'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function getAncestors(){
+    if (isset($this->pluginDefinition['parentId'])) {
+      $manager = \Drupal::service('plugin.manager.payment.status');
+      $parent = $this->pluginDefinition['parentId'];
+      return array_unique(array_merge(array($parent), $manager->createInstance($parent)->getAncestors()));
+    }
+    return array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getChildren() {
+    $manager = \Drupal::service('plugin.manager.payment.status');
+    $children = array();
+    foreach ($manager->getDefinitions() as $definition) {
+      if (isset($definition['parentId']) && $definition['parentId'] == $this->getPluginId()) {
+        $children[] = $definition['id'];
+      }
+    }
+
+    return $children;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function getDescendants() {
+    $manager = \Drupal::service('plugin.manager.payment.status');
+    $children = $this->getChildren();
+    $descendants = $children;
+    foreach ($children as $child) {
+      $descendants = array_merge($descendants, $manager->createInstance($child)->getDescendants());
+    }
+
+    return array_unique($descendants);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function hasAncestor($plugin_id) {
+    return in_array($plugin_id, $this->getAncestors());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function isOrHasAncestor($plugin_id) {
+    return $this->getPluginId() == $plugin_id|| $this->hasAncestor($plugin_id);
+  }
+}
diff --git a/payment/lib/Drupal/payment/Plugin/payment/status/Cancelled.php b/payment/lib/Drupal/payment/Plugin/payment/status/Cancelled.php
new file mode 100644
index 0000000..61c69bc
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/payment/status/Cancelled.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\payment\status\Cancelled.
+ */
+
+namespace Drupal\payment\Plugin\payment\status;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\payment\Annotations\PaymentStatus;
+use Drupal\payment\Plugin\payment\status\Base;
+
+/**
+ * A cancelled payment.
+ *
+ * @PaymentStatus(
+ *   id = "payment_cancelled",
+ *   label = @Translation("Cancelled"),
+ *   parentId = "payment_failed"
+ * )
+ */
+class Cancelled extends Base {
+}
diff --git a/payment/lib/Drupal/payment/Plugin/payment/status/Created.php b/payment/lib/Drupal/payment/Plugin/payment/status/Created.php
new file mode 100644
index 0000000..a1c21ea
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/payment/status/Created.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\payment\status\Created.
+ */
+
+namespace Drupal\payment\Plugin\payment\status;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\payment\Annotations\PaymentStatus;
+use Drupal\payment\Plugin\payment\status\Base;
+
+/**
+ * A newly created payment.
+ *
+ * @PaymentStatus(
+ *   id = "payment_created",
+ *   label = @Translation("Created"),
+ *   parentId = "payment_no_money_transferred"
+ * )
+ */
+class Created extends Base {
+}
diff --git a/payment/lib/Drupal/payment/Plugin/payment/status/Expired.php b/payment/lib/Drupal/payment/Plugin/payment/status/Expired.php
new file mode 100644
index 0000000..94a701f
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/payment/status/Expired.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\payment\status\Expired.
+ */
+
+namespace Drupal\payment\Plugin\payment\status;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\payment\Annotations\PaymentStatus;
+use Drupal\payment\Plugin\payment\status\Base;
+
+/**
+ * An expired payment.
+ *
+ * @PaymentStatus(
+ *   id = "payment_expired",
+ *   label = @Translation("Expired"),
+ *   parentId = "payment_failed"
+ * )
+ */
+class Expired extends Base {
+}
diff --git a/payment/lib/Drupal/payment/Plugin/payment/status/Failed.php b/payment/lib/Drupal/payment/Plugin/payment/status/Failed.php
new file mode 100644
index 0000000..9da4417
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/payment/status/Failed.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\payment\status\Failed.
+ */
+
+namespace Drupal\payment\Plugin\payment\status;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\payment\Annotations\PaymentStatus;
+use Drupal\payment\Plugin\payment\status\Base;
+
+/**
+ * A failed payment.
+ *
+ * @PaymentStatus(
+ *   id = "payment_failed",
+ *   label = @Translation("Failed"),
+ *   parentId = "payment_no_money_transferred"
+ * )
+ */
+class Failed extends Base {
+}
diff --git a/payment/lib/Drupal/payment/Plugin/payment/status/Manager.php b/payment/lib/Drupal/payment/Plugin/payment/status/Manager.php
new file mode 100644
index 0000000..ddd0ac6
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/payment/status/Manager.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\payment\status\Manager.
+ */
+
+namespace Drupal\payment\Plugin\payment\status;
+
+use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Component\Plugin\Factory\DefaultFactory;
+use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
+use Drupal\Core\Plugin\Discovery\AlterDecorator;
+use Drupal\Core\Plugin\Discovery\CacheDecorator;
+
+/**
+ * Manages discovery and instantiation of payment status plugins.
+ *
+ * @see \Drupal\payment\Plugin\payment\status\PaymentStatusInterface
+ */
+class Manager extends PluginManagerBase {
+
+  /**
+   * Constructor.
+   *
+   * @param array $namespaces
+   *   An array of paths keyed by their corresponding namespaces.
+   */
+  public function __construct(\Traversable $namespaces) {
+    $annotation_namespaces = array(
+      'Drupal\payment\Annotations' => drupal_get_path('module', 'payment') . '/lib',
+    );
+    $this->discovery = new AnnotatedClassDiscovery('payment/status', $namespaces, $annotation_namespaces, 'Drupal\payment\Annotations\PaymentStatus');
+    $this->discovery = new DerivativeDiscoveryDecorator($this->discovery);
+    $this->discovery = new AlterDecorator($this->discovery, 'payment_status');
+    $this->discovery = new CacheDecorator($this->discovery, 'payment_status');
+    $this->factory = new DefaultFactory($this->discovery);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createInstance($plugin_id, array $configuration = array()) {
+    // If a plugin is missing, use the default.
+    try {
+      return parent::createInstance($plugin_id, $configuration);
+    }
+    catch (PluginException $e) {
+      return parent::createInstance('payment_unknown', $configuration);
+    }
+  }
+}
diff --git a/payment/lib/Drupal/payment/Plugin/payment/status/MoneyTransferred.php b/payment/lib/Drupal/payment/Plugin/payment/status/MoneyTransferred.php
new file mode 100644
index 0000000..fcb12e6
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/payment/status/MoneyTransferred.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\payment\status\MoneyTransferred.
+ */
+
+namespace Drupal\payment\Plugin\payment\status;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\payment\Annotations\PaymentStatus;
+use Drupal\payment\Plugin\payment\status\Base;
+
+/**
+ * Money has been transferred.
+ *
+ * @PaymentStatus(
+ *   id = "payment_money_transferred",
+ *   label = @Translation("Money has been transferred")
+ * )
+ */
+class MoneyTransferred extends Base {
+}
diff --git a/payment/lib/Drupal/payment/Plugin/payment/status/NoMoneyTransferred.php b/payment/lib/Drupal/payment/Plugin/payment/status/NoMoneyTransferred.php
new file mode 100644
index 0000000..8377100
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/payment/status/NoMoneyTransferred.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\payment\status\NoMoneyTransferred.
+ */
+
+namespace Drupal\payment\Plugin\payment\status;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\payment\Annotations\PaymentStatus;
+use Drupal\payment\Plugin\payment\status\Base;
+
+/**
+ * No money has been transferred.
+ *
+ * @PaymentStatus(
+ *   id = "payment_no_money_transferred",
+ *   label = @Translation("No money has been transferred")
+ * )
+ */
+class NoMoneyTransferred extends Base {
+}
diff --git a/payment/lib/Drupal/payment/Plugin/payment/status/PaymentStatusInterface.php b/payment/lib/Drupal/payment/Plugin/payment/status/PaymentStatusInterface.php
new file mode 100644
index 0000000..477cad9
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/payment/status/PaymentStatusInterface.php
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\plugin\payment\PaymentStatus\PaymentStatusInterface.
+ */
+
+namespace Drupal\payment\plugin\payment\status;
+
+use Drupal\Component\Plugin\PluginInspectionInterface;
+
+/**
+ * A payment status plugin.
+ */
+interface PaymentStatusInterface extends PluginInspectionInterface {
+
+  /**
+   * Sets the created date and time.
+   *
+   * @param string $created
+   *   A Unix timestamp.
+   * *
+   * * @return \Drupal\payment\Plugin\payment\status\PaymentStatusInterface
+   */
+  public function setCreated($created);
+
+  /**
+   * Gets the created date and time.
+   *
+   * @return string
+   *   A Unix timestamp.
+   */
+  public function getCreated();
+
+  /**
+   * Sets the ID of the payment this status is for.
+   *
+   * @param int $id
+   * *
+   * * @return \Drupal\payment\Plugin\payment\status\PaymentStatusInterface
+   */
+  public function setPaymentId($id);
+
+  /**
+   * Gets the ID of the payment this status is for.
+   *
+   * @param int
+   */
+  public function getPaymentId();
+
+  /**
+   * Sets the ID.
+   *
+   * @param int $id
+   * *
+   * * @return \Drupal\payment\Plugin\payment\status\PaymentStatusInterface
+   */
+  public function setId($id);
+
+  /**
+   * Gets the ID.
+   *
+   * @return int
+   */
+  public function getId();
+
+  /**
+   * Gets this payment status's ancestors.
+   *
+   * @return array
+   *   The plugin IDs of this status's ancestors.
+   */
+  function getAncestors();
+
+  /**
+   * Gets this payment status's children.
+   *
+   * @return array
+   *   The plugin IDs of this status's children.
+   */
+  public function getChildren();
+
+  /**
+   * Get this payment status's descendants.
+   *
+   * @return array
+   *   The machine names of this status's descendants.
+   */
+  function getDescendants();
+
+  /**
+   * Checks if the status has a given other status as one of its ancestors.
+   *.
+   * @param string $plugin_id
+   *   The payment status plugin ID to check against.
+   *
+   * @return boolean
+   */
+  function hasAncestor($plugin_id);
+
+  /**
+   * Checks if the status is equal to a given other status or has it one of
+   * its ancestors.
+   *
+   * @param string $plugin_id
+   *   The payment status plugin ID to check against.
+   *
+   * @return boolean
+   */
+  function isOrHasAncestor($plugin_id);
+}
diff --git a/payment/lib/Drupal/payment/Plugin/payment/status/Pending.php b/payment/lib/Drupal/payment/Plugin/payment/status/Pending.php
new file mode 100644
index 0000000..d0d9504
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/payment/status/Pending.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\payment\status\Pending.
+ */
+
+namespace Drupal\payment\Plugin\payment\status;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\payment\Annotations\PaymentStatus;
+use Drupal\payment\Plugin\payment\status\Base;
+
+/**
+ * An pending payment status.
+ *
+ * @PaymentStatus(
+ *   id = "payment_pending",
+ *   label = @Translation("Pending"),
+ *   parentId = "payment_no_money_transferred"
+ * )
+ */
+class Pending extends Base {
+}
diff --git a/payment/lib/Drupal/payment/Plugin/payment/status/Success.php b/payment/lib/Drupal/payment/Plugin/payment/status/Success.php
new file mode 100644
index 0000000..14a975b
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/payment/status/Success.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\payment\status\Success.
+ */
+
+namespace Drupal\payment\Plugin\payment\status;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\payment\Annotations\PaymentStatus;
+use Drupal\payment\Plugin\payment\status\Base;
+
+/**
+ * An unknown payment status.
+ *
+ * @PaymentStatus(
+ *   id = "payment_success",
+ *   label = @Translation("Completed"),
+ *   parentId = "payment_money_transferred"
+ * )
+ */
+class Success extends Base {
+}
diff --git a/payment/lib/Drupal/payment/Plugin/payment/status/Unknown.php b/payment/lib/Drupal/payment/Plugin/payment/status/Unknown.php
new file mode 100644
index 0000000..b7075f0
--- /dev/null
+++ b/payment/lib/Drupal/payment/Plugin/payment/status/Unknown.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\payment\status\Unknown.
+ */
+
+namespace Drupal\payment\Plugin\payment\status;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\payment\Annotations\PaymentStatus;
+use Drupal\payment\Plugin\payment\status\Base;
+
+/**
+ * An unknown payment status.
+ *
+ * @PaymentStatus(
+ *   description = @Translation("The payment status could not be automatically verified."),
+ *   id = "payment_unknown",
+ *   label = @Translation("Unknown")
+ * )
+ */
+class Unknown extends Base {
+}
diff --git a/payment/lib/Drupal/payment/Tests/AccessibleInterfaceWebTestBase.php b/payment/lib/Drupal/payment/Tests/AccessibleInterfaceWebTestBase.php
index f02c1b3..a6f70b1 100644
--- a/payment/lib/Drupal/payment/Tests/AccessibleInterfaceWebTestBase.php
+++ b/payment/lib/Drupal/payment/Tests/AccessibleInterfaceWebTestBase.php
@@ -101,7 +101,8 @@ class AccessibleInterfaceWebTestBase extends WebTestBase {
       $authenticated->save();
       $with = $permissions ? 'with' : 'without';
       $can = $access['authenticated_with_permissions'] ? 'can' : 'cannot';
-      $this->assertEqual($data->access($operation, $authenticated), $access['authenticated_with_permissions'], "An authenticated user (UID $authenticated->uid) $can perform operation <strong>$operation</strong> on <strong>$data_label</strong>$comment with permission(s) " . $this->permissionLabel($permissions));
+      $uid = $authenticated->id();
+      $this->assertEqual($data->access($operation, $authenticated), $access['authenticated_with_permissions'], "An authenticated user (UID $uid) $can perform operation <strong>$operation</strong> on <strong>$data_label</strong>$comment with permission(s) " . $this->permissionLabel($permissions));
     }
 
     // Test authenticated users without all permissions.
@@ -117,7 +118,7 @@ class AccessibleInterfaceWebTestBase extends WebTestBase {
       drupal_static_reset();
       drupal_flush_all_caches();
       $can = $access['authenticated_without_permissions'] ? 'can' : 'cannot';
-      $this->assertFalse($data->access($operation, $authenticated), "An authenticated user (UID $authenticated->uid) $can perform operation <strong>$operation</strong> on <strong>$data_label</strong>$comment without permission " . $this->permissionLabel(array($permissions[$i])));
+      $this->assertFalse($data->access($operation, $authenticated), "An authenticated user (UID $uid) $can perform operation <strong>$operation</strong> on <strong>$data_label</strong>$comment without permission " . $this->permissionLabel(array($permissions[$i])));
     }
   }
 }
diff --git a/payment/lib/Drupal/payment/Tests/Plugin/Core/entity/PaymentAccessControllerTest.php b/payment/lib/Drupal/payment/Tests/Plugin/Core/entity/PaymentAccessControllerTest.php
new file mode 100644
index 0000000..1b0be24
--- /dev/null
+++ b/payment/lib/Drupal/payment/Tests/Plugin/Core/entity/PaymentAccessControllerTest.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Contains class \Drupal\payment\Tests\PaymentAccessControllerTest.
+ */
+
+namespace Drupal\payment\Tests\Plugin\Core\entity;
+
+use Drupal\payment\Tests\AccessibleInterfaceWebTestBase;
+use Drupal\payment\Tests\Utility;
+
+/**
+ * Tests \Drupal\payment\Plugin\Core\entity\PaymentAccessController.
+ */
+class PaymentAccessControllerTest extends AccessibleInterfaceWebTestBase {
+
+  public static $modules = array('payment');
+
+  /**
+   * {@inheritdoc}
+   */
+  static function getInfo() {
+    return array(
+      'name' => '\Drupal\payment\Plugin\Core\entity\PaymentAccessController',
+      'group' => 'Payment',
+    );
+  }
+
+  /**
+   * Tests access control.
+   */
+  function testAccessControl() {
+    $payment_1 = Utility::createPayment(1);
+    $payment_2 = Utility::createPayment(2);
+    $authenticated = $this->drupalCreateUser();
+
+    // Create a new payment.
+    $this->assertDataAccess(entity_create('payment', array()), 'a payment', 'create', $authenticated, array(), array(
+      'anonymous' => TRUE,
+      'authenticated_without_permissions' => TRUE,
+    ));
+
+    // Test deleting, updating and viewing a payment.
+    $operations = array('delete', 'update', 'view');
+    foreach ($operations as $operation) {
+      // Test a payment that belongs to user 1.
+      $data_label = 'a payment with UID ' . $payment_1->getOwnerId();
+      $this->assertDataAccess($payment_1, $data_label, $operation, $authenticated, array("payment.payment.$operation.any"));
+      $this->assertDataAccess($payment_1, $data_label, $operation, $authenticated, array("payment.payment.$operation.own"), array(
+        'authenticated_with_permissions' => FALSE,
+      ));
+      $this->assertDataAccess($payment_1, $data_label, $operation, $authenticated);
+
+      // Test a payment that belongs to user 2.
+      $data_label = 'a payment with UID ' . $payment_2->getOwnerId();
+      $this->assertDataAccess($payment_2, $data_label, $operation, $authenticated, array("payment.payment.$operation.any"));
+      $this->assertDataAccess($payment_2, $data_label, $operation, $authenticated, array("payment.payment.$operation.own"));
+      $this->assertDataAccess($payment_2, $data_label, $operation, $authenticated);
+    }
+  }
+}
diff --git a/payment/lib/Drupal/payment/Tests/Plugin/Core/entity/PaymentStorageControllerTest.php b/payment/lib/Drupal/payment/Tests/Plugin/Core/entity/PaymentStorageControllerTest.php
new file mode 100644
index 0000000..0d26b35
--- /dev/null
+++ b/payment/lib/Drupal/payment/Tests/Plugin/Core/entity/PaymentStorageControllerTest.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @file
+ * Contains class \Drupal\payment\Tests\Plugin\Core\entity\PaymentStorageControllerTest.
+ */
+
+namespace Drupal\payment\Tests\Plugin\Core\entity;
+
+use Drupal\payment\Plugin\Core\entity\PaymentInterface;
+use Drupal\payment\Tests\Utility;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests \Drupal\payment\Plugin\Core\entity\PaymentStorageController.
+ */
+class PaymentStorageControllerTest extends WebTestBase {
+
+  public static $modules = array('payment');
+
+  /**
+   * {@inheritdoc}
+   */
+  static function getInfo() {
+    return array(
+      'name' => '\Drupal\payment\Plugin\Core\entity\PaymentStorageController',
+      'group' => 'Payment',
+    );
+  }
+
+  /**
+   * Tests create();
+   */
+  function testCreate() {
+    $payment = entity_create('payment', array());
+    $this->assertTrue($payment instanceof PaymentInterface);
+    $this->assertEqual(count($payment->validate()), 0);
+  }
+
+  /**
+   * Tests save();
+   */
+  function testSave() {
+    $payment = Utility::createPayment(1);
+    $this->assertFalse($payment->id());
+    $payment->save();
+    $this->assertTrue($payment->id());
+    $payment_loaded = entity_load_unchanged('payment', $payment->id());
+    $this->assertEqual(count($payment_loaded->getLineItems()), count($payment->getLineItems()));
+    $this->assertEqual(count($payment_loaded->getStatuses()), count($payment->getStatuses()));
+  }
+
+  /**
+   * Tests delete();
+   */
+  function testDelete() {
+    $payment = Utility::createPayment(1);
+    $payment->save();
+    $this->assertTrue($payment->id());
+    $payment->delete();
+    $this->assertFalse(entity_load('payment', $payment->id()));
+    $line_items_exists = db_select('payment_line_item')
+      ->condition('payment_id', $payment->id())
+      ->countQuery()
+      ->execute()
+      ->fetchField();
+    $this->assertFalse($line_items_exists);
+    $statuses_exist = db_select('payment_status')
+      ->condition('payment_id', $payment->id())
+      ->countQuery()
+      ->execute()
+      ->fetchField();
+    $this->assertFalse($statuses_exist);
+  }
+}
diff --git a/payment/lib/Drupal/payment/Tests/Plugin/Core/entity/PaymentTest.php b/payment/lib/Drupal/payment/Tests/Plugin/Core/entity/PaymentTest.php
new file mode 100644
index 0000000..038b495
--- /dev/null
+++ b/payment/lib/Drupal/payment/Tests/Plugin/Core/entity/PaymentTest.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains class \Drupal\payment\Tests\Plugin\Core\entity\PaymentTest.
+ */
+
+namespace Drupal\payment\Tests\Plugin\Core\entity;
+
+use Drupal\payment\Plugin\Core\entity\Payment;
+use Drupal\simpletest\DrupalUnitTestBase;
+
+/**
+ * Tests \Drupal\payment\Plugin\Core\entity\Payment.
+ */
+class PaymentTest extends DrupalUnitTestBase {
+
+  public static $modules = array('payment', 'system');
+
+  /**
+   * {@inheritdoc}
+   */
+  static function getInfo() {
+    return array(
+      'name' => '\Drupal\payment\Plugin\Core\entity\Payment',
+      'group' => 'Payment',
+    );
+  }
+
+  /**
+   * Tests label().
+   */
+  function testLabel() {
+    $payment = entity_create('payment', array());
+    $this->assertIdentical($payment->label(), 'Payment ');
+  }
+}
diff --git a/payment/lib/Drupal/payment/Tests/Plugin/payment/line_item/BaseTest.php b/payment/lib/Drupal/payment/Tests/Plugin/payment/line_item/BaseTest.php
new file mode 100644
index 0000000..98c703b
--- /dev/null
+++ b/payment/lib/Drupal/payment/Tests/Plugin/payment/line_item/BaseTest.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Contains class \Drupal\payment\Tests\Plugin\payment\status\BaseTest.
+ */
+
+namespace Drupal\payment\Tests\Plugin\payment\line_item;
+
+use Drupal\payment\Plugin\payment\line_item\LineItemInterface;
+use Drupal\simpletest\DrupalUnitTestBase;
+
+/**
+ * Tests \Drupal\payment\Plugin\payment\status\Base.
+ */
+class BaseTest extends DrupalUnitTestBase {
+
+  public static $modules = array('payment');
+
+  /**
+   * {@inheritdoc}
+   */
+  static function getInfo() {
+    return array(
+      'name' => '\Drupal\payment\Plugin\payment\line_item\Base',
+      'group' => 'Payment',
+    );
+  }
+
+  /**
+   * {@inheritdoc
+   */
+  function setUp() {
+    parent::setUp();
+    $this->lineItem = \Drupal::service('plugin.manager.payment.line_item')->createInstance('payment_basic');
+  }
+
+  /**
+   * Tests setAmount() and getAmount().
+   */
+  function testGetAmount() {
+    $amount = 5.3;
+    $this->assertTrue($this->lineItem->setAmount($amount) instanceof LineItemInterface);
+    $this->assertIdentical($this->lineItem->getAmount(), $amount);
+  }
+
+  /**
+   * Tests setQuantity() and getQuantity().
+   */
+  function testGetQuantity() {
+    $quantity = 7;
+    $this->assertTrue($this->lineItem->setQuantity($quantity) instanceof LineItemInterface);
+    $this->assertIdentical($this->lineItem->getQuantity(), $quantity);
+  }
+
+  /**
+   * Tests getTotalAmount().
+   */
+  function testGetTotalAmount() {
+    $amount= 7;
+    $quantity = 7;
+    $this->lineItem->setAmount($amount);
+    $this->lineItem->setQuantity($quantity);
+    $this->assertIdentical($this->lineItem->getTotalAmount(), 49);
+  }
+
+  /**
+   * Tests setName() and getName().
+   */
+  function testGetName() {
+    $name = $this->randomName();
+    $this->assertTrue($this->lineItem->setName($name) instanceof LineItemInterface);
+    $this->assertIdentical($this->lineItem->getName(), $name);
+  }
+
+  /**
+   * Tests setPaymentId() and getPaymentId().
+   */
+  function testGetPaymentId() {
+    $id = 7;
+    $this->assertTrue($this->lineItem->setPaymentId($id) instanceof LineItemInterface);
+    $this->assertIdentical($this->lineItem->getPaymentId(), $id);
+  }
+}
diff --git a/payment/lib/Drupal/payment/Tests/Plugin/payment/line_item/ManagerTest.php b/payment/lib/Drupal/payment/Tests/Plugin/payment/line_item/ManagerTest.php
new file mode 100644
index 0000000..b5aafec
--- /dev/null
+++ b/payment/lib/Drupal/payment/Tests/Plugin/payment/line_item/ManagerTest.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Contains class \Drupal\payment\Tests\Plugin\payment\line_item\ManagerTest.
+ */
+
+namespace Drupal\payment\Tests\Plugin\payment\line_item;
+
+use Drupal\simpletest\DrupalUnitTestBase;
+
+/**
+ * Tests \Drupal\payment\Plugin\payment\line_item\Manager.
+ */
+class ManagerTest extends DrupalUnitTestBase {
+
+  public static $modules = array('payment');
+
+  /**
+   * {@inheritdoc}
+   */
+  static function getInfo() {
+    return array(
+      'name' => '\Drupal\payment\Plugin\payment\line_item\Manager',
+      'group' => 'Payment',
+    );
+  }
+
+  /**
+   * Tests getDefinitions().
+   */
+  function testGetDefinitions() {
+    $manager = \Drupal::service('plugin.manager.payment.line_item');
+    // Test the default line item plugins.
+    $definitions = $manager->getDefinitions();
+    $this->assertEqual(count($definitions), 1);
+    foreach ($definitions as $definition) {
+      $this->assertIdentical(strpos($definition['id'], 'payment_'), 0);
+      $this->assertTrue(is_subclass_of($definition['class'], '\Drupal\payment\Plugin\payment\line_item\LineItemInterface'));
+    }
+  }
+}
diff --git a/payment/lib/Drupal/payment/Tests/Plugin/payment/status/BaseTest.php b/payment/lib/Drupal/payment/Tests/Plugin/payment/status/BaseTest.php
new file mode 100644
index 0000000..38e883d
--- /dev/null
+++ b/payment/lib/Drupal/payment/Tests/Plugin/payment/status/BaseTest.php
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * @file
+ * Contains class \Drupal\payment\Tests\Plugin\payment\status\BaseTest.
+ */
+
+namespace Drupal\payment\Tests\Plugin\payment\status;
+
+use Drupal\simpletest\DrupalUnitTestBase;
+use Drupal\payment\Plugin\payment\status\PaymentStatusInterface;
+
+/**
+ * Tests \Drupal\payment\Plugin\payment\status\Base.
+ */
+class BaseTest extends DrupalUnitTestBase {
+
+  public static $modules = array('payment');
+
+  /**
+   * {@inheritdoc}
+   */
+  static function getInfo() {
+    return array(
+      'name' => '\Drupal\payment\Plugin\payment\status\Base',
+      'group' => 'Payment',
+    );
+  }
+
+  /**
+   * {@inheritdoc
+   */
+  function setup() {
+    parent::setUp();
+    $this->manager = \Drupal::service('plugin.manager.payment.status');
+    $this->status = $this->manager->createInstance('payment_created');
+  }
+
+  /**
+   * Tests setCreated() and getCreated().
+   */
+  function testGetCreated() {
+    $created = 123;
+    $this->assertTrue($this->status->setCreated($created) instanceof PaymentStatusInterface);
+    $this->assertIdentical($this->status->getCreated(), $created);
+  }
+
+  /**
+   * Tests setPaymentId() and getPaymentId().
+   */
+  function testGetPaymentId() {
+    $id = 7;
+    $this->assertTrue($this->status->setPaymentId($id) instanceof PaymentStatusInterface);
+    $this->assertIdentical($this->status->getPaymentId(), $id);
+  }
+
+  /**
+   * Tests setId() and getId().
+   */
+  function testGetId() {
+    $id= 7;
+    $this->assertTrue($this->status->setId($id) instanceof PaymentStatusInterface);
+    $this->assertIdentical($this->status->getId(), $id);
+  }
+
+  /**
+   * Tests getChildren().
+   */
+  function testGetChildren() {
+    $status = $this->manager->createInstance('payment_no_money_transferred');
+    $expected = array('payment_created', 'payment_failed', 'payment_pending');
+    $this->assertEqual($status->getChildren(), $expected);
+  }
+
+  /**
+   * Tests getDescendants().
+   */
+  function testGetDescendants() {
+    $status = $this->manager->createInstance('payment_no_money_transferred');
+    $expected = array('payment_created', 'payment_failed', 'payment_pending', 'payment_authorization_failed', 'payment_cancelled', 'payment_expired');
+    $this->assertEqual($status->getDescendants(), $expected);
+  }
+
+  /**
+   * Tests getAncestors().
+   */
+  function testGetAncestors() {
+    $status = $this->manager->createInstance('payment_authorization_failed');
+    $expected = array('payment_failed', 'payment_no_money_transferred');
+    $this->assertEqual($status->getAncestors(), $expected);
+  }
+
+  /**
+   * Tests hasAncestor().
+   */
+  function testHasAncestor() {
+    $status = $this->manager->createInstance('payment_failed');
+    $this->assertTrue($status->isOrHasAncestor('payment_no_money_transferred'));
+  }
+
+  /**
+   * Tests isOrHasAncestor().
+   */
+  function testIsOrHasAncestor() {
+    $status = $this->manager->createInstance('payment_failed');
+    $this->assertTrue($status->isOrHasAncestor('payment_failed'));
+    $this->assertTrue($status->isOrHasAncestor('payment_no_money_transferred'));
+  }
+}
diff --git a/payment/lib/Drupal/payment/Tests/Plugin/payment/status/ManagerTest.php b/payment/lib/Drupal/payment/Tests/Plugin/payment/status/ManagerTest.php
new file mode 100644
index 0000000..e79f7ce
--- /dev/null
+++ b/payment/lib/Drupal/payment/Tests/Plugin/payment/status/ManagerTest.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Contains class \Drupal\payment\Tests\Plugin\payment\status\ManagerTest.
+ */
+
+namespace Drupal\payment\Tests\Plugin\payment\status;
+
+use Drupal\simpletest\DrupalUnitTestBase;
+
+/**
+ * Tests \Drupal\payment\Plugin\payment\status\Manager.
+ */
+class ManagerTest extends DrupalUnitTestBase {
+
+  public static $modules = array('payment');
+
+  /**
+   * {@inheritdoc}
+   */
+  static function getInfo() {
+    return array(
+      'name' => '\Drupal\payment\Plugin\payment\status\Manager',
+      'group' => 'Payment',
+    );
+  }
+
+  /**
+   * Tests getDefinitions().
+   */
+  function testGetDefinitions() {
+    $manager = \Drupal::service('plugin.manager.payment.status');
+    // Test the default status plugins.
+    $definitions = $manager->getDefinitions();
+    $this->assertEqual(count($definitions), 10);
+    foreach ($definitions as $definition) {
+      $this->assertIdentical(strpos($definition['id'], 'payment_'), 0);
+      $this->assertTrue(is_subclass_of($definition['class'], '\Drupal\payment\Plugin\payment\status\PaymentStatusInterface'));
+    }
+  }
+}
diff --git a/payment/lib/Drupal/payment/Tests/UpgradeMap.php b/payment/lib/Drupal/payment/Tests/UpgradeMap.php
new file mode 100644
index 0000000..8b64849
--- /dev/null
+++ b/payment/lib/Drupal/payment/Tests/UpgradeMap.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Contains class \Drupal\payment\Tests\UpgradeMap.
+ */
+
+namespace Drupal\payment\Tests;
+
+use Drupal\simpletest\DrupalUnitTestBase;
+
+/**
+ * Tests the payment_upgrade_map_*() functions.
+ */
+class UpgradeMap extends DrupalUnitTestBase {
+
+  public static $modules = array('payment');
+
+  /**
+   * {@inheritdoc}
+   */
+  static function getInfo() {
+    return array(
+      'name' => 'Upgrade maps',
+      'group' => 'Payment',
+    );
+  }
+
+  /**
+   * Tests payment_upgrade_map_status().
+   */
+  function testStatus() {
+    module_load_install('payment');
+    $manager = \Drupal::service('plugin.manager.payment.status');
+    $pluginIds = array_keys($manager->getDefinitions());
+    $this->assertFalse(array_diff(payment_upgrade_map_status(), $pluginIds));
+  }
+
+  /**
+   * Tests payment_upgrade_map_payment_method().
+   */
+  function testPaymentMethod() {
+    module_load_install('payment');
+    $manager = \Drupal::service('plugin.manager.payment.payment_method');
+    $pluginIds = array_keys($manager->getDefinitions());
+    $this->assertFalse(array_diff(payment_upgrade_map_payment_method(), $pluginIds));
+  }
+}
diff --git a/payment/lib/Drupal/payment/Tests/UpgradePathWithContent.php b/payment/lib/Drupal/payment/Tests/UpgradePathWithContent.php
new file mode 100644
index 0000000..2228466
--- /dev/null
+++ b/payment/lib/Drupal/payment/Tests/UpgradePathWithContent.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Contains class Drupal\payment\Tests\UpgradePathWithContent.
+ */
+
+namespace Drupal\payment\Tests;
+
+use Drupal\system\Tests\Upgrade\UpgradePathTestBase;
+
+/**
+ * Tests Payment's upgrade path.
+ */
+class UpgradePathWithContent extends UpgradePathTestBase {
+
+  static function getInfo() {
+    return array(
+      'name'  => 'Upgrade path (with existing content and configuration)',
+      'group' => 'Payment',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function setUp() {
+    $this->databaseDumpFiles = array(
+      drupal_get_path('module', 'payment') . '/../payment-database-dump.php',
+      drupal_get_path('module', 'payment') . '/../payment-database-dump-content.php',
+    );
+    parent::setUp();
+  }
+
+  /**
+   * Tests a successful upgrade.
+   */
+  function testPaymentUpgrade() {
+    $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.');
+  }
+}
diff --git a/payment/lib/Drupal/payment/Tests/UpgradePathWithoutContent.php b/payment/lib/Drupal/payment/Tests/UpgradePathWithoutContent.php
new file mode 100644
index 0000000..743face
--- /dev/null
+++ b/payment/lib/Drupal/payment/Tests/UpgradePathWithoutContent.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains class Drupal\payment\Tests\UpgradePathWithContent.
+ */
+
+namespace Drupal\payment\Tests;
+
+use Drupal\system\Tests\Upgrade\UpgradePathTestBase;
+
+/**
+ * Tests Payment's upgrade path.
+ */
+class UpgradePathWithoutContent extends UpgradePathTestBase {
+
+  static function getInfo() {
+    return array(
+      'name'  => 'Upgrade path (without existing content and configuration)',
+      'group' => 'Payment',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function setUp() {
+    $this->databaseDumpFiles[] = drupal_get_path('module', 'payment') . '/../payment-database-dump.php';
+    parent::setUp();
+  }
+
+  /**
+   * Tests a successful upgrade.
+   */
+  function testPaymentUpgrade() {
+    $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.');
+  }
+}
diff --git a/payment/lib/Drupal/payment/Tests/Utility.php b/payment/lib/Drupal/payment/Tests/Utility.php
index 88f45fb..9d7c35a 100644
--- a/payment/lib/Drupal/payment/Tests/Utility.php
+++ b/payment/lib/Drupal/payment/Tests/Utility.php
@@ -8,7 +8,8 @@
 namespace Drupal\payment\Tests;
 
 use Drupal\payment\Plugin\payment\PaymentMethod\PaymentMethodInterface as PluginPaymentMethodInterface;
-use Drupal\Tests\UnitTestCase;
+use Drupal\payment\Plugin\payment\PaymentMethod\Unavailable;
+use Drupal\Component\Utility\Random;
 
 /**
  * Provides utility tools to support tests.
@@ -18,28 +19,22 @@ class Utility {
   /**
    * Creates a payment.
    *
-   * @todo port to D8.
-   *
    * @param integer $uid
    *   The user ID of the payment's owner.
    * @param PaymentMethod $payment_method
-   *   An optional payment method to set. Defaults to PaymentMethodUnavailable.
+   *   An optional payment method to set. Defaults to Unavailable.
    *
-   * @return Payment
+   * @return \Drupal\payment\Plugin\Core\entity\Payment
    */
   static function createPayment($uid, PaymentMethod $payment_method = NULL) {
-    $payment_method = $payment_method ? $payment_method : new PaymentMethodUnavailable;
-    $payment = new Payment(array(
-      'currency_code' => 'XXX',
-      'description' => 'This is the payment description',
-      'finish_callback' => 'payment_test_finish_callback',
-      'method' => $payment_method,
-      'uid' => $uid,
-    ));
-    $payment->setLineItem(new PaymentLineItem(array(
+    $lineItemManager = \Drupal::service('plugin.manager.payment.line_item');
+    $payment = entity_create('payment', array());
+    $payment->setFinishCallback('payment_test_finish_callback');
+    $payment->setPaymentMethodId('payment_unavailable');
+    $payment->setOwnerId($uid);
+    $payment->setLineItem($lineItemManager->createInstance('payment_basic', array(
       'name' => 'foo',
       'amount' => 1.0,
-      'tax_rate' => 0.1,
     )));
 
     return $payment;
@@ -56,7 +51,7 @@ class Utility {
    * @return PaymentMethod
    */
   static function createPaymentMethod($uid, PaymentMethodInterface $plugin = NULL) {
-    $name = UnitTestCase::randomName();
+    $name = Random::name();
     $plugin = $plugin ? $plugin : \Drupal::service('plugin.manager.payment.payment_method')->createInstance('payment_unavailable');
     $payment_method = entity_create('payment_method', array(
       'plugin' => $plugin,
diff --git a/payment/payment.api.php b/payment/payment.api.php
index 65fdef7..3b81634 100644
--- a/payment/payment.api.php
+++ b/payment/payment.api.php
@@ -5,33 +5,18 @@
  * Hook documentation.
  */
 
-/**
- * Defines payment statuses.
- *
- * @return array
- *   An array with PaymentStatusInfo objects.
- */
-function hook_payment_status_info() {
-  return array(
-    new PaymentStatusInfo(array(
-      'description' => t('Foo payments are still being processed by Bar to guarantee their authenticity.'),
-      'status' => PAYMENT_STATUS_FOO,
-      'parent' => PAYMENT_STATUS_PENDING,
-      'title' => t('Pending (waiting for Bar authentication)'),
-    )),
-  );
-}
+use Drupal\payment\Plugin\Core\Entity\PaymentInterface;
+use Drupal\payment\Plugin\payment\status\PaymentStatusInterface;
 
 /**
- * Alters payment statuses.
+ * Alters payment status plugins.
  *
- * @param array $statuses_info
- *   An array with PaymentStatusInfo objects.
- *
- * @return NULL
+ * @param array $definitions
+ *   Keys are plugin IDs. Values are plugin definitions.
  */
-function hook_payment_status_info_alter(array &$statuses_info) {
-  $statuses_info[PAYMENT_STATUS_FAILED]->title = 'Something went wrong!';
+function hook_payment_status_alter(array &$definitions) {
+  // Rename a plugin.
+  $definitions['payment_failed']['label'] = 'Something went wrong!';
 }
 
 /**
@@ -49,42 +34,12 @@ function hook_payment_method_alter(array &$definitions) {
 }
 
 /**
- * Defines line item types.
+ * Alters line item plugins.
  *
- * @see Payment::getLineItems()
- *
- * @return array
- *   An array with PaymentLineItemInfo objects.
- */
-function hook_payment_line_item_info() {
-  return array(
-    new PaymentLineItemInfo(array(
-      'name' => 'foo_fee_credit_card',
-      'title' => t('Credit card fee'),
-    )),
-    new PaymentLineItemInfo(array(
-      'name' => 'foo_fee_wire_transfer',
-      'title' => t('Wire transfer fee'),
-    )),
-    new PaymentLineItemInfo(array(
-      // Use a custom callback, so we can get any/all line items we need
-      // simultaneously.
-      'callback' => 'foo_payment_line_item_get_fee',
-      'name' => 'foo_fee',
-      'title' => t('Any payment method fee'),
-    )),
-  );
-}
-
-/**
- * Alters line item types.
- *
- * @param array $line_items_info
- *   An array with PaymentLineItemInfo objects, keyed by PaymentLineItemInfo::name.
+ * @param array $definitions
+ *   Keys are plugin IDs. Values are plugin definitions.
  */
-function hook_payment_line_item_info_alter(array &$line_items_info) {
-  // Set a callback for a line item.
-  $line_items_info['foo_fee_credit_card']['callback'] = 'foo_payment_line_item_get';
+function hook_payment_line_item_alter(array &$definitions) {
 }
 
 /**
@@ -92,13 +47,13 @@ function hook_payment_line_item_info_alter(array &$line_items_info) {
  *
  * @see Payment::setStatus()
  *
- * @param Payment $payment
- * @param PaymentStatusItem $previous_status_item
+ * @param \Drupal\payment\Plugin\Core\Entity\PaymentInterface $payment
+ * @param \Drupal\payment\Plugin\payment\status\PaymentStatusInterface $previous_status_item
  *   The status the payment had before it was set.
  *
  * @return NULL
  */
-function hook_payment_status_change(Payment $payment, PaymentStatusItem $previous_status_item) {
+function hook_payment_status_change(PaymentInterface $payment, PaymentStatusInterface $previous_status_item) {
   // Notify the site administrator, for instance.
 }
 
@@ -108,17 +63,15 @@ function hook_payment_status_change(Payment $payment, PaymentStatusItem $previou
  *
  * @see Payment::execute()
  *
- * @param Payment $payment
+ * @param \Drupal\payment\Plugin\Core\Entity\PaymentInterface $payment
  *
  * @return NULL
  */
-function hook_payment_pre_execute(Payment $payment) {
+function hook_payment_pre_execute(PaymentInterface $payment) {
   // Add a payment method processing fee.
-  $payment->setLineItem(new PaymentLineItem(array(
+  $payment->setLineItem(\Drupal::service('plugin.manager.payment.line_item')->createInstance('payment_basic', array(
     'name' => 'foo_fee',
     'amount' => 5.50,
-    'description' => 'Credit card fee',
-    'tax_rate' => 0.19,
   )));
 }
 
@@ -127,12 +80,12 @@ function hook_payment_pre_execute(Payment $payment) {
  *
  * @see Payment::finish()
  *
- * @param Payment $payment
+ * @param \Drupal\payment\Plugin\Core\Entity\PaymentInterface $payment
  *
  * @return NULL
  */
-function hook_payment_pre_finish(Payment $payment) {
-  if (payment_status_is_or_has_ancestor($payment->getStatus()->status, PAYMENT_STATUS_SUCCESS)) {
+function hook_payment_pre_finish(PaymentInterface $payment) {
+  if ($payment->getStatus()->isOrHasAncestor('payment_success')) {
     drupal_set_message(t('Your payment was successfully completed.'));
   }
   else {
@@ -147,15 +100,15 @@ function hook_payment_pre_finish(Payment $payment) {
  * different payment methods, for example when looking for payment methods that
  * are capable of processing a payment.
  *
- * @param Payment $payment
- *   $payment->method contains the method currently configured, but NOT the
+ * @param \Drupal\payment\Plugin\Core\Entity\PaymentInterface $payment
+ *   $payment->getMethod() contains the method currently configured, but NOT the
  *   method that $payment should be tested against, which is $payment_method.
  * @param PaymentMethod $payment_method
  *
  * @return boolean
  *   Whether the payment and/or the payment method are valid.
  */
-function hook_payment_validate(Payment $payment, PaymentMethod $payment_method) {}
+function hook_payment_validate(PaymentInterface $payment, PaymentMethod $payment_method) {}
 
 /**
  * Alter the payment form.
diff --git a/payment/payment.classes.inc b/payment/payment.classes.inc
index d1a187b..af8cb4f 100644
--- a/payment/payment.classes.inc
+++ b/payment/payment.classes.inc
@@ -17,469 +17,6 @@ class PaymentCommon {
 }
 
 /**
- * A single payment. Contains all payment-specific data.
- *
- * @see PaymentMethod
- * @see PaymentMethodController
- */
-class Payment extends PaymentCommon {
-
-  /**
-   * The machine name of the context that created this Payment, such as the
-   * webshop module.
-   *
-   * @var string
-   */
-  public $context = '';
-
-  /**
-   * Information about this payment that is specific to the context that
-   * created it, such as the webshop module.
-   *
-   * @var array
-   */
-  public $context_data = array();
-
-  /**
-   * The ISO 4217 currency code of the payment amount.
-   *
-   * @var string
-   */
-  public $currency_code = 'XXX';
-
-  /**
-   * The general description of this payment. Not to be confused with line item
-   * descriptions.
-   *
-   * @var string
-   */
-  public $description = '';
-
-  /**
-   * Arguments to pass on to t() when translating $this->description.
-   *
-   * @var array
-   */
-  public $description_arguments = array();
-
-  /**
-   * The name of a function to call when payment execution is completed,
-   * regardless of the payment status. It receives one argument:
-   * - $payment Payment
-   *   The Payment object.
-   * The callback does not need to return anything and is free to redirect the
-   * user or display something.
-   * Use Payment::context_data to pass on arbitrary data to the finish callback.
-   *
-   * @var string
-   */
-  public $finish_callback = '';
-
-  /**
-   * An array with PaymentLineItem objects.
-   *
-   * @see Payment::setLineItem()
-   *
-   * @var array
-   */
-  public $line_items = array();
-
-  /**
-   * The payment method used for this payment.
-   *
-   * @var PaymentMethod
-   */
-  public $method = NULL;
-
-  /**
-   * Information about this payment that is specific to its payment method.
-   *
-   * @var array
-   */
-  public $method_data = array();
-
-  /**
-   * The internal ID of this payment.
-   *
-   * @var integer
-   */
-  public $pid = 0;
-
-  /**
-   * The status log. Contains PaymentStatusItem objects ordered by datetime. Do
-   * not set directly, but use Payment::setStatus() instead.
-   *
-   * @see Payment::setStatus()
-   *
-   * @var array
-   */
-  public $statuses = array();
-
-  /**
-   * The UID of the user this payment belongs to.
-   *
-   * @var integer
-   */
-  public $uid = NULL;
-
-  /**
-   * Constructor.
-   *
-   * @param array $properties
-   *   An associative array. Keys are property names and values are property
-   *   values.
-   *
-   * @return NULL
-   */
-  function __construct(array $properties = array()) {
-    global $user;
-
-    parent::__construct($properties);
-
-    if (is_null($this->uid)) {
-      $this->uid = $user->uid;
-    }
-    if (!$this->statuses) {
-      // We purposefully avoid Payment::setStatus(), because this is the
-      // payment's first status.
-      $this->statuses[] = new PaymentStatusItem(PAYMENT_STATUS_NEW);
-    }
-  }
-
-  /**
-   * Execute the actual payment.
-   *
-   * @return NULL
-   */
-  function execute() {
-    // Preprocess the payment.
-    module_invoke_all('payment_pre_execute', $this);
-    if (module_exists('rules')) {
-      rules_invoke_event('payment_pre_execute', $this);
-    }
-    // Execute the payment.
-    if ($this->method) {
-      try {
-        $this->method->validate($this);
-        $this->setStatus(new PaymentStatusItem(PAYMENT_STATUS_PENDING));
-        $this->method->controller->execute($this);
-      }
-      // An exception was thrown during validation.
-      catch (PaymentValidationException $e) {
-        $this->setStatus(new PaymentStatusItem(PAYMENT_STATUS_FAILED));
-      }
-    }
-    // There is no payment method.
-    else {
-      $this->setStatus(new PaymentStatusItem(PAYMENT_STATUS_FAILED));
-    }
-    // This is only called if the payment execution didn't redirect the user
-    // offsite. Otherwise it's the payment method return page's responsibility.
-    $this->finish();
-  }
-
-  /**
-   * Finish the payment after its execution.
-   *
-   * @return NULL
-   */
-  function finish() {
-    entity_save('payment', $this);
-    module_invoke_all('payment_pre_finish', $this);
-    if (module_exists('rules')) {
-      rules_invoke_event('payment_pre_finish', $this);
-    }
-    call_user_func($this->finish_callback, $this);
-  }
-
-  /**
-   * Set a line item.
-   *
-   * @see Payment::getLineItems()
-   *
-   * @param PaymentLineItem $line_item
-   *
-   * @return Payment
-   */
-  function setLineItem(PaymentLineItem $line_item) {
-    $this->line_items[$line_item->name] = $line_item;
-
-    return $this;
-  }
-
-  /**
-   * Get line items.
-   *
-   * @param string $name (optional)
-   *   The requested line item(s)'s machine name, as possibly defined in
-   *   hook_payment_line_item_info(). If $name is NULL, then all line items
-   *   will be returned.
-   *
-   * @return array
-   *   An array with matching PaymentLineItem objects.
-   */
-  function getLineItems($name = NULL) {
-    if (is_null($name)) {
-      return payment_line_item_get_all($name, $this);
-    }
-    elseif ($line_item_info = payment_line_item_info($name)) {
-      return call_user_func($line_item_info->callback, $name, $this);
-    }
-    else {
-      return payment_line_item_get_specific($name, $this);
-    }
-  }
-
-  /**
-   * Get the total payment amount.
-   *
-   * @param boolean $tax
-   *   Whether to include taxes or not.
-   * @param array $line_items
-   *   An array with PaymentLineItem objects to calculate the total from.
-   *   Leave empty to use $this->line_items.
-   *
-   * @return float
-   */
-  function totalAmount($tax, array $line_items = NULl) {
-    $total = 0;
-    if (is_null($line_items)) {
-      $line_items = $this->line_items;
-    }
-    foreach ($line_items as $line_item) {
-      $total += $line_item->totalAmount($tax);
-    }
-
-    return $total;
-  }
-
-  /**
-   * Set the payment status.
-   *
-   * @param PaymentStatusItem $status_item
-   *
-   * @return Payment
-   */
-  function setStatus(PaymentStatusItem $status_item) {
-    $previous_status_item = $this->getStatus();
-    $status_item->pid = $this->pid;
-    // Prevent duplicate status items.
-    if ($this->getStatus()->status != $status_item->status) {
-      $this->statuses[] = $status_item;
-    }
-    foreach (module_implements('payment_status_change') as $module_name) {
-      call_user_func($module_name . '_payment_status_change', $this, $previous_status_item);
-      // If a hook invocation has added another log item, a new loop with
-      // invocations has already been executed and we don't need to continue
-      // with this one.
-      if ($this->getStatus()->status != $status_item->status) {
-        return;
-      }
-    }
-    if (module_exists('rules')) {
-      rules_invoke_event('payment_status_change', $this, $previous_status_item);
-    }
-
-    return $this;
-  }
-
-  /**
-   * Get the current payment status.
-   *
-   * @return PaymentStatusItem
-   */
-  function getStatus() {
-    return end($this->statuses);
-  }
-
-  /**
-   * Get available/valid payment methods for this payment.
-   *
-   * @param array $payment_methods
-   *   An array with the PaymentMethod objects to check for. Use an empty
-   *   array to check the availability of all payment methods.
-   *
-   * @return array
-   *   An array with PaymentMethod objects usable for Payment in its current
-   *   state, keyed by PMID.
-   */
-  function availablePaymentMethods(array $payment_methods = array()) {
-    if (!$payment_methods) {
-      $payment_methods = entity_load('payment_method', FALSE);
-    }
-    $available = array();
-    foreach ($payment_methods as $payment_method) {
-      try {
-        $payment_method->validate($this, FALSE);
-        $available[$payment_method->pmid] = $payment_method;
-      }
-      catch (PaymentValidationException $e) {
-      }
-    }
-
-    return $available;
-  }
-}
-
-/**
- * Entity API controller for payment entities.
- */
-class PaymentEntityController extends EntityAPIController {
-
-  /**
-   * Implements EntityAPIController::load().
-   */
-  function load($ids = array(), $conditions = array()) {
-    $entities = parent::load($ids, $conditions);
-    foreach ($entities as $payment) {
-      // Cast non-string scalars to their original types, because some backends
-      // store/return all variables as strings.
-      $payment->pid = (int) $payment->pid;
-      $payment->uid = (int) $payment->uid;
-    }
-
-    return $entities;
-  }
-
-  /**
-   * Implements EntityAPIController::attachLoad().
-   */
-  function attachLoad(&$queried_entities, $revision_id = FALSE) {
-    $pids = array_keys($queried_entities);
-
-    // Load the payments's payment methods.
-    $pmids = array();
-    foreach ($queried_entities as $payment) {
-      $pmids[] = $payment->pmid;
-    }
-    $methods = entity_load('payment_method', $pmids);
-
-    // Load line items.
-    $result = db_select('payment_line_item')
-      ->fields('payment_line_item')
-      ->condition('pid', $pids)
-      ->execute();
-    $line_items = array();
-    while ($line_item_data = $result->fetchAssoc('name', PDO::FETCH_ASSOC)) {
-      $line_item_data['description_arguments'] = unserialize($line_item_data['description_arguments']);
-      $line_item_data['amount'] = (float) $line_item_data['amount'];
-      $line_item_data['tax_rate'] = (float) $line_item_data['tax_rate'];
-      $line_item_data['quantity'] = (int) $line_item_data['quantity'];
-      $line_items[$line_item_data['pid']][$line_item_data['name']] = new PaymentLineItem($line_item_data);
-    }
-
-    // Load the payments's status items.
-    $result = db_select('payment_status_item')
-      ->fields('payment_status_item')
-      ->condition('pid', $pids)
-      ->orderBy('psiid')
-      ->execute();
-    $status_items = array();
-    while ($data = $result->fetchObject()) {
-      $status_item = new PaymentStatusItem($data->status, $data->created, $data->pid, $data->psiid);
-      $status_items[$status_item->pid][] = $status_item;
-    }
-
-    // Add all data to the payments.
-    foreach ($queried_entities as $payment) {
-      $payment->statuses = $status_items[$payment->pid];
-      $payment->line_items = isset($line_items[$payment->pid]) ? $line_items[$payment->pid] : array();
-      $payment->method = $methods[$payment->pmid];
-      unset($payment->pmid);
-    }
-
-    parent::attachLoad($queried_entities, $revision_id);
-  }
-
-  /**
-   * Implements EntityAPIController::save().
-   */
-  function save($entity, DatabaseTransaction $transaction = NULL) {
-    $payment = $entity;
-
-    // Save the payment.
-    $payment->pmid = $payment->method->pmid;
-    $return = parent::save($payment, $transaction);
-    unset($payment->pmid);
-
-    // Save line items.
-    foreach ($payment->line_items as $line_item) {
-      db_merge('payment_line_item')
-        ->key(array(
-          'name' => $line_item->name,
-          'pid' => $payment->pid,
-        ))
-        ->fields(array(
-          'amount' => $line_item->amount,
-          'amount_total' => $line_item->amount * $line_item->quantity * ($line_item->tax_rate + 1),
-          'description' => $line_item->description,
-          'description_arguments' => serialize($line_item->description_arguments),
-          'name' => $line_item->name,
-          'pid' => $payment->pid,
-          'quantity' => $line_item->quantity,
-          'tax_rate' => $line_item->tax_rate,
-        ))
-        ->execute();
-    }
-
-    // Save the payment's status items.
-    $update = empty(reset($payment->statuses)->psiid) || empty(end($payment->statuses)->psiid);
-    foreach ($payment->statuses as $status_item) {
-      // Statuses cannot be edited, so only save the ones without a PSIID set.
-      if (!$status_item->psiid) {
-        $status_item->pid = $payment->pid;
-        drupal_write_record('payment_status_item', $status_item);
-      }
-    }
-    if ($update) {
-      $payment->psiid_first = reset($payment->statuses)->psiid;
-      $payment->psiid_last = end($payment->statuses)->psiid;
-      $query = db_update('payment')
-        ->condition('pid', $payment->pid)
-        ->fields(array(
-          'psiid_first' => reset($payment->statuses)->psiid,
-          'psiid_last' => end($payment->statuses)->psiid,
-        ));
-        $query->execute();
-    }
-
-    return $return;
-  }
-
-  /**
-   * Implements EntityAPIController::view().
-   */
-  function view($entities, $view_mode = 'full', $langcode = NULL, $page = NULL) {
-    $build = parent::view($entities, $view_mode, $langcode, $page);
-    foreach ($build['payment'] as &$payment_build) {
-      $payment = $payment_build['#entity'];
-      $payment_build['payment_line_items'] = payment_line_items($payment);
-      $payment_build['payment_status_items'] = payment_status_items($payment);
-      $payment_build['payment_method'] = array(
-        '#type' => 'item',
-        '#title' => t('Payment method'),
-        '#markup' => check_plain($payment->method->title_generic),
-      );
-    }
-
-    return $build;
-  }
-
-  /**
-   * Implements EntityAPIController::delete().
-   */
-  function delete($ids, DatabaseTransaction $transaction = NULL) {
-    parent::delete($ids, $transaction);
-    db_delete('payment_line_item')
-      ->condition('pid', $ids)
-      ->execute();
-    db_delete('payment_status_item')
-      ->condition('pid', $ids)
-      ->execute();
-  }
-}
-
-/**
  * A payment method that essentially disables payments.
  *
  * This is a 'placeholder' method that returns defaults and doesn't really do
@@ -502,239 +39,6 @@ class PaymentMethodUnavailable extends PaymentMethod {
 }
 
 /**
- * Payment status information.
- */
-class PaymentStatusInfo extends PaymentCommon {
-
-  /**
-   * A US English human-readable plain text description.
-   *
-   * @var string
-   */
-  public $description = '';
-
-  /**
-   * This status's parent status.
-   *
-   * @var array
-   */
-  public $parent = NULL;
-
-  /**
-   * The status itself.
-   *
-   * @var string
-   */
-  public $status = '';
-
-  /**
-   * A US English human-readable plain text title.
-   *
-   * @var string
-   */
-  public $title = '';
-
-  /**
-   * Get this payment status's ancestors.
-   *
-   * @return array
-   *   The machine names of this status's ancestors.
-   */
-  function ancestors() {
-    $ancestors = array($this->parent);
-    if ($this->parent) {
-      $ancestors = array_merge($ancestors, payment_status_info($this->parent)->ancestors());
-    }
-
-    return array_unique($ancestors);
-  }
-
-  /**
-   * Get this payment status's children.
-   *
-   * @return array
-   *   The machine names of this status's children.
-   */
-  function children() {
-    $children = array();
-    foreach (payment_statuses_info() as $status_info) {
-      if ($status_info->parent == $this->status) {
-        $children[] = $status_info->status;
-      }
-    }
-
-    return $children;
-  }
-
-  /**
-   * Get this payment status's descendants.
-   *
-   * @return array
-   *   The machine names of this status's descendants.
-   */
-  function descendants() {
-    $children = $this->children();
-    $descendants = $children;
-    foreach ($children as $child) {
-      $descendants = array_merge($descendants, payment_status_info($child)->descendants());
-    }
-
-    return array_unique($descendants);
-  }
-}
-
-/**
- * A payment line item.
- *
- * @see Payment::setLineItem()
- */
-class PaymentLineItem extends PaymentCommon {
-
-  /**
-   * The payment amount, excluding tax. The number of decimals depends on the
-   * ISO 4217 currency used.
-   *
-   * @var float
-   */
-  public $amount = 0.0;
-
-  /**
-   * A US English human-readable description for this line item. May contain
-   * HTML.
-   *
-   * @var string
-   */
-  public $description = '';
-
-  /**
-   * Arguments to pass on to t() when translating $this->description.
-   *
-   * @var array
-   */
-  public $description_arguments = array();
-
-  /**
-   * The unique machine name (for a certain payment).
-   *
-   * @var string
-   */
-  public $name = '';
-
-  /**
-   * The tax rate that applies to PaymentLineItem::amount. Should be a float
-   * between 0 and 1.
-   *
-   * @var float
-   */
-  public $tax_rate = 0.0;
-
-  /**
-   * Quantity.
-   *
-   * @var integer
-   */
-  public $quantity = 1;
-
-  /**
-   * Return this line item's unit amount.
-   *
-   * @param boolean $tax
-   *   Whether to include taxes or not.
-   *
-   * @return float
-   */
-  function unitAmount($tax) {
-    return $this->amount * ($tax ? $this->tax_rate + 1 : 1);
-  }
-
-  /**
-   * Return this line item's total amount.
-   *
-   * @param boolean $tax
-   *   Whether to include taxes or not.
-   *
-   * @return float
-   */
-  function totalAmount($tax) {
-    return $this->amount * $this->quantity * ($tax ? $this->tax_rate + 1 : 1);
-  }
-}
-/**
- * A payment status line item.
- */
-class PaymentStatusItem {
-
-  /**
-   * The status itself.
-   *
-   * @var string
-   */
-  public $status = '';
-
-  /**
-   * The Unix datetime this status was set.
-   *
-   * @var integer
-   */
-  public $created = 0;
-
-  /**
-   * The PID of the payment this status item belongs to.
-   *
-   * @var integer
-   */
-  public $pid = 0;
-
-  /**
-   * The unique internal ID of this payment status item.
-   *
-   * @var integer
-   */
-  public $psiid = 0;
-
-  function __construct($status, $created = 0, $pid = 0, $psiid = 0) {
-    $this->status = $status;
-    $this->created = $created ? $created : time();
-    $this->pid = $pid;
-    $this->psiid = $psiid;
-  }
-}
-
-/**
- * Information about a line item type.
- */
-class PaymentLineItemInfo extends PaymentCommon {
-
-  /**
-   * The callback function to get this line item from the Payment.
-   *
-   * The function accepts the machine name of the line item to get and the
-   * Payment object to get it from as parameters. It should return an array,
-   * optionally filled with PaymentLineItem objects.
-   *
-   * @see Payment::getLineItems()
-   * @see payment_line_item_get_all()
-   *
-   * @var string
-   */
-  public $callback = 'payment_line_item_get_specific';
-
-  /**
-   * The unique (for this payment) machine name.
-   *
-   * @var string
-   */
-  public $name = '';
-
-  /**
-   * The human-readable plain text title.
-   *
-   * @var string
-   */
-  public $title = '';
-}
-
-/**
  * A Payment-related exception.
  */
 class PaymentException extends Exception {
diff --git a/payment/payment.info.yml b/payment/payment.info.yml
index f72c438..4b33f1e 100644
--- a/payment/payment.info.yml
+++ b/payment/payment.info.yml
@@ -4,3 +4,5 @@ core: 8.x
 configure: admin/config/services/payment
 type: module
 package: Payment
+dependencies:
+  - entity_reference
diff --git a/payment/payment.install b/payment/payment.install
index 6de1892..2336dbb 100644
--- a/payment/payment.install
+++ b/payment/payment.install
@@ -6,6 +6,14 @@
  */
 
 /**
+ * A bit flag used to let us know if an entity has been customly defined.
+ *
+ * This constant comes from Entity 7.x-1.x and is required for
+ * payment_update_7103() to work under Drupal 8.x.
+ */
+define('ENTITY_CUSTOM', 0x01);
+
+/**
  * Implements hook_schema().
  */
 function payment_schema() {
@@ -21,49 +29,37 @@ function payment_schema() {
         'size' => 'big',
         'not null' => TRUE,
       ),
-      'description' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-      ),
-      'description_arguments' => array(
-        'type' => 'blob',
-        'size' => 'big',
-        'serialize' => TRUE,
-      ),
       'name' => array(
         'type' => 'varchar',
         'length' => 255,
       ),
-      'pid' => array(
-        'description' => 'The {payment}.pid this line item belongs to.',
+      'payment_id' => array(
+        'description' => 'The {payment}.id this line item belongs to.',
         'type' => 'int',
         'not null' => TRUE,
         'default' => 0,
       ),
+      'plugin_id' => array(
+        'type' => 'varchar',
+        'length' => 255,
+      ),
       'quantity' => array(
         'type' => 'int',
         'default' => 1,
         'not null' => TRUE,
       ),
-      'tax_rate' => array(
-        'type' => 'float',
-        'size' => 'big',
-        'default' => 0.0,
-        'not null' => TRUE,
-      ),
     ),
-    'primary key' => array('name', 'pid'),
+    'primary key' => array('name', 'payment_id'),
     'foreign keys' => array(
-      'pid' => array(
+      'payment_id' => array(
         'table' => 'payment',
         'columns' => array(
-          'pid' => 'pid',
+          'payment_id' => 'id',
         ),
       ),
     ),
     'indexes' => array(
-      'pid' => array('pid'),
+      'payment_id' => array('payment_id'),
     ),
   );
   $schema['payment'] = array(
@@ -72,57 +68,39 @@ function payment_schema() {
         'type' => 'varchar',
         'length' => 255,
       ),
-      'context_data' => array(
-        'type' => 'blob',
-        'size' => 'big',
-        'serialize' => TRUE,
-      ),
       'currency_code' => array(
         'type' => 'varchar',
         'length' => 3,
         'default' => 'XXX',
         'not null' => TRUE,
       ),
-      'description' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-      ),
-      'description_arguments' => array(
-        'type' => 'blob',
-        'size' => 'big',
-        'serialize' => TRUE,
-        'not null' => TRUE,
-      ),
       'finish_callback' => array(
         'type' => 'varchar',
         'length' => 255,
         'not null' => TRUE,
       ),
-      'pid' => array(
+      'id' => array(
         'type' => 'serial',
       ),
-      'pmid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'default' => 0,
-        'not null' => TRUE,
+      'payment_method_id' => array(
+        'type' => 'varchar',
+        'length' => 255,
       ),
-      'psiid_first' => array(
-        'description' => "The {payment_status_item}.psiid of this payment's first status item.",
+      'fist_payment_status_id' => array(
+        'description' => "The {payment_status_item}.id of this payment's first status item.",
         'type' => 'int',
         'unsigned' => TRUE,
         'default' => 0,
         'not null' => TRUE,
       ),
-      'psiid_last' => array(
-        'description' => "The {payment_status_item}.psiid of this payment's most recent status item.",
+      'last_payment_status_id' => array(
+        'description' => "The {payment_status_item}.id of this payment's most recent status item.",
         'type' => 'int',
         'unsigned' => TRUE,
         'default' => 0,
         'not null' => TRUE,
       ),
-      'uid' => array(
+      'owner_id' => array(
         'description' => 'The {users}.uid this payment belongs to.',
         'type' => 'int',
         'not null' => TRUE,
@@ -130,69 +108,63 @@ function payment_schema() {
       ),
     ),
     'foreign keys' => array(
-      'pmid' => array(
-        'table' => 'payment_method',
-        'columns' => array(
-          'pmid' => 'pmid',
-        ),
-      ),
-      'psiid_last' => array(
+      'fist_payment_status_id' => array(
         'table' => 'payment_status_item',
         'columns' => array(
-          'psiid_last' => 'psiid',
+          'fist_payment_status_id' => 'id',
         ),
       ),
-      'psiid_first' => array(
+      'last_payment_status_id' => array(
         'table' => 'payment_status_item',
         'columns' => array(
-          'psiid_first' => 'psiid',
+          'last_payment_status_id' => 'id',
         ),
       ),
-      'uid' => array(
+      'owner_id' => array(
         'table' => 'user',
         'columns' => array(
-          'uid' => 'uid',
+          'owner_id' => 'uid',
         ),
       ),
     ),
     'indexes' => array(
-      'pid' => array('pid'),
+      'id' => array('id'),
     ),
-    'primary key' => array('pid'),
+    'primary key' => array('id'),
   );
-  $schema['payment_status_item'] = array(
+  $schema['payment_status'] = array(
     'fields' => array(
-      'status' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-      ),
       'created' => array(
         'type' => 'int',
         'not null' => TRUE,
       ),
-      'pid' => array(
+      'payment_id' => array(
         'description' => 'The payment ID.',
         'type' => 'int',
         'not null' => TRUE,
         'default' => 0,
       ),
-      'psiid' => array(
+      'plugin_id' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+      ),
+      'id' => array(
         'type' => 'serial',
       ),
     ),
     'foreign keys' => array(
-      'pid' => array(
+      'payment_id' => array(
         'table' => 'payment',
         'columns' => array(
-          'pid' => 'pid',
+          'payment_id' => 'id',
         ),
       ),
     ),
     'indexes' => array(
-      'pid' => array('pid'),
+      'payment_id' => array('payment_id'),
     ),
-    'primary key' => array('psiid'),
+    'primary key' => array('id'),
   );
   $schema['payment_method'] = array(
     'fields' => array(
@@ -433,4 +405,182 @@ function payment_update_7105(array &$sandbox) {
         ->execute();
     }
   }
-}
\ No newline at end of file
+}
+
+/**
+ * Upgrades payments to 8.x-2.x.
+ */
+function payment_update_8200(array &$sandbox) {
+  // Rename pid to id.
+  db_drop_primary_key('payment');
+  db_change_field('payment', 'pid', 'id', array(
+    'type' => 'serial',
+  ), array(
+    'primary key' => array('id'),
+  ));
+  db_drop_index('payment', 'pid');
+  db_add_index('payment', 'id', array('id'));
+
+  // Rename context_data to context_configuration.
+  db_change_field('payment', 'context_data', 'context_configuration', array(
+    'type' => 'blob',
+    'size' => 'big',
+    'serialize' => TRUE,
+  ));
+
+  // Rename pmid to payment_method_id and convert numeric IDs to entity names.
+  db_change_field('payment', 'pmid', 'payment_method_id', array(
+    'type' => 'varchar',
+    'length' => 255,
+  ));
+  $names = db_select('payment_method', 'pm')
+    ->fields('pm', array('pmid', 'name'))
+    ->execute()
+    ->fetchAllKeyed();
+  foreach ($names as $pmid => $name) {
+    db_update('payment')
+      ->fields(array(
+        'payment_method_id' => $name,
+      ))
+      ->condition('payment_method_id', $pmid)
+      ->execute();
+  }
+
+  // Rename psiid_first to first_payment_status_id.
+  db_change_field('payment', 'psiid_first', 'first_payment_status_id', array(
+    'description' => "The {payment_status_item}.id of this payment's first status item.",
+    'type' => 'int',
+    'unsigned' => TRUE,
+    'default' => 0,
+    'not null' => TRUE,
+  ));
+
+  // Rename psiid_lastto last_payment_status_id.
+  db_change_field('payment', 'psiid_last', 'last_payment_status_id', array(
+    'description' => "The {payment_status_item}.id of this payment's most recent status item.",
+    'type' => 'int',
+    'unsigned' => TRUE,
+    'default' => 0,
+    'not null' => TRUE,
+  ));
+
+  // Rename uid to owner_id.
+  db_change_field('payment', 'uid', 'owner_id', array(
+    'description' => 'The {users}.uid this payment belongs to.',
+    'type' => 'int',
+    'not null' => TRUE,
+    'default' => 0,
+  ));
+
+  // Drop unnecessary fields.
+  db_drop_field('payment_line_item', 'context_configuration');
+  db_drop_field('payment_line_item', 'description');
+  db_drop_field('payment_line_item', 'description_arguments');
+}
+
+/**
+ * Upgrades payment line items to 8.x-2.x.
+ */
+function payment_update_8201(array &$sandbox) {
+  // Rename pid to payment_id.
+  db_drop_primary_key('payment_line_item');
+  db_drop_index('payment_line_item', 'pid');
+  db_change_field('payment_line_item', 'pid', 'payment_id', array(
+    'description' => 'The {payment}.id this line item belongs to.',
+    'type' => 'int',
+    'not null' => TRUE,
+    'default' => 0,
+  ));
+  db_add_index('payment_line_item', 'payment_id', array('payment_id'));
+  db_add_primary_key('payment_line_item', array('name', 'payment_id'));
+
+  // Add plugin_id to store the line item plugin type.
+  db_add_field('payment_line_item', 'plugin_id', array(
+    'type' => 'varchar',
+    'length' => 255,
+  ));
+
+  // Drop unnecessary fields.
+  db_drop_field('payment_line_item', 'description');
+  db_drop_field('payment_line_item', 'description_arguments');
+  db_drop_field('payment_line_item', 'tax_rate');
+}
+
+/**
+ * Upgrades payment statuses to 8.x-2.x.
+ */
+function payment_update_8202(array &$sandbox) {
+  db_rename_table('payment_status_item', 'payment_status');
+
+  // Rename psiid to id. Add a temporary index in order to be able to drop and
+  //re-add the primary key.
+  db_add_index('payment_status', 'psiid', array('psiid'));
+  db_drop_primary_key('payment_status');
+  db_change_field('payment_status', 'psiid', 'id', array(
+    'type' => 'serial',
+  ), array(
+    'primary key' => array('id'),
+  ));
+  db_drop_index('payment_status', 'psiid');
+
+  // Rename pid to payment_id.
+  db_change_field('payment_status', 'pid', 'payment_id', array(
+    'description' => 'The payment ID.',
+    'type' => 'int',
+    'not null' => TRUE,
+    'default' => 0,
+  ));
+  db_drop_index('payment_status', 'pid');
+  db_add_index('payment_status', 'payment_id', array('payment_id'));
+
+  // Rename status to plugin_id and update the column's values.
+  db_change_field('payment_status', 'status', 'plugin_id', array(
+    'type' => 'varchar',
+    'length' => 255,
+    'not null' => TRUE,
+  ));
+  foreach (payment_upgrade_map_status() as $old_status => $plugin_id) {
+    db_update('payment_status')
+      ->fields(array(
+        'plugin_id' => $plugin_id,
+      ))
+      ->condition('plugin_id', $old_status)
+      ->execute();
+  }
+}
+
+/**
+ * Maps payment statuses from 7.x-1.x to 8.x-2.x.
+ *
+ * @return array
+ *   Keys are 7.x-1.x statuses. Values are 8.x-2.x status plugin IDs.
+ */
+function payment_upgrade_map_status() {
+  return array(
+    'payment_status_money_transferred' => 'payment_money_transferred',
+    'payment_status_money_not_transferred' => 'payment_no_money_transferred',
+    'payment_status_unknown' => 'payment_unknown',
+    'payment_status_new' => 'payment_created',
+    'payment_status_pending' => 'payment_pending',
+    'payment_status_success' => 'payment_success',
+    'payment_status_failed' => 'payment_failed',
+    'payment_status_cancelled' => 'payment_cancelled',
+    'payment_status_expired' => 'payment_expired',
+    'payment_status_authorization_failed' => 'payment_authorization_failed',
+  );
+}
+
+/**
+ * Maps payment method plugins from 7.x-1.x to 8.x-2.x.
+ *
+ * @return array
+ *   Keys are 7.x-1.x payment method controller names. Values are 8.x-2.x
+ *   plugin IDs.
+ */
+function payment_upgrade_map_payment_method() {
+  return array(
+    'PaymentMethodControllerUnavailable' => 'payment_unavailable',
+    // @todo
+    // 'PaymentMethodBasicController' => '',
+  );
+}
diff --git a/payment/payment.module b/payment/payment.module
index 61063a0..518de55 100644
--- a/payment/payment.module
+++ b/payment/payment.module
@@ -9,56 +9,6 @@
 module_load_include('inc', 'payment', 'payment.ui');
 
 /**
- * An absolute parent status: a payment for which all money has been transferred.
- */
-define('PAYMENT_STATUS_MONEY_TRANSFERRED', 'payment_status_money_transferred');
-
-/**
- * An absolute parent status: a payment for which no money has been transferred.
- */
-define('PAYMENT_STATUS_MONEY_NOT_TRANSFERRED', 'payment_status_money_not_transferred');
-
-/**
- * An absolute parent status: a payment whose status is unknown to us.
- */
-define('PAYMENT_STATUS_UNKNOWN', 'payment_status_unknown');
-
-/**
- * A new payment for which processing has not yet started.
- */
-define('PAYMENT_STATUS_NEW', 'payment_status_new');
-
-/**
- * An open (pending) payment.
- */
-define('PAYMENT_STATUS_PENDING', 'payment_status_pending');
-
-/**
- * A payment for which funds have been successfully transferred.
- */
-define('PAYMENT_STATUS_SUCCESS', 'payment_status_success');
-
-/**
- * A failed payment, e.g. for which no funds have been transferred.
- */
-define('PAYMENT_STATUS_FAILED', 'payment_status_failed');
-
-/**
- * A payment that was cancelled by the payer.
- */
-define('PAYMENT_STATUS_CANCELLED', 'payment_status_cancelled');
-
-/**
- * A payment that expired due to inactivity.
- */
-define('PAYMENT_STATUS_EXPIRED', 'payment_status_expired');
-
-/**
- * A payment for which payer authorization failed.
- */
-define('PAYMENT_STATUS_AUTHORIZATION_FAILED', 'payment_status_authorization_failed');
-
-/**
  * The prefix for payment line item tokens.
  */
 define('PAYMENT_LINE_ITEM_TOKEN_PREFIX', 'line_item-');
@@ -378,13 +328,13 @@ function payment_permission() {
  * Implements hook_hook_info().
  */
 function payment_hook_info() {
-  $hooks['payment_status_info'] = array(
+  $hooks['payment_status_alter'] = array(
     'group' => 'payment',
   );
   $hooks['payment_method_alter'] = array(
     'group' => 'payment',
   );
-  $hooks['payment_line_item_info'] = array(
+  $hooks['payment_line_item_alter'] = array(
     'group' => 'payment',
   );
   $hooks['payment_status_change'] = array(
@@ -445,30 +395,6 @@ function payment_field_extra_fields() {
  * Implements hook_entity_info().
  */
 function payment_entity_info() {
-  $entity_info['payment'] = array(
-    'label' => t('Payment'),
-    'controller class' => 'PaymentEntityController',
-    'entity class' => 'Payment',
-    'module' => 'payment',
-    'base table' => 'payment',
-    'entity keys' => array(
-      'id' => 'pid',
-    ),
-    'label callback' => 'payment_label',
-    // @todo Enable static cache once http://drupal.org/node/1273756 is fixed.
-    'static cache' => FALSE,
-    'fieldable' => TRUE,
-    'bundles' => array(
-      'payment' => array(
-        'label' => t('Payment'),
-        'admin' => array(
-          'path' => 'admin/config/services/payment/payment',
-          'access arguments' => array('payment.payment.administer'),
-        ),
-      ),
-    ),
-    'access callback' => 'payment_access',
-  );
   $entity_info['payment_method'] = array(
     'label' => t('Payment method'),
     'controller class' => 'PaymentMethodEntityController',
@@ -491,70 +417,9 @@ function payment_entity_info() {
 }
 
 /**
- * Returns the label of a given Payment entity.
- *
- * @param Payment $entity
- *   A Payment entity.
- *
- * @return string
- *   The label of the Payment entity.
- */
-function payment_label(Payment $entity) {
-  return t($entity->description, $entity->description_arguments);
-}
-
-/**
  * Implements hook_entity_property_info().
  */
 function payment_entity_property_info() {
-  // Payment.
-  $properties['payment']['properties']['context'] = array(
-    'description' => t("The machine-readable name of the context that created the payment."),
-    'label' => t('Context'),
-    'required' => TRUE,
-    'schema field' => 'context',
-  );
-  $properties['payment']['properties']['currency_code'] = array(
-    'description' => t('A three-letter ISO 4217 currency code.'),
-    'label' => t('Currency code'),
-    'required' => TRUE,
-    'schema field' => 'currency_code',
-  );
-  $properties['payment']['properties']['description'] = array(
-    'label' => t('Description'),
-    'schema field' => 'description',
-    'getter callback' => 'payment_entity_property_get',
-  );
-  $properties['payment']['properties']['finish_callback'] = array(
-    'description' => t('The name of the function to call once payment processing is completed.'),
-    'label' => t('Finish callback'),
-    'required' => TRUE,
-    'schema field' => 'finish_callback',
-  );
-  $properties['payment']['properties']['method'] = array(
-    'label' => t('Payment method'),
-    'required' => TRUE,
-    'type' => 'payment_method',
-  );
-  $properties['payment']['properties']['pid'] = array(
-    'label' => t('Payment ID'),
-    'schema field' => 'pid',
-    'type' => 'integer',
-  );
-  $properties['payment']['properties']['pmid'] = array(
-    'label' => t('Payment method ID'),
-    'schema field' => 'pmid',
-    'type' => 'integer',
-  );
-  $properties['payment']['properties']['uid'] = array(
-    'label' => t('User ID'),
-    'description' => t('The ID of the user this payment belongs to.'),
-    'required' => TRUE,
-    'schema field' => 'uid',
-    'type' => 'integer',
-  );
-
-  // Payment method.
   $properties['payment_method']['properties']['controller_class_name'] = array(
     'label' => t('Payment method type'),
     'schema field' => 'controller_class_name',
@@ -591,21 +456,6 @@ function payment_entity_property_info() {
 }
 
 /**
- * Gets the property of a Payment entity.
- *
- * @see payment_entity_property_info()
- */
-function payment_entity_property_get(Payment $payment, array $options, $name, $type, $info) {
-  switch ($name) {
-    case 'description':
-      return payment_label($payment);
-
-    default:
-      return entity_property_verbatim_get($payment, $options, $name, $type, $info);
-  }
-}
-
-/**
  * Implements hook_element_info().
  */
 function payment_element_info() {
@@ -673,113 +523,6 @@ function payment_views_api() {
 }
 
 /**
- * Gets payment statuses.
- *
- * @see hook_payment_status_info()
- *
- * @return array
- *   An array with PaymentStatusInfo objects for all available statuses.
- */
-function payment_statuses_info() {
-  $statuses_info = &drupal_static(__FUNCTION__);
-
-  if (!$statuses_info) {
-    $statuses_info = array();
-    foreach (module_invoke_all('payment_status_info') as $status_info) {
-      $statuses_info[$status_info->status] = $status_info;
-    }
-    drupal_alter('payment_status_info', $statuses_info);
-  }
-
-  return $statuses_info;
-}
-
-/**
- * Gets a specific payment status.
- *
- * @param string $status
- *   A status' system name.
- * @param boolean $unknown
- *   Whether to return info for PAYMENT_STATUS_UNKNOWN if the requested status
- *   does not exist.
- *
- * @return mixed
- *   A PaymentStatusInfo object or FALSE if the status does not exist.
- */
-function payment_status_info($status, $unknown = FALSE) {
-  $statuses_info = payment_statuses_info();
-
-  return isset($statuses_info[$status]) ? $statuses_info[$status] : ($unknown ? $statuses_info[PAYMENT_STATUS_UNKNOWN] : FALSE);
-}
-
-/**
- * Returns information about all line item types.
- *
- * @see hook_payment_line_item_type_info()
- *
- * @return array
- *   An array with PaymentLineItemInfo objects, keyed by PaymentLineItemInfo::name.
- */
-function payment_line_items_info() {
-  $line_items_info = &drupal_static(__FUNCTION__);
-
-  if (is_null($line_items_info)) {
-    $line_items_info = array();
-    foreach (module_invoke_all('payment_line_item_info') as $line_item_info) {
-      $line_items_info[$line_item_info->name] = $line_item_info;
-    }
-    drupal_alter('payment_line_item_info', $line_items_info);
-  }
-
-  return $line_items_info;
-}
-
-/**
- * Returns information about a specific line item type.
- *
- * @param string $name
- *   The line item type's machine name.
- *
- * @return mixed
- *   A PaymentLineItemInfo object or FALSE if the requested info could not be
- *   found.
- */
-function payment_line_item_info($name) {
-  $line_items_info = payment_line_items_info();
-
-  return isset($line_items_info[$name]) ? $line_items_info[$name] : FALSE;
-}
-
-/**
- * Check if a payment status has a given other status as one of its ancestors.
- *
- * @param string $status
- *   The payment status.
- * @param string $ancestor_status
- *   The ancestor status to check for.
- *
- * @return boolean
- */
-function payment_status_has_ancestor($status, $ancestor_status) {
-  return in_array($ancestor_status, payment_status_info($status, TRUE)->ancestors());
-}
-
-/**
- * Check if a payment status is equal to a given other status or has it one of
- * its ancestors.
- *
- * @param string $status
- *   The payment status.
- * @param string $ancestor_status
- *   The ancestor status to check for.
- *
- * @return boolean
- */
-function payment_status_is_or_has_ancestor($status, $ancestor_status) {
-  return $status == $ancestor_status || in_array($ancestor_status, payment_status_info($status, TRUE)->ancestors());
-}
-
-/**
  * Convert an amount as a float to a human-readable format.
  *
  * @param float $amount
@@ -805,77 +548,6 @@ function payment_amount_human_readable($amount, $currency_code = NULL) {
 }
 
 /**
- * Check if a user has access to perform a certain payment operation.
- *
- * @see payment_permission()
- *
- * @param string $operation
- *   One of the following operations:
- *   - "create"
- *   - "update" (does not require $payment, but only grants access if the user
- *     has permission to update any payment)
- *   - "delete" (does not require $payment_method, but only grants access if
- *     the user has permission to delete any payment)
- *   - "view" (does not require $payment_method, but only grants access if the
- *     user has permission to view any payment)
- * @param Payment $payment
- *   The payment the user wants to perform the operation on.
- * @param object $account
- *   The user account for which to check access. If NULL, the current user is
- *   used.
- *
- * @return boolean
- */
-function payment_access($operation, Payment $payment = NULL, $account = NULL) {
-  global $user;
-
-  if (!$account) {
-    $account = $user;
-  }
-
-  switch ($operation) {
-    case 'create':
-      // We let other modules decide whether users have access to create
-      // new payments. There is no corresponding permission for this operation.
-      return TRUE;
-    case 'view':
-    case 'update';
-    case 'delete':
-      return user_access('payment.payment.' . $operation . '.any', $account) || $payment && user_access('payment.payment.' . $operation . '.own', $account) && $account->uid == $payment->uid;
-  }
-  return FALSE;
-}
-
-/**
- * Implements PaymentLineItemInfo::callback.
- */
-function payment_line_item_get_specific($name, Payment $payment) {
-  return isset($payment->line_items[$name]) ? array($payment->line_items[$name]) : array();
-}
-
-/**
- * Implements PaymentLineItemInfo::callback.
- */
-function payment_line_item_get_all($name, Payment $payment) {
-  return $payment->line_items;
-}
-
-/**
- * Implements PaymentLineItemInfo::callback.
- */
-function payment_line_item_get_prefixed($name, Payment $payment) {
-  $selection = array();
-
-  foreach ($payment->line_items as $line_item) {
-    if (strpos($line_item->name, $name) === 0) {
-      $selection[] = $line_item;
-    }
-  }
-
-  return $selection;
-}
-
-/**
  * Implements Rules access callback.
  */
 function payment_rules_access($type, $name) {
diff --git a/payment/payment.payment.inc b/payment/payment.payment.inc
index 1551b70..af38762 100644
--- a/payment/payment.payment.inc
+++ b/payment/payment.payment.inc
@@ -6,62 +6,6 @@
  */
 
 /**
- * Implements hook_payment_status_info().
- */
-function payment_payment_status_info() {
-  return array(
-    new PaymentStatusInfo(array(
-      'status' => PAYMENT_STATUS_MONEY_TRANSFERRED,
-      'title' => t('Money has been transferred'),
-    )),
-    new PaymentStatusInfo(array(
-      'status' => PAYMENT_STATUS_MONEY_NOT_TRANSFERRED,
-      'title' => t('No money has been transferred'),
-    )),
-    new PaymentStatusInfo(array(
-      'description' => t('The payment status could not be automatically verified.'),
-      'status' => PAYMENT_STATUS_UNKNOWN,
-      'title' => t('Unknown'),
-    )),
-    new PaymentStatusInfo(array(
-      'status' => PAYMENT_STATUS_PENDING,
-      'title' => t('Pending'),
-      'parent' => PAYMENT_STATUS_MONEY_NOT_TRANSFERRED,
-    )),
-    new PaymentStatusInfo(array(
-      'status' => PAYMENT_STATUS_SUCCESS,
-      'title' => t('Completed'),
-      'parent' => PAYMENT_STATUS_MONEY_TRANSFERRED,
-    )),
-    new PaymentStatusInfo(array(
-      'status' => PAYMENT_STATUS_FAILED,
-      'title' => t('Failed'),
-      'parent' => PAYMENT_STATUS_MONEY_NOT_TRANSFERRED,
-    )),
-    new PaymentStatusInfo(array(
-      'status' => PAYMENT_STATUS_CANCELLED,
-      'title' => t('Cancelled'),
-      'parent' => PAYMENT_STATUS_FAILED,
-    )),
-    new PaymentStatusInfo(array(
-      'status' => PAYMENT_STATUS_EXPIRED,
-      'title' => t('Expired'),
-      'parent' => PAYMENT_STATUS_FAILED,
-    )),
-    new PaymentStatusInfo(array(
-      'status' => PAYMENT_STATUS_AUTHORIZATION_FAILED,
-      'title' => t('Authorization failed'),
-      'parent' => PAYMENT_STATUS_FAILED,
-    )),
-    new PaymentStatusInfo(array(
-      'status' => PAYMENT_STATUS_NEW,
-      'title' => t('Created'),
-      'parent' => PAYMENT_STATUS_MONEY_NOT_TRANSFERRED,
-    )),
-  );
-}
-
-/**
  * Implements hook_payment_line_item_info().
  */
 function payment_payment_line_item_info() {
diff --git a/payment/payment.services.yml b/payment/payment.services.yml
index 1112460..e2f0d2c 100644
--- a/payment/payment.services.yml
+++ b/payment/payment.services.yml
@@ -1,4 +1,10 @@
 services:
+  plugin.manager.payment.line_item:
+    class: Drupal\payment\Plugin\payment\line_item\Manager
+    arguments: ['@container.namespaces']
   plugin.manager.payment.payment_method:
     class: Drupal\payment\Plugin\payment\PaymentMethod\Manager
-    arguments: ['@container.namespaces']
\ No newline at end of file
+    arguments: ['@container.namespaces']
+  plugin.manager.payment.status:
+    class: Drupal\payment\Plugin\payment\status\Manager
+    arguments: ['@container.namespaces']
diff --git a/payment/tests/payment_test/tests/PaymentTestEntityCRUDWebTestCase.test b/payment/tests/payment_test/tests/PaymentTestEntityCRUDWebTestCase.test
deleted file mode 100644
index c30b3c5..0000000
--- a/payment/tests/payment_test/tests/PaymentTestEntityCRUDWebTestCase.test
+++ /dev/null
@@ -1,85 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains class PaymentTestEntityCrudWebTestCase.
- */
-
-/**
- * Tests entity CRUD functionality.
- */
-class PaymentTestEntityCrudWebTestCase extends PaymentWebTestCase {
-
-  static function getInfo() {
-    return array(
-      'name' => 'Entity CRUD functionality',
-      'group' => 'Payment',
-    );
-  }
-
-  function setUp(array $modules = array()) {
-    parent::setUp($modules = array('payment_test'));
-  }
-
-  /**
-   * Test payment CRUD functionality.
-   */
-  function testPaymentCRUD() {
-    $schema = drupal_get_schema('payment');
-    $payment_method = $this->paymentMethodCreate(1, payment_method_controller_load('PaymentMethodControllerUnavailable'));
-    entity_save('payment_method', $payment_method);
-
-    // Test inserting a payment.
-    $payment = new Payment(array(
-      'currency_code' => 'foo',
-      'description' => 'bar',
-      'finish_callback' => 'payment_test_finish_callback',
-      'method' => $payment_method,
-    ));
-    entity_save('payment', $payment);
-    $query = db_select('payment');
-    foreach ($schema['fields'] as $name => $definition) {
-      if (property_exists($payment, $name)) {
-        if (!empty($definition['serialize'])) {
-          $query->condition($name, serialize($payment->$name));
-        }
-        else {
-          $query->condition($name, $payment->$name);
-        }
-      }
-    }
-    $count = $query->countQuery()->execute()->fetchField();
-    $this->assertTrue($count, 'A new payment is correctly inserted.');
-
-    // Test loading a payment.
-    $payment_loaded = entity_load_single('payment', $payment->pid);
-    // @todo
-
-    // Test updating a payment.
-    $payment->currency_code = 'bar';
-    $payment->description = 'foo';
-    entity_save('payment', $payment);
-    $query = db_select('payment');
-    foreach ($schema['fields'] as $name => $definition) {
-      if (property_exists($payment, $name)) {
-        if (!empty($definition['serialize'])) {
-          $query->condition($name, serialize($payment->$name));
-        }
-        else {
-          $query->condition($name, $payment->$name);
-        }
-      }
-    }
-    $count = $query->countQuery()->execute()->fetchField();
-    $this->assertTrue($count, 'A payment is correctly updated.');
-
-    // Test deleting a payment.
-    entity_delete('payment', $payment->pid);
-    $count = db_select('payment')
-      ->condition('pid', $payment->pid)
-      ->countQuery()
-      ->execute()
-      ->fetchField();
-    $this->assertFalse($count, 'A payment is correctly deleted.');
-  }
-}
diff --git a/payment/tests/payment_test/tests/PaymentTestPaymentEntityPermissionWebTestCase.test b/payment/tests/payment_test/tests/PaymentTestPaymentEntityPermissionWebTestCase.test
deleted file mode 100644
index 784d569..0000000
--- a/payment/tests/payment_test/tests/PaymentTestPaymentEntityPermissionWebTestCase.test
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains class PaymentTestPaymentEntityPermissionWebTestCase.
- */
-
-/**
- * Tests payment entity permissions.
- */
-class PaymentTestPaymentEntityPermissionWebTestCase extends PaymentWebTestCase {
-
-  static function getInfo() {
-    return array(
-      'name' => 'Payment permissions',
-      'group' => 'Payment',
-      'dependencies' => array('payment'),
-    );
-  }
-
-  /**
-   * Overrides parent::setUp().
-   */
-  function setUp(array $modules = array()) {
-    parent::setUp($modules + array('payment'));
-  }
-
-  function testPaymentEntityPermissions() {
-    $payment_1 = $this->paymentCreate(1);
-    $payment_2 = $this->paymentCreate(2);
-
-    // Test creating a payment.
-    $this->XtoolsAssertEntityPermission(NULL, 'Payment', 'payment_access', 'create', array(), array(
-      'anonymous' => TRUE,
-      'authenticated_without_permissions' => TRUE,
-    ));
-
-    // Test deleting, updating and viewing a payment.
-    $operations = array('delete', 'update', 'view');
-    foreach ($operations as $operation) {
-      // Test a payment that belongs to user 1.
-      $this->XtoolsAssertEntityPermission($payment_1, 'Payment', 'payment_access', $operation, array("payment.payment.$operation.any"));
-      $this->XtoolsAssertEntityPermission($payment_1, 'Payment', 'payment_access', $operation, array("payment.payment.$operation.own"), array(
-        'authenticated_with_permissions' => FALSE,
-      ));
-      $this->XtoolsAssertEntityPermission($payment_1, 'Payment', 'payment_access', $operation);
-
-      // Test a payment that belongs to user 2.
-      $this->XtoolsAssertEntityPermission($payment_2, 'Payment', 'payment_access', $operation, array("payment.payment.$operation.any"));
-      $this->XtoolsAssertEntityPermission($payment_2, 'Payment', 'payment_access', $operation, array("payment.payment.$operation.own"));
-      $this->XtoolsAssertEntityPermission($payment_2, 'Payment', 'payment_access', $operation);
-    }
-  }
-}
diff --git a/payment/tests/payment_test/tests/PaymentTestPaymentStatusItemWebTestCase.test b/payment/tests/payment_test/tests/PaymentTestPaymentStatusItemWebTestCase.test
deleted file mode 100644
index c0a0ba3..0000000
--- a/payment/tests/payment_test/tests/PaymentTestPaymentStatusItemWebTestCase.test
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains class PaymentTestPaymentStatusItemWebTestCase.
- */
-
-/**
- * Test payment status item handling.
- */
-class PaymentTestPaymentStatusItemWebTestCase extends PaymentWebTestCase {
-
-  static function getInfo() {
-    return array(
-      'name' => 'Payment status item handling',
-      'group' => 'Payment',
-    );
-  }
-
-  /**
-   * Overrides parent::setUp().
-   */
-  function setUp(array $modules = array()) {
-    parent::setUp($modules = array('payment'));
-  }
-
-  /**
-   * Test payment status item handling.
-   */
-  function testPaymentStatusItem() {
-    $payment = new Payment;
-    $status_pending = new PaymentStatusItem(PAYMENT_STATUS_PENDING);
-    $status_success = new PaymentStatusItem(PAYMENT_STATUS_SUCCESS);
-    $payment->setStatus($status_pending);
-    $payment->setStatus($status_success);
-    $this->assertTrue($payment->getStatus() === $status_success, 'Payment::setStatus() sets status items in the right order and Payment::getStatus() retrieves them.');
-  }
-}
diff --git a/payment/tests/payment_test/tests/PaymentTestUpdatePathWebTestCaseWithContent.test b/payment/tests/payment_test/tests/PaymentTestUpdatePathWebTestCaseWithContent.test
deleted file mode 100644
index 0efe6ac..0000000
--- a/payment/tests/payment_test/tests/PaymentTestUpdatePathWebTestCaseWithContent.test
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains class PaymentTestUpgradePathWebTestCase.
- */
-
-/**
- * Test Payment's update path.
- */
-class PaymentTestUpdatePathWebTestCase extends UpdatePathTestCase {
-
-  static function getInfo() {
-    return array(
-      'name'  => 'Update path (with existing content and configuration)',
-      'group' => 'Payment',
-      'dependencies' => array('payment', 'paymentform', 'paymentmethodbasic', 'paymentreference'),
-    );
-  }
-
-  function setUp() {
-    $this->profile = 'testing';
-    $this->databaseDumpFiles = array(
-      drupal_get_path('module', 'payment') . '/../payment-database-dump.php',
-      drupal_get_path('module', 'payment') . '/../payment-database-dump-content.php',
-    );
-    parent::setUp();
-    // Re-register the autoload functions that were unregistered by
-    // UpdatePathTestCase::setUp(), because it also loads some module files
-    // that work with classes.
-    spl_autoload_register('drupal_autoload_class');
-    spl_autoload_register('drupal_autoload_interface');
-    registry_rebuild();
-  }
-
-  /**
-   * Test a successful upgrade.
-   */
-  function testPaymentUpgrade() {
-    $this->assertTrue($this->performUpgrade(), 'The update was completed successfully.');
-  }
-}
\ No newline at end of file
diff --git a/payment/tests/payment_test/tests/PaymentTestUpdatePathWebTestCaseWithoutContent.test b/payment/tests/payment_test/tests/PaymentTestUpdatePathWebTestCaseWithoutContent.test
deleted file mode 100644
index fb6e6ab..0000000
--- a/payment/tests/payment_test/tests/PaymentTestUpdatePathWebTestCaseWithoutContent.test
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains class PaymentTestUpdatePathWebTestCaseWithoutContent.
- */
-
-/**
- * Test Payment's update path.
- */
-class PaymentTestUpdatePathWebTestCaseWithoutContent extends UpdatePathTestCase {
-
-  static function getInfo() {
-    return array(
-      'name'  => 'Update path (without existing content and configuration)',
-      'group' => 'Payment',
-      'dependencies' => array('payment', 'paymentform', 'paymentmethodbasic', 'paymentreference'),
-    );
-  }
-
-  function setUp() {
-    $this->profile = 'testing';
-    $this->databaseDumpFiles = array(drupal_get_path('module', 'payment') . '/../payment-database-dump.php');
-    parent::setUp();
-    // Re-register the autoload functions that were unregistered by
-    // UpdatePathTestCase::setUp(), because it also loads some module files
-    // that work with classes.
-    spl_autoload_register('drupal_autoload_class');
-    spl_autoload_register('drupal_autoload_interface');
-    registry_rebuild();
-  }
-
-  /**
-   * Test a successful upgrade.
-   */
-  function testPaymentUpgrade() {
-    $this->assertTrue($this->performUpgrade(), 'The update was completed successfully.');
-  }
-}
\ No newline at end of file
diff --git a/payment/tests/payment_test/tests/PaymentTestUpdatePathWithContentWebTestCase.test b/payment/tests/payment_test/tests/PaymentTestUpdatePathWithContentWebTestCase.test
deleted file mode 100644
index af3da5a..0000000
--- a/payment/tests/payment_test/tests/PaymentTestUpdatePathWithContentWebTestCase.test
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains class PaymentTestUpdatePathWithContentWebTestCase.
- */
-
-/**
- * Tests Payment's update path.
- */
-class PaymentTestUpdatePathWithContentWebTestCase extends UpdatePathTestCase {
-
-  static function getInfo() {
-    return array(
-      'name'  => 'Update path (with existing content and configuration)',
-      'group' => 'Payment',
-      'dependencies' => array('payment', 'paymentform', 'paymentmethodbasic', 'paymentreference'),
-    );
-  }
-
-  /**
-   * Overrides parent::setUp().
-   */
-  function setUp() {
-    $this->profile = 'testing';
-    $this->databaseDumpFiles = array(
-      drupal_get_path('module', 'payment') . '/../payment-database-dump.php',
-      drupal_get_path('module', 'payment') . '/../payment-database-dump-content.php',
-    );
-    parent::setUp();
-    // Re-register the autoload functions that were unregistered by
-    // UpdatePathTestCase::setUp(), because it also loads some module files
-    // that work with classes.
-    spl_autoload_register('drupal_autoload_class');
-    spl_autoload_register('drupal_autoload_interface');
-    registry_rebuild();
-  }
-
-  /**
-   * Test a successful upgrade.
-   */
-  function testPaymentUpgrade() {
-    $this->assertTrue($this->performUpgrade(), 'The update was completed successfully.');
-  }
-}
diff --git a/payment/tests/payment_test/tests/PaymentTestUpdatePathWithoutContentWebTestCase.test b/payment/tests/payment_test/tests/PaymentTestUpdatePathWithoutContentWebTestCase.test
deleted file mode 100644
index 6b58fbb..0000000
--- a/payment/tests/payment_test/tests/PaymentTestUpdatePathWithoutContentWebTestCase.test
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains class PaymentTestUpdatePathWithoutContentWebTestCase.
- */
-
-/**
- * Tests Payment's update path.
- */
-class PaymentTestUpdatePathWithoutContentWebTestCase extends UpdatePathTestCase {
-
-  static function getInfo() {
-    return array(
-      'name'  => 'Update path (without existing content and configuration)',
-      'group' => 'Payment',
-      'dependencies' => array('payment', 'paymentform', 'paymentmethodbasic', 'paymentreference'),
-    );
-  }
-
-  /**
-   * Overrides parent::setUp().
-   */
-  function setUp() {
-    $this->profile = 'testing';
-    $this->databaseDumpFiles = array(drupal_get_path('module', 'payment') . '/../payment-database-dump.php');
-    parent::setUp();
-    // Re-register the autoload functions that were unregistered by
-    // UpdatePathTestCase::setUp(), because it also loads some module files
-    // that work with classes.
-    spl_autoload_register('drupal_autoload_class');
-    spl_autoload_register('drupal_autoload_interface');
-    registry_rebuild();
-  }
-
-  /**
-   * Test a successful upgrade.
-   */
-  function testPaymentUpgrade() {
-    $this->assertTrue($this->performUpgrade(), 'The update was completed successfully.');
-  }
-}
diff --git a/paymentmethodbasic/paymentmethodbasic.module b/paymentmethodbasic/paymentmethodbasic.module
index 7690058..0d40327 100644
--- a/paymentmethodbasic/paymentmethodbasic.module
+++ b/paymentmethodbasic/paymentmethodbasic.module
@@ -85,7 +85,7 @@ function paymentmethodbasic_payment_method_delete($entity) {
  * payment-specific information and does not transfer any money. It can be
  * useful to create a "collect on delivery" payment method, for instance.
  */
-class PaymentMethodBasicController extends PaymentMethodController {
+class PaymentMethodBasicController {
 
   public $controller_data_defaults = array(
     'message' => '',
diff --git a/paymentreference/paymentreference.install b/paymentreference/paymentreference.install
index 54cfab4..56ec1de 100644
--- a/paymentreference/paymentreference.install
+++ b/paymentreference/paymentreference.install
@@ -89,4 +89,15 @@ function paymentreference_update_7100(&$sandbox) {
       ->condition('pid', $pids)
       ->execute();
   }
-}
\ No newline at end of file
+}
+
+/**
+ * Implements hook_update_dependencies()
+ */
+function paymentreference_update_dependencies() {
+  $dependencies['payment'][8200] = array(
+    'paymentreference' => 7100,
+  );
+
+  return $dependencies;
+}
