diff --git a/payment/src/Entity/Payment/PaymentAccess.php b/payment/src/Entity/Payment/PaymentAccess.php
index 0e4d458..3a8c082 100644
--- a/payment/src/Entity/Payment/PaymentAccess.php
+++ b/payment/src/Entity/Payment/PaymentAccess.php
@@ -10,6 +10,8 @@ namespace Drupal\payment\Entity\Payment;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityAccessController;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\payment\Entity\PaymentInterface;
+use Drupal\payment\Plugin\Payment\Method\PaymentMethodUpdatePaymentStatusInterface;
 
 /**
  * Defines the default list controller for ConfigEntity objects.
@@ -21,6 +23,26 @@ class PaymentAccess extends EntityAccessController {
    */
   protected function checkAccess(EntityInterface $payment, $operation, $langcode, AccountInterface $account) {
     /** @var \Drupal\payment\Entity\PaymentInterface $payment */
+
+    if ($operation == 'update_status') {
+      $payment_method = $payment->getPaymentMethod();
+      if ($payment_method instanceof PaymentMethodUpdatePaymentStatusInterface && !$payment_method->updatePaymentStatusAccess($account)) {
+        return FALSE;
+      }
+    }
+    return $this->checkAccessPermission($payment, $operation, $account);
+  }
+
+  /**
+   * Checks if a user has permission to perform a payment operation.
+   *
+   * @param \Drupal\payment\Entity\PaymentInterface $payment
+   * @param string $operation
+   * @param \Drupal\Core\Session\AccountInterface
+   *
+   * @return bool
+   */
+  protected function checkAccessPermission(PaymentInterface $payment, $operation, AccountInterface $account) {
     return $account->hasPermission('payment.payment.' . $operation . '.any') || $account->hasPermission('payment.payment.' . $operation . '.own') && $account->id() == $payment->getOwnerId();
   }
 
