diff --git a/modules/payment_test/src/Plugin/Field/FieldType/MockPluginBagItem.php b/modules/payment_test/src/Plugin/Field/FieldType/MockPluginBagItem.php
new file mode 100644
index 0000000..49d58d8
--- /dev/null
+++ b/modules/payment_test/src/Plugin/Field/FieldType/MockPluginBagItem.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\payment_test\Plugin\Field\FieldType\PluginBagItem.
+ */
+
+namespace Drupal\payment_test\Plugin\Field\FieldType;
+
+use Drupal\payment\Plugin\Field\FieldType\PluginBagItemBase;
+use Drupal\payment_test\Plugin\PluginTest\MockManager;
+
+/**
+ * Provides a plugin bag field item for testing.
+ *
+ * @FieldType(
+ *   id = "payment_test_plugin_bag",
+ *   label = @Translation("Plugin bag")
+ * )
+ */
+class MockPluginBagItem extends PluginBagItemBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPluginManager() {
+    return new MockManager();
+  }
+
+}
diff --git a/modules/payment_test/src/Plugin/PluginTest/MockConfigurablePlugin.php b/modules/payment_test/src/Plugin/PluginTest/MockConfigurablePlugin.php
new file mode 100644
index 0000000..69a0b9c
--- /dev/null
+++ b/modules/payment_test/src/Plugin/PluginTest/MockConfigurablePlugin.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\payment_test\Plugin\PluginTest\MockPlugin.
+ */
+
+namespace Drupal\payment_test\Plugin\PluginTest;
+
+use Drupal\Component\Plugin\ConfigurablePluginInterface;
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\payment\Plugin\Payment\FieldTypePluginInterface;
+use Drupal\payment\Plugin\Payment\FieldTypePluginTrait;
+
+/**
+ * Provides a configurable mock plugin.
+ */
+class MockConfigurablePlugin extends PluginBase implements ConfigurablePluginInterface, FieldTypePluginInterface {
+
+  use FieldTypePluginTrait;
+
+  /**
+   * Constructs a Drupal\Component\Plugin\PluginBase object.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
+    $this->configuration = $configuration;
+    $this->pluginId = $plugin_id;
+    $this->pluginDefinition = $plugin_definition;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setConfiguration(array $configuration) {
+    $this->configuration = $configuration + $this->defaultConfiguration();
+    $this->setContainingFieldItemConfiguration($this->configuration);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfiguration() {
+    return $this->configuration;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateDependencies() {
+    return [];
+  }
+
+}
diff --git a/modules/payment_test/src/Plugin/PluginTest/MockManager.php b/modules/payment_test/src/Plugin/PluginTest/MockManager.php
new file mode 100644
index 0000000..ff0738f
--- /dev/null
+++ b/modules/payment_test/src/Plugin/PluginTest/MockManager.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\payment_test\Plugin\PluginTest\MockManager.
+ */
+
+namespace Drupal\payment_test\Plugin\PluginTest;
+
+use Drupal\Component\Plugin\Factory\DefaultFactory;
+use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Component\Plugin\Discovery\StaticDiscovery;
+
+/**
+ * Provides a plugin manager for testing plugni-related functionality.
+ */
+class MockManager extends PluginManagerBase {
+
+  /**
+   * Constructs a new instance.
+   */
+  public function __construct() {
+    $this->discovery = new StaticDiscovery();
+
+    $this->discovery->setDefinition('payment_test_plugin', array(
+      'label' => t('Plugin'),
+      'class' => 'Drupal\payment_test\Plugin\PluginTest\MockPlugin',
+    ));
+
+    $this->discovery->setDefinition('payment_test_configurable_plugin', array(
+      'label' => t('Configurable plugin'),
+      'class' => 'Drupal\payment_test\Plugin\PluginTest\MockConfigurablePlugin',
+    ));
+
+    $this->factory = new DefaultFactory($this->discovery);
+  }
+}
diff --git a/modules/payment_test/src/Plugin/PluginTest/MockPlugin.php b/modules/payment_test/src/Plugin/PluginTest/MockPlugin.php
new file mode 100644
index 0000000..9b7e129
--- /dev/null
+++ b/modules/payment_test/src/Plugin/PluginTest/MockPlugin.php
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\payment_test\Plugin\PluginTest\MockPlugin.
+ */
+
+namespace Drupal\payment_test\Plugin\PluginTest;
+
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\payment\Plugin\Payment\FieldTypePluginInterface;
+use Drupal\payment\Plugin\Payment\FieldTypePluginTrait;
+
+/**
+ * Provides a mock plugin.
+ */
+class MockPlugin extends PluginBase implements FieldTypePluginInterface {
+
+  use FieldTypePluginTrait;
+
+}
diff --git a/src/Plugin/Field/FieldType/PaymentMethodItem.php b/src/Plugin/Field/FieldType/PaymentMethodItem.php
new file mode 100644
index 0000000..7ba0432
--- /dev/null
+++ b/src/Plugin/Field/FieldType/PaymentMethodItem.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\payment\Plugin\Field\FieldType\PaymentMethodItem.
+ */
+
+namespace Drupal\payment\Plugin\Field\FieldType;
+
+use Drupal\payment\Payment;
+
+/**
+ * Provides a plugin bag for payment method plugins.
+ *
+ * @FieldType(
+ *   id = "payment_method",
+ *   label = @Translation("Payment method plugins")
+ * )
+ */
+class PaymentMethodItem extends PluginBagItemBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPluginManager() {
+    // @todo Unit-test this.
+    return Payment::methodManager();
+  }
+
+}
diff --git a/src/Plugin/Field/FieldType/PaymentStatusItem.php b/src/Plugin/Field/FieldType/PaymentStatusItem.php
new file mode 100644
index 0000000..617e59c
--- /dev/null
+++ b/src/Plugin/Field/FieldType/PaymentStatusItem.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\payment\Plugin\Field\FieldType\PaymentStatusItem.
+ */
+
+namespace Drupal\payment\Plugin\Field\FieldType;
+
+use Drupal\payment\Payment;
+
+/**
+ * Provides a plugin bag for payment type plugins.
+ *
+ * @FieldType(
+ *   id = "payment_status",
+ *   label = @Translation("Payment status plugins")
+ * )
+ */
+class PaymentStatusItem extends PluginBagItemBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPluginManager() {
+    // @todo Unit-test this.
+    return Payment::statusManager();
+  }
+
+}
diff --git a/src/Plugin/Field/FieldType/PaymentTypeItem.php b/src/Plugin/Field/FieldType/PaymentTypeItem.php
new file mode 100644
index 0000000..9bdb567
--- /dev/null
+++ b/src/Plugin/Field/FieldType/PaymentTypeItem.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\payment\Plugin\Field\FieldType\PaymentTypeItem.
+ */
+
+namespace Drupal\payment\Plugin\Field\FieldType;
+
+use Drupal\payment\Payment;
+
+/**
+ * Provides a plugin bag for payment type plugins.
+ *
+ * @FieldType(
+ *   id = "payment_type",
+ *   label = @Translation("Payment type plugins")
+ * )
+ */
+class PaymentTypeItem extends PluginBagItemBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPluginManager() {
+    // @todo Unit-test this.
+    return Payment::typeManager();
+  }
+
+}
diff --git a/src/Plugin/Field/FieldType/PluginBagItemBase.php b/src/Plugin/Field/FieldType/PluginBagItemBase.php
new file mode 100644
index 0000000..1962c52
--- /dev/null
+++ b/src/Plugin/Field/FieldType/PluginBagItemBase.php
@@ -0,0 +1,241 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Field\Plugin\Field\FieldType\PluginBagItemBase.
+ */
+
+namespace Drupal\payment\Plugin\Field\FieldType;
+
+use Drupal\Component\Plugin\PluginInspectionInterface;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Field\FieldItemBase;
+use Drupal\Core\TypedData\DataDefinition;
+use Drupal\Core\TypedData\MapDataDefinition;
+use Drupal\payment\Plugin\Payment\FieldTypePluginInterface;
+
+/**
+ * Provides a base for plugin bag field items.
+ */
+abstract class PluginBagItemBase extends FieldItemBase {
+
+  /**
+   * Whether a method was called from the contained plugin.
+   *
+   * @var bool
+   */
+  protected $calledFromContainedPlugin = FALSE;
+
+  /**
+   * The instantiated plugin.
+   *
+   * @var \Drupal\Component\Plugin\PluginInspectionInterface|\Drupal\payment\Plugin\Payment\FieldTypePluginInterface
+   */
+  protected $pluginInstance;
+
+  /**
+   * Returns the plugin manager.
+   *
+   * @return \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  abstract protected function getPluginManager();
+
+  /**
+   * Creates a plugin instance.
+   *
+   * @param string $plugin_id
+   * @param mixed[] $plugin_configuration
+   *
+   * @return \Drupal\Component\Plugin\PluginInspectionInterface
+   *
+   * @throws \Exception
+   */
+  protected function createContainedPluginInstance($plugin_id, array $plugin_configuration = []) {
+    $plugin_instance = $this->getPluginManager()->createInstance($plugin_id, $plugin_configuration);
+    if (!$plugin_instance instanceof FieldTypePluginInterface) {
+      throw new \Exception(sprintf('%s must implement \Drupal\payment\Plugin\Payment\FieldTypePluginInterface if it is to be contained by a field item.', get_class($plugin_instance)));
+    }
+    $plugin_instance->setFieldItem($this);
+
+    return $plugin_instance;
+  }
+
+  /**
+   * Gets the instantiated plugin.
+   *
+   * @return \Drupal\Component\Plugin\PluginInspectionInterface|null
+   *   The plugin or NULL of no plugin ID was configured yet.
+   */
+  public function getContainedPluginInstance() {
+    if (!$this->pluginInstance && $this->getContainedPluginId()) {
+      $this->pluginInstance = $this->createContainedPluginInstance($this->getContainedPluginId(), $this->getContainedPluginConfiguration());
+    }
+
+    return $this->pluginInstance;
+  }
+
+  /**
+   * Sets the instantiated plugin.
+   *
+   * @param \Drupal\Component\Plugin\PluginInspectionInterface $plugin_instance
+   *
+   * @return $this
+   */
+  public function setContainedPluginInstance(PluginInspectionInterface $plugin_instance) {
+    if ($this->pluginInstance) {
+      $this->pluginInstance->unsetFieldItem();
+    }
+    $this->pluginInstance = $plugin_instance;
+    $this->setContainedPluginId($plugin_instance->getPluginId());
+
+    return $this;
+  }
+
+  /**
+   * Resets the instantiated plugin.
+   *
+   * @return $this
+   */
+  public function resetContainedPluginInstance() {
+    if ($this->pluginInstance) {
+      $this->pluginInstance->unsetFieldItem();
+    }
+    $this->pluginInstance = NULL;
+    $this->get('plugin_id')->applyDefaultValue();
+    $this->get('plugin_configuration')->setValue(array());
+
+    return $this;
+  }
+
+  /**
+   * Gets the plugin ID.
+   *
+   * @return string
+   */
+  public function getContainedPluginId() {
+    return $this->get('plugin_id')->getValue();
+  }
+
+  /**
+   * Sets the plugin ID.
+   *
+   * @param string $plugin_id
+   *
+   * @return $this
+   */
+  public function setContainedPluginId($plugin_id) {
+    $this->get('plugin_id')->setValue($plugin_id);
+
+    return $this;
+  }
+
+  /**
+   * Sets the plugin configuration.
+   *
+   * @return mixed[]
+   */
+  public function getContainedPluginConfiguration() {
+    return $this->get('plugin_configuration')->getValue();
+  }
+
+  /**
+   * Sets the plugin configuration.
+   *
+   * @param mixed[] $configuration
+   *
+   * @return $this
+   */
+  public function setContainedPluginConfiguration(array $configuration) {
+    $this->get('plugin_configuration')->setValue($configuration);
+
+    return $this;
+  }
+
+  /**
+   * Sets the plugin configuration without typed data notifications.
+   *
+   * @param mixed[] $configuration
+   *
+   * @return $this
+   */
+  public function _setContainedPluginConfigurationFromContainedPlugin(array $configuration) {
+    $this->calledFromContainedPlugin = TRUE;
+    $this->setContainedPluginConfiguration($configuration);
+    $this->calledFromContainedPlugin = FALSE;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
+    $properties['plugin_id'] = DataDefinition::create('string')
+      ->setLabel(t('Plugin ID'));
+    $properties['plugin_configuration'] = MapDataDefinition::create('any')
+      ->setLabel(t('Plugin configuration'));
+
+    return $properties;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onChange($property_name) {
+    if ($property_name == 'plugin_id') {
+      if ($this->getPluginManager()->hasDefinition($this->getContainedPluginId())) {
+        $this->pluginInstance = $this->createContainedPluginInstance($this->getContainedPluginId());
+      }
+      else {
+        $this->pluginInstance = NULL;
+      }
+      $this->setContainedPluginConfiguration([]);
+    }
+    if ($property_name == 'plugin_configuration' && !$this->calledFromContainedPlugin && $this->getPluginManager()->hasDefinition($this->getContainedPluginId())) {
+      $class = $this->getPluginManager()->getDefinition($this->getContainedPluginId())['class'];
+      if (in_array('Drupal\Component\Plugin\ConfigurablePluginInterface', class_implements($class))) {
+        /** @var \Drupal\Component\Plugin\ConfigurablePluginInterface $plugin */
+        $plugin = $this->pluginInstance;
+        $plugin->setConfiguration($this->get($property_name)->getValue());
+      }
+      else {
+        $this->writePropertyValue($property_name, []);
+      }
+    }
+    parent::onChange($property_name);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function mainPropertyName() {
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function schema(FieldStorageDefinitionInterface $field_definition) {
+    $columns = array(
+      'plugin_id' => array(
+        'description' => 'The plugin ID.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+      ),
+      'plugin_configuration' => array(
+        'description' => 'The plugin configuration.',
+        'type' => 'blob',
+        'not null' => TRUE,
+        'serialize' => TRUE,
+      ),
+    );
+
+    $schema = array(
+      'columns' => $columns,
+    );
+
+    return $schema;
+  }
+
+}
diff --git a/src/Plugin/Payment/FieldTypePluginInterface.php b/src/Plugin/Payment/FieldTypePluginInterface.php
new file mode 100644
index 0000000..795087c
--- /dev/null
+++ b/src/Plugin/Payment/FieldTypePluginInterface.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\Payment\FieldTypePluginInterface.
+ */
+
+namespace Drupal\payment\Plugin\Payment;
+
+use Drupal\payment\Plugin\Field\FieldType\PluginBagItemBase;
+
+/**
+ * Defines a plugin that can be contained in a field.
+ */
+interface FieldTypePluginInterface {
+
+  /**
+   * Sets the containing field item.
+   *
+   * @param \Drupal\payment\Plugin\Field\FieldType\PluginBagItemBase $field_item
+   *
+   * @return $this
+   */
+  public function setFieldItem(PluginBagItemBase $field_item);
+
+  /**
+   * Unsets the containing field item.
+   *
+   * @return $this
+   */
+  public function unsetFieldItem();
+
+}
diff --git a/src/Plugin/Payment/FieldTypePluginTrait.php b/src/Plugin/Payment/FieldTypePluginTrait.php
new file mode 100644
index 0000000..bcd7cf9
--- /dev/null
+++ b/src/Plugin/Payment/FieldTypePluginTrait.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\payment\Plugin\Payment\FieldTypePluginTrait.
+ */
+
+namespace Drupal\payment\Plugin\Payment;
+
+use Drupal\payment\Plugin\Field\FieldType\PluginBagItemBase;
+
+/**
+ * Provides a trait that implements
+ * \Drupal\payment\Plugin\Payment\FieldTypePluginInterface and parts of
+ * \Drupal\Component\Plugin\PluginInspectionInterface.
+ */
+trait FieldTypePluginTrait {
+
+  /**
+   * The containing field item.
+   *
+   * @var \Drupal\payment\Plugin\Field\FieldType\PluginBagItemBase
+   */
+  protected $fieldItem;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setFieldItem(PluginBagItemBase $field_item) {
+    $this->fieldItem = $field_item;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function unsetFieldItem() {
+    $this->fieldItem = NULL;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setContainingFieldItemConfiguration() {
+    if ($this->fieldItem) {
+      /** @var \Drupal\payment\Plugin\Payment\FieldTypePluginTrait|\Drupal\Component\Plugin\ConfigurablePluginInterface $this */
+      $this->fieldItem->_setContainedPluginConfigurationFromContainedPlugin($this->getConfiguration());
+    }
+  }
+
+}
diff --git a/src/Plugin/Payment/Method/PaymentMethodBase.php b/src/Plugin/Payment/Method/PaymentMethodBase.php
index 8c749b5..f3597d6 100644
--- a/src/Plugin/Payment/Method/PaymentMethodBase.php
+++ b/src/Plugin/Payment/Method/PaymentMethodBase.php
@@ -18,6 +18,8 @@ use Drupal\payment\Event\PaymentExecuteAccess;
 use Drupal\payment\Event\PaymentPreCapture;
 use Drupal\payment\Event\PaymentPreExecute;
 use Drupal\payment\Event\PaymentPreRefund;
+use Drupal\payment\Plugin\Payment\FieldTypePluginInterface;
+use Drupal\payment\Plugin\Payment\FieldTypePluginTrait;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
@@ -30,7 +32,9 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  *   form.
  * - message_text_format: The ID of the text format to format message_text with.
  */
-abstract class PaymentMethodBase extends PluginBase implements ContainerFactoryPluginInterface, PaymentMethodInterface, PaymentMethodCapturePaymentInterface, PaymentMethodRefundPaymentInterface {
+abstract class PaymentMethodBase extends PluginBase implements ContainerFactoryPluginInterface, FieldTypePluginInterface, PaymentMethodInterface, PaymentMethodCapturePaymentInterface, PaymentMethodRefundPaymentInterface {
+
+  use FieldTypePluginTrait;
 
   /**
    * The event dispatcher.
diff --git a/src/Plugin/Payment/Status/PaymentStatusBase.php b/src/Plugin/Payment/Status/PaymentStatusBase.php
index b444597..ac2ca98 100644
--- a/src/Plugin/Payment/Status/PaymentStatusBase.php
+++ b/src/Plugin/Payment/Status/PaymentStatusBase.php
@@ -9,6 +9,8 @@ namespace Drupal\payment\Plugin\Payment\Status;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Plugin\PluginBase;
 use Drupal\payment\Entity\PaymentInterface;
+use Drupal\payment\Plugin\Payment\FieldTypePluginInterface;
+use Drupal\payment\Plugin\Payment\FieldTypePluginTrait;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -17,7 +19,9 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  * Plugins extending this class should provide a configuration schema that
  * extends payment.plugin_configuration.payment_status.payment_base.
  */
-abstract class PaymentStatusBase extends PluginBase implements ContainerFactoryPluginInterface, PaymentStatusInterface {
+abstract class PaymentStatusBase extends PluginBase implements ContainerFactoryPluginInterface, FieldTypePluginInterface, PaymentStatusInterface {
+
+  use FieldTypePluginTrait;
 
   /**
    * The payment this payment status is for.
diff --git a/src/Plugin/Payment/Type/PaymentTypeBase.php b/src/Plugin/Payment/Type/PaymentTypeBase.php
index fc1a837..2cbc86d 100644
--- a/src/Plugin/Payment/Type/PaymentTypeBase.php
+++ b/src/Plugin/Payment/Type/PaymentTypeBase.php
@@ -11,13 +11,17 @@ use Drupal\Core\Plugin\PluginBase;
 use Drupal\payment\Entity\PaymentInterface;
 use Drupal\payment\Event\PaymentEvents;
 use Drupal\payment\Event\PaymentTypePreResumeContext;
+use Drupal\payment\Plugin\Payment\FieldTypePluginInterface;
+use Drupal\payment\Plugin\Payment\FieldTypePluginTrait;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
  * Defines base payment type.
  */
-abstract class PaymentTypeBase extends PluginBase implements ContainerFactoryPluginInterface, PaymentTypeInterface {
+abstract class PaymentTypeBase extends PluginBase implements ContainerFactoryPluginInterface, FieldTypePluginInterface, PaymentTypeInterface {
+
+  use FieldTypePluginTrait;
 
   /**
    * The event dispatcher.
diff --git a/src/Tests/Plugin/Field/FieldType/PluginBagItemBaseTest.php b/src/Tests/Plugin/Field/FieldType/PluginBagItemBaseTest.php
new file mode 100644
index 0000000..0891756
--- /dev/null
+++ b/src/Tests/Plugin/Field/FieldType/PluginBagItemBaseTest.php
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field\Tests\PluginBagItemBaseTest.
+ */
+
+namespace Drupal\payment\Tests\Plugin\Field\FieldType;
+
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\payment_test\Plugin\PluginTest\MockConfigurablePlugin;
+use Drupal\simpletest\KernelTestBase;
+
+/**
+ * Tests \Drupal\payment\Plugin\Field\Plugin\Field\FieldType\PluginBagItemBase.
+ *
+ * @group Payment
+ */
+class PluginBagItemBaseTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['payment_test', 'plugin_test'];
+
+  /**
+   * The field item under test.
+   *
+   * @var \Drupal\payment\Plugin\Field\FieldType\PluginBagItemBase
+   */
+  protected $fieldItem;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $field_definition = BaseFieldDefinition::create('payment_test_plugin_bag');
+
+    $this->fieldItem = \Drupal::typedDataManager()->create($field_definition)[0];
+  }
+
+  /**
+   * Tests the field.
+   */
+  protected function testField() {
+    $plugin_id = 'payment_test_plugin';
+    $plugin_id_configurable = 'payment_test_configurable_plugin';
+    $plugin_configuration = [
+      'foo' => $this->randomMachineName()
+    ];
+
+    // Test default values.
+    $this->assertNull($this->fieldItem->getContainedPluginId());
+    $this->assertEqual($this->fieldItem->getContainedPluginConfiguration(), []);
+    $this->assertNull($this->fieldItem->getContainedPluginInstance());
+
+    // Test setting values and auto-instantiation for a non-configurable plugin.
+    $this->fieldItem->setContainedPluginId($plugin_id);
+    $this->assertEqual($this->fieldItem->getContainedPluginId(), $plugin_id);
+    $this->fieldItem->setContainedPluginConfiguration($plugin_configuration);
+    $this->assertEqual($this->fieldItem->getContainedPluginConfiguration(), []);
+
+    // Test setting values and auto-instantiation for a configurable plugin.
+    $this->fieldItem->setContainedPluginId($plugin_id_configurable);
+    $this->assertEqual($this->fieldItem->getContainedPluginId(), $plugin_id_configurable);
+    $this->fieldItem->setContainedPluginConfiguration($plugin_configuration);
+    $this->assertEqual($this->fieldItem->getContainedPluginConfiguration(), $plugin_configuration);
+    /** @var \Drupal\payment_test\Plugin\PluginTest\MockConfigurablePlugin $plugin_instance_a */
+    $plugin_instance_a = $this->fieldItem->getContainedPluginInstance();
+    $this->assertTrue($plugin_instance_a instanceof MockConfigurablePlugin);
+    $this->assertEqual($plugin_instance_a->getConfiguration(), $plugin_configuration);
+    $altered_plugin_configuration = $plugin_configuration += [
+      'bar' => $this->randomMachineName(),
+    ];
+    $plugin_instance_a->setConfiguration($altered_plugin_configuration);
+    $this->assertEqual($plugin_instance_a->getConfiguration(), $altered_plugin_configuration);
+    $this->assertEqual($this->fieldItem->getContainedPluginConfiguration(), $altered_plugin_configuration);
+
+    // Test resetting the values.
+    $this->fieldItem->resetContainedPluginInstance();
+    $this->assertNull($this->fieldItem->getContainedPluginId());
+    $this->assertEqual($this->fieldItem->getContainedPluginConfiguration(), []);
+    $this->assertNull($this->fieldItem->getContainedPluginInstance());
+
+    // Test setting values again and auto-instantiation.
+    $this->fieldItem->setContainedPluginId($plugin_id_configurable);
+    $this->assertEqual($this->fieldItem->getContainedPluginId(), $plugin_id_configurable);
+    $this->fieldItem->setContainedPluginConfiguration($plugin_configuration);
+    $this->assertEqual($this->fieldItem->getContainedPluginConfiguration(), $plugin_configuration);
+    /** @var \Drupal\payment_test\Plugin\PluginTest\MockConfigurablePlugin $plugin_instance_b */
+    $plugin_instance_b = $this->fieldItem->getContainedPluginInstance();
+    $this->assertTrue($plugin_instance_b instanceof MockConfigurablePlugin);
+    $this->assertEqual($plugin_instance_b->getConfiguration(), $plugin_configuration);
+    // Make sure this is indeed a new instance and not the old one.
+    $this->assertNotIdentical(spl_object_hash($plugin_instance_b), spl_object_hash($plugin_instance_a));
+    // Make sure changing the configuration on the new instance changes the
+    // configuration in the field item.
+    $altered_plugin_configuration_a = $plugin_configuration + [
+      'bar' => $this->randomMachineName(),
+    ];
+    $altered_plugin_configuration_b = $plugin_configuration + [
+      'baz' => $this->randomMachineName(),
+    ];
+    $plugin_instance_b->setConfiguration($altered_plugin_configuration_b);
+    $this->assertEqual($this->fieldItem->getContainedPluginConfiguration(), $altered_plugin_configuration_b);
+    // Make sure changing the configuration on the old instance no longer has
+    // any effect on the field item.
+    $plugin_instance_a->setConfiguration($altered_plugin_configuration_a);
+    $this->assertEqual($this->fieldItem->getContainedPluginConfiguration(), $altered_plugin_configuration_b);
+  }
+
+}