diff --git a/payment/src/Entity/Payment/PaymentStatusForm.php b/payment/src/Entity/Payment/PaymentStatusForm.php
index 60dbd8c..1605be0 100644
--- a/payment/src/Entity/Payment/PaymentStatusForm.php
+++ b/payment/src/Entity/Payment/PaymentStatusForm.php
@@ -13,6 +13,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Routing\UrlGeneratorInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\payment\Plugin\Payment\Method\PaymentMethodUpdatePaymentStatusInterface;
 use Drupal\payment\Plugin\Payment\Status\PaymentStatusManagerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -78,8 +79,15 @@ class PaymentStatusForm extends EntityForm {
    * {@inheritdoc}
    */
   public function form(array $form, array &$form_state) {
+    $limit_plugin_ids = NULL;
+    /** @var \Drupal\payment\Entity\PaymentInterface $payment */
+    $payment = $this->getEntity();
+    $payment_method = $payment->getPaymentMethod();
+    if ($payment_method instanceof PaymentMethodUpdatePaymentStatusInterface) {
+      $limit_plugin_ids = $payment_method->getSettablePaymentStatuses($this->currentUser, $payment);
+    }
     $form['plugin_id'] = array(
-      '#options' => $this->paymentStatusManager->options(),
+      '#options' => $this->paymentStatusManager->options($limit_plugin_ids),
       '#required' => TRUE,
       '#title' => $this->t('New status'),
       '#type' => 'select',
diff --git a/payment/src/Plugin/Payment/Method/PaymentMethodUpdatePaymentStatusInterface.php b/payment/src/Plugin/Payment/Method/PaymentMethodUpdatePaymentStatusInterface.php
new file mode 100644
index 0000000..caa2ef8
--- /dev/null
+++ b/payment/src/Plugin/Payment/Method/PaymentMethodUpdatePaymentStatusInterface.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\Payment\Method\PaymentMethodUpdatePaymentStatusInterface.
+ */
+
+namespace Drupal\payment\Plugin\Payment\Method;
+
+use Drupal\Component\Plugin\ConfigurablePluginInterface;
+use Drupal\Component\Plugin\PluginInspectionInterface;
+use Drupal\Core\Plugin\PluginFormInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\payment\Entity\PaymentInterface;
+
+/**
+ * Defines a payment method that controls payment status updates.
+ *
+ * Users can update payment statuses if they have the
+ * "payment.payment.update_status.any" and/or
+ * "payment.payment.update_status.own" permissions. By implementing this
+ * interface, payment methods can exercise additional control on top of these
+ * permissions.
+ */
+interface PaymentMethodUpdatePaymentStatusInterface {
+
+  /**
+   * Checks if the payment status can be updated.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *
+   * @return bool
+   */
+  public function updatePaymentStatusAccess(AccountInterface $account);
+
+  /**
+   * Returns the statuses that can be set on a payment.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   * @param \Drupal\payment\Entity\PaymentInterface $payment
+   *   The payment to set the status on.
+   *
+   * @return string[]
+   *   The plugin IDs of the settable statuses.
+   */
+  public function getSettablePaymentStatuses(AccountInterface $account, PaymentInterface $payment);
+
+}
diff --git a/payment/src/Plugin/Payment/Status/PaymentStatusManager.php b/payment/src/Plugin/Payment/Status/PaymentStatusManager.php
index fcb9fc3..de9c3b5 100644
--- a/payment/src/Plugin/Payment/Status/PaymentStatusManager.php
+++ b/payment/src/Plugin/Payment/Status/PaymentStatusManager.php
@@ -60,20 +60,23 @@ class PaymentStatusManager extends DefaultPluginManager implements PaymentStatus
   /**
    * {@inheritdoc}
    */
-  public function options() {
-    return $this->optionsLevel($this->hierarchy(), 0);
+  public function options(array $limit_plugin_ids = NULL) {
+    return $this->optionsLevel($this->hierarchy($limit_plugin_ids), 0);
   }
 
   /**
    * {@inheritdoc}
    */
-  public function hierarchy() {
+  public function hierarchy(array $limit_plugin_ids = NULL) {
     static $hierarchy = NULL;
 
     if (is_null($hierarchy)) {
       $parents = array();
       $children = array();
       $definitions = $this->getDefinitions();
+      if (is_array($limit_plugin_ids)) {
+        $definitions = array_intersect_key($definitions, array_flip($limit_plugin_ids));
+      }
       uasort($definitions, array($this, 'sort'));
       foreach ($definitions as $plugin_id => $definition) {
         if (!empty($definition['parent_id'])) {
diff --git a/payment/src/Plugin/Payment/Status/PaymentStatusManagerInterface.php b/payment/src/Plugin/Payment/Status/PaymentStatusManagerInterface.php
index 216a5ad..4368815 100644
--- a/payment/src/Plugin/Payment/Status/PaymentStatusManagerInterface.php
+++ b/payment/src/Plugin/Payment/Status/PaymentStatusManagerInterface.php
@@ -29,19 +29,25 @@ interface PaymentStatusManagerInterface extends PluginManagerInterface {
   /**
    * Returns payment status options.
    *
+   * @param string[]|null $limit_plugin_ids
+   *   An array of plugin IDs to limit the options to, or NULL to allow all.
+   *
    * @return array
    *   Keys are plugin IDs. Values are plugin labels.
    */
-  public function options();
+  public function options(array $limit_plugin_ids = NULL);
 
   /**
    * Returns a hierarchical representation of payment statuses.
    *
+   * @param string[]|null $limit_plugin_ids
+   *   An array of plugin IDs to limit the statuses to, or NULL to allow all.
+   *
    * @return array
    *   A possibly infinitely nested associative array. Keys are plugin IDs and
    *   values are arrays of similar structure as this method's return value.
    */
-  public function hierarchy();
+  public function hierarchy(array $limit_plugin_ids = NULL);
 
   /**
    * Gets a payment status's ancestors.
diff --git a/payment/tests/src/Entity/Payment/PaymentAccessUnitTest.php b/payment/tests/src/Entity/Payment/PaymentAccessUnitTest.php
index 249e551..48355d6 100644
--- a/payment/tests/src/Entity/Payment/PaymentAccessUnitTest.php
+++ b/payment/tests/src/Entity/Payment/PaymentAccessUnitTest.php
@@ -8,6 +8,8 @@
 namespace Drupal\payment\Tests\Entity\Payment;
 
 use Drupal\payment\Entity\Payment\PaymentAccess;
+use Drupal\payment\Plugin\Payment\Method\PaymentMethodInterface;
+use Drupal\payment\Plugin\Payment\Method\PaymentMethodUpdatePaymentStatusInterface;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -44,6 +46,69 @@ class PaymentAccessUnitTest extends UnitTestCase {
   /**
    * @covers ::checkAccess
    */
+  public function testCheckAccessUpdateStatusWithAccess() {
+    $operation = 'update_status';
+    $language_code = $this->randomName();
+
+    $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
+    $account->expects($this->once())
+      ->method('hasPermission')
+      ->with('payment.payment.update_status.any')
+      ->will($this->returnValue(TRUE));
+
+    $payment_method = $this->getMock('\Drupal\payment\Tests\Entity\Payment\PaymentAccessUnitTestDummyPaymentMethodUpdateStatusInterface');
+    $payment_method->expects($this->once())
+      ->method('updatePaymentStatusAccess')
+      ->with($account)
+      ->will($this->returnValue(TRUE));
+
+    $payment = $this->getMockBuilder('\Drupal\payment\Entity\Payment')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $payment->expects($this->atLeastOnce())
+      ->method('getPaymentMethod')
+      ->will($this->returnValue($payment_method));
+
+    $class = new \ReflectionClass($this->accessController);
+    $method = $class->getMethod('checkAccess');
+    $method->setAccessible(TRUE);
+    $this->assertTRUE($method->invokeArgs($this->accessController, array($payment, $operation, $language_code, $account)));
+  }
+
+  /**
+   * @covers ::checkAccess
+   */
+  public function testCheckAccessUpdateStatusWithoutAccess() {
+    $operation = 'update_status';
+    $language_code = $this->randomName();
+
+    $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
+    $account->expects($this->never())
+      ->method('hasPermission');
+
+    $payment_method = $this->getMock('\Drupal\payment\Tests\Entity\Payment\PaymentAccessUnitTestDummyPaymentMethodUpdateStatusInterface');
+    $payment_method->expects($this->once())
+      ->method('updatePaymentStatusAccess')
+      ->with($account)
+      ->will($this->returnValue(FALSE));
+
+    $payment = $this->getMockBuilder('\Drupal\payment\Entity\Payment')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $payment->expects($this->atLeastOnce())
+      ->method('getPaymentMethod')
+      ->will($this->returnValue($payment_method));
+
+    $class = new \ReflectionClass($this->accessController);
+    $method = $class->getMethod('checkAccess');
+    $method->setAccessible(TRUE);
+    $this->assertFALSE($method->invokeArgs($this->accessController, array($payment, $operation, $language_code, $account)));
+  }
+
+  /**
+   * @covers ::checkAccess
+   * @covers ::checkAccessPermission
+   */
   public function testCheckAccessWithoutPermission() {
     $operation = $this->randomName();
     $language_code = $this->randomName();
@@ -63,6 +128,7 @@ class PaymentAccessUnitTest extends UnitTestCase {
 
   /**
    * @covers ::checkAccess
+   * @covers ::checkAccessPermission
    */
   public function testCheckAccessWithAnyPermission() {
     $operation = $this->randomName();
@@ -84,6 +150,7 @@ class PaymentAccessUnitTest extends UnitTestCase {
 
   /**
    * @covers ::checkAccess
+   * @covers ::checkAccessPermission
    */
   public function testCheckAccessWithOwnPermission() {
     $owner_id = mt_rand();
@@ -146,3 +213,9 @@ class PaymentAccessUnitTest extends UnitTestCase {
   }
 
 }
+
+/**
+ * Extends two interfaces, because we can only mock one.
+ */
+interface PaymentAccessUnitTestDummyPaymentMethodUpdateStatusInterface extends PaymentMethodUpdatePaymentStatusInterface, PaymentMethodInterface {
+}
diff --git a/payment/tests/src/Entity/Payment/PaymentStatusFormUnitTest.php b/payment/tests/src/Entity/Payment/PaymentStatusFormUnitTest.php
index 42071f9..a2a4a04 100644
--- a/payment/tests/src/Entity/Payment/PaymentStatusFormUnitTest.php
+++ b/payment/tests/src/Entity/Payment/PaymentStatusFormUnitTest.php
@@ -9,7 +9,9 @@ namespace Drupal\payment\Tests\Entity\Payment;
 
   use Drupal\Core\Language\Language;
   use Drupal\payment\Entity\Payment\PaymentStatusForm;
-use Drupal\Tests\UnitTestCase;
+  use Drupal\payment\Plugin\Payment\Method\PaymentMethodInterface;
+  use Drupal\payment\Plugin\Payment\Method\PaymentMethodUpdatePaymentStatusInterface;
+  use Drupal\Tests\UnitTestCase;
 
 /**
  * @coversDefaultClass \Drupal\payment\Entity\Payment\PaymentStatusForm
@@ -115,8 +117,11 @@ class PaymentStatusFormUnitTest extends UnitTestCase {
    * @covers ::form
    */
   public function testFormWithDateTimeModule() {
+    $settable_payment_status_ids = array($this->randomName());
+
     $this->paymentStatusManager->expects($this->once())
-      ->method('options');
+      ->method('options')
+      ->with($settable_payment_status_ids);
 
     $this->moduleHandler->expects($this->once())
       ->method('moduleExists')
@@ -125,6 +130,15 @@ class PaymentStatusFormUnitTest extends UnitTestCase {
 
     $language = new Language();
 
+    $payment_method = $this->getMock('\Drupal\payment\Tests\Entity\Payment\PaymentStatusFormUnitTestDummyPaymentMethodUpdateStatusInterface');
+    $payment_method->expects($this->once())
+      ->method('getSettablePaymentStatuses')
+      ->with($this->currentUser)
+      ->will($this->returnValue($settable_payment_status_ids));
+
+    $this->payment->expects($this->atLeastOnce())
+      ->method('getPaymentMethod')
+      ->will($this->returnValue($payment_method));
     $this->payment->expects($this->any())
       ->method('language')
       ->will($this->returnValue($language));
@@ -259,3 +273,9 @@ class PaymentStatusFormUnitTest extends UnitTestCase {
   }
 
 }
+
+/**
+ * Extends two interfaces, because we can only mock one.
+ */
+interface PaymentStatusFormUnitTestDummyPaymentMethodUpdateStatusInterface extends PaymentMethodUpdatePaymentStatusInterface, PaymentMethodInterface {
+}
diff --git a/payment/tests/src/Plugin/Payment/Status/PaymentStatusManagerUnitTest.php b/payment/tests/src/Plugin/Payment/Status/PaymentStatusManagerUnitTest.php
index e18f330..f95cfed 100644
--- a/payment/tests/src/Plugin/Payment/Status/PaymentStatusManagerUnitTest.php
+++ b/payment/tests/src/Plugin/Payment/Status/PaymentStatusManagerUnitTest.php
@@ -152,6 +152,9 @@ class PaymentStatusManagerUnitTest extends UnitTestCase {
         'label' => $child_label,
         'parent_id' => 'foo',
       ),
+      'baz' => array(
+        'label' => $this->randomName(),
+      ),
     );
     $this->discovery->expects($this->once())
       ->method('getDefinitions')
@@ -161,7 +164,7 @@ class PaymentStatusManagerUnitTest extends UnitTestCase {
         'bar' => array(),
       ),
     );
-    $this->assertSame($expected_hierarchy, $this->paymentStatusManager->hierarchy());
+    $this->assertSame($expected_hierarchy, $this->paymentStatusManager->hierarchy(array('foo', 'bar')));
   }
 
   /**
@@ -180,6 +183,9 @@ class PaymentStatusManagerUnitTest extends UnitTestCase {
         'label' => $child_label,
         'parent_id' => 'foo',
       ),
+      'baz' => array(
+        'label' => $this->randomName(),
+      ),
     );
     $this->discovery->expects($this->once())
       ->method('getDefinitions')
@@ -188,7 +194,7 @@ class PaymentStatusManagerUnitTest extends UnitTestCase {
       'foo' => $parent_label,
       'bar' => '- ' . $child_label,
     );
-    $this->assertSame($expected_options, $this->paymentStatusManager->options());
+    $this->assertSame($expected_options, $this->paymentStatusManager->options(array('foo', 'bar')));
   }
 
   /**
