diff --git a/modules/payment/src/Entity/PaymentGateway.php b/modules/payment/src/Entity/PaymentGateway.php
index 5ec473b2..91664ae6 100644
--- a/modules/payment/src/Entity/PaymentGateway.php
+++ b/modules/payment/src/Entity/PaymentGateway.php
@@ -3,6 +3,7 @@
 namespace Drupal\commerce_payment\Entity;
 
 use Drupal\commerce_payment\PaymentGatewayPluginCollection;
+use Drupal\Core\Condition\ConditionPluginCollection;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 
 /**
@@ -45,6 +46,7 @@ use Drupal\Core\Config\Entity\ConfigEntityBase;
  *     "status",
  *     "plugin",
  *     "configuration",
+ *     "visibility",
  *   },
  *   links = {
  *     "add-form" = "/admin/commerce/config/payment-gateways/add",
@@ -99,6 +101,20 @@ class PaymentGateway extends ConfigEntityBase implements PaymentGatewayInterface
   protected $pluginCollection;
 
   /**
+   * The condition plugin manager.
+   *
+   * @var \Drupal\Core\Executable\ExecutableManagerInterface
+   */
+  protected $conditionPluginManager;
+
+  /**
+   * The visibility settings for this block.
+   *
+   * @var array
+   */
+  protected $visibility = [];
+
+  /**
    * {@inheritdoc}
    */
   public function getWeight() {
@@ -142,6 +158,7 @@ class PaymentGateway extends ConfigEntityBase implements PaymentGatewayInterface
   public function getPluginCollections() {
     return [
       'configuration' => $this->getPluginCollection(),
+      'visibility' => $this->getVisibilityConditions(),
     ];
   }
 
@@ -161,4 +178,56 @@ class PaymentGateway extends ConfigEntityBase implements PaymentGatewayInterface
     return $this->pluginCollection;
   }
 
+  /**
+   * Gets the condition plugin manager.
+   *
+   * @return \Drupal\Core\Executable\ExecutableManagerInterface
+   *   The condition plugin manager.
+   */
+  protected function conditionPluginManager() {
+    if (!isset($this->conditionPluginManager)) {
+      $this->conditionPluginManager = \Drupal::service('plugin.manager.condition');
+    }
+    return $this->conditionPluginManager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getVisibility() {
+    return $this->getVisibilityConditions()->getConfiguration();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setVisibilityConfig($instance_id, array $configuration) {
+    $conditions = $this->getVisibilityConditions();
+    if (!$conditions->has($instance_id)) {
+      $configuration['id'] = $instance_id;
+      $conditions->addInstanceId($instance_id, $configuration);
+    }
+    else {
+      $conditions->setInstanceConfiguration($instance_id, $configuration);
+    }
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getVisibilityConditions() {
+    if (!isset($this->visibilityCollection)) {
+      $this->visibilityCollection = new ConditionPluginCollection($this->conditionPluginManager(), $this->get('visibility'));
+    }
+    return $this->visibilityCollection;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getVisibilityCondition($instance_id) {
+    return $this->getVisibilityConditions()->get($instance_id);
+  }
+
 }
diff --git a/modules/payment/src/Form/PaymentGatewayForm.php b/modules/payment/src/Form/PaymentGatewayForm.php
index 9d44c121..44f5445c 100644
--- a/modules/payment/src/Form/PaymentGatewayForm.php
+++ b/modules/payment/src/Form/PaymentGatewayForm.php
@@ -4,8 +4,14 @@ namespace Drupal\commerce_payment\Form;
 
 use Drupal\commerce\Form\CommercePluginEntityFormBase;
 use Drupal\commerce_payment\PaymentGatewayManager;
+use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\PaymentGatewayBase;
+use Drupal\Component\Plugin\ContextAwarePluginInterface;
 use Drupal\Component\Utility\Html;
+use Drupal\Core\Condition\ConditionManager;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Form\SubformState;
+use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
+use Drupal\Core\Plugin\PluginFormFactoryInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 class PaymentGatewayForm extends CommercePluginEntityFormBase {
@@ -18,13 +24,46 @@ class PaymentGatewayForm extends CommercePluginEntityFormBase {
   protected $pluginManager;
 
   /**
+   * The condition plugin manager.
+   *
+   * @var \Drupal\Core\Condition\ConditionManager
+   */
+  protected $conditionManager;
+
+  /**
+   * The context repository service.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
+   */
+  protected $contextRepository;
+
+  /**
+   * The plugin form manager.
+   *
+   * @var \Drupal\Core\Plugin\PluginFormFactoryInterface
+   */
+  protected $pluginFormFactory;
+
+  /**
+   * Qualified conditions.
+   */
+  protected $qualifiedConditions = [
+    'user_role',
+    'entity_bundle:commerce_order',
+    'commerce_order',
+  ];
+
+  /**
    * Constructs a new PaymentGatewayForm object.
    *
    * @param \Drupal\commerce_payment\PaymentGatewayManager $plugin_manager
    *   The payment gateway plugin manager.
    */
-  public function __construct(PaymentGatewayManager $plugin_manager) {
+  public function __construct(PaymentGatewayManager $plugin_manager, ConditionManager $condition_manager, ContextRepositoryInterface $context_repository, PluginFormFactoryInterface $plugin_form_manager) {
     $this->pluginManager = $plugin_manager;
+    $this->conditionManager = $condition_manager;
+    $this->contextRepository = $context_repository;
+    $this->pluginFormFactory = $plugin_form_manager;
   }
 
   /**
@@ -32,7 +71,10 @@ class PaymentGatewayForm extends CommercePluginEntityFormBase {
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('plugin.manager.commerce_payment_gateway')
+      $container->get('plugin.manager.commerce_payment_gateway'),
+      $container->get('plugin.manager.condition'),
+      $container->get('context.repository'),
+      $container->get('plugin_form.factory')
     );
   }
 
@@ -112,7 +154,28 @@ class PaymentGatewayForm extends CommercePluginEntityFormBase {
       '#title' => $this->t('Enabled'),
       '#default_value' => $gateway->status(),
     ];
-
+    // Store the gathered contexts in the form state for other objects to use
+    // during form building.
+    $contexts = $this->contextRepository->getAvailableContexts();
+    unset($contexts['@node.node_route_context:node']);
+    $form_state->setTemporaryValue('gathered_contexts', $contexts);
+    $form['#tree'] = TRUE;
+    $form['settings'] = [];
+    $form['visibility'] = $this->buildVisibilityInterface([], $form_state);
+    // If creating a new block, calculate a safe default machine name.
+    $form['id'] = array(
+      '#type' => 'machine_name',
+      '#maxlength' => 64,
+      '#description' => $this->t('A unique name for this block instance. Must be alpha-numeric and underscore separated.'),
+      '#default_value' => !$this->entity->isNew() ? $this->entity->id() : $this->getUniqueMachineName($this->entity),
+      '#machine_name' => array(
+        'exists' => '\Drupal\block\Entity\Block::load',
+        'replace_pattern' => '[^a-z0-9_.]+',
+        'source' => array('settings', 'label'),
+      ),
+      '#required' => TRUE,
+      '#disabled' => !$this->entity->isNew(),
+    );
     return $this->protectPluginIdElement($form);
   }
 
@@ -132,6 +195,7 @@ class PaymentGatewayForm extends CommercePluginEntityFormBase {
     /** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $gateway */
     $gateway = $this->entity;
     $gateway->getPlugin()->validateConfigurationForm($form['configuration'], $form_state);
+    $this->validateVisibility($form, $form_state);
   }
 
   /**
@@ -143,6 +207,17 @@ class PaymentGatewayForm extends CommercePluginEntityFormBase {
     /** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $gateway */
     $gateway = $this->entity;
     $gateway->getPlugin()->submitConfigurationForm($form['configuration'], $form_state);
+    $this->submitVisibility($form, $form_state);
+    $sub_form_state = SubformState::createForSubform($form['settings'], $form, $form_state);
+    // Call the plugin submit handler.
+    $paymentGateway = $gateway->getPlugin();
+    // If this block is context-aware, set the context mapping.
+    if ($paymentGateway instanceof ContextAwarePluginInterface && $paymentGateway->getContextDefinitions()) {
+      $context_mapping = $sub_form_state->getValue('context_mapping', []);
+      $paymentGateway->setContextMapping($context_mapping);
+    }
+
+    $gateway->save();
   }
 
   /**
@@ -154,4 +229,120 @@ class PaymentGatewayForm extends CommercePluginEntityFormBase {
     $form_state->setRedirect('entity.commerce_payment_gateway.collection');
   }
 
+  /**
+   * Helper function for building the visibility UI form.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @return array
+   *   The form array with the visibility UI added in.
+   */
+  protected function buildVisibilityInterface(array $form, FormStateInterface $form_state) {
+    $form['visibility_tabs'] = [
+      '#type' => 'vertical_tabs',
+      '#title' => $this->t('Visibility'),
+      '#parents' => ['visibility_tabs'],
+      '#attached' => [
+        'library' => [
+          'block/drupal.block',
+        ],
+      ],
+    ];
+    // @todo Allow list of conditions to be configured in
+    //   https://www.drupal.org/node/2284687.
+    $visibility = $this->entity->getVisibility();
+    foreach ($this->conditionManager->getDefinitionsForContexts($form_state->getTemporaryValue('gathered_contexts')) as $condition_id => $definition) {
+      // Don't display the current theme condition.
+      if ($condition_id == 'current_theme') {
+        continue;
+      }
+      // Don't display the language condition until we have multiple languages.
+      if ($condition_id == 'language') {
+        continue;
+      }
+      /** @var \Drupal\Core\Condition\ConditionInterface $condition */
+      $condition = $this->conditionManager->createInstance($condition_id, isset($visibility[$condition_id]) ? $visibility[$condition_id] : []);
+      $form_state->set(['conditions', $condition_id], $condition);
+      $condition_form = $condition->buildConfigurationForm([], $form_state);
+      $condition_form['#type'] = 'details';
+      $condition_form['#title'] = $condition->getPluginDefinition()['label'];
+      $condition_form['#group'] = 'visibility_tabs';
+      $form[$condition_id] = $condition_form;
+    }
+    return $form;
+  }
+
+  /**
+   * Helper function to independently validate the visibility UI.
+   *
+   * @param array $form
+   *   A nested array form elements comprising the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  protected function validateVisibility(array $form, FormStateInterface $form_state) {
+    // Validate visibility condition settings.
+    foreach ($form_state->getValue('visibility') as $condition_id => $values) {
+      // All condition plugins use 'negate' as a Boolean in their schema.
+      // However, certain form elements may return it as 0/1. Cast here to
+      // ensure the data is in the expected type.
+      if (array_key_exists('negate', $values)) {
+        $form_state->setValue(['visibility', $condition_id, 'negate'], (bool) $values['negate']);
+      }
+
+      // Allow the condition to validate the form.
+      $condition = $form_state->get(['conditions', $condition_id]);
+      $condition->validateConfigurationForm($form['visibility'][$condition_id], SubformState::createForSubform($form['visibility'][$condition_id], $form, $form_state));
+    }
+  }
+
+  /**
+   * Helper function to independently submit the visibility UI.
+   *
+   * @param array $form
+   *   A nested array form elements comprising the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  protected function submitVisibility(array $form, FormStateInterface $form_state) {
+    foreach ($form_state->getValue('visibility') as $condition_id => $values) {
+      // Allow the condition to submit the form.
+      $condition = $form_state->get(['conditions', $condition_id]);
+      $condition->submitConfigurationForm($form['visibility'][$condition_id], SubformState::createForSubform($form['visibility'][$condition_id], $form, $form_state));
+
+      // Setting conditions' context mappings is the plugins' responsibility.
+      // This code exists for backwards compatibility, because
+      // \Drupal\Core\Condition\ConditionPluginBase::submitConfigurationForm()
+      // did not set its own mappings until Drupal 8.2
+      // @todo Remove the code that sets context mappings in Drupal 9.0.0.
+      if ($condition instanceof ContextAwarePluginInterface) {
+        $context_mapping = isset($values['context_mapping']) ? $values['context_mapping'] : [];
+        $condition->setContextMapping($context_mapping);
+      }
+
+      $condition_configuration = $condition->getConfiguration();
+      // Update the visibility conditions on the gateway.
+      $this->entity->setVisibilityConfig($condition_id, $condition_configuration);
+    }
+  }
+
+  /**
+   * Retrieves the plugin form for a given paymentGateway.
+   *
+   * @param \Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\PaymentGatewayBase $paymentGatewayBase
+   *   The payment gateway plugin.
+   *
+   * @return \Drupal\Core\Plugin\PluginFormInterface
+   *   The plugin form for the paymentGateway.
+   */
+  protected function getPluginForm(PaymentGatewayBase $paymentGatewayBase) {
+    if ($paymentGatewayBase instanceof PaymentGatewayBase) {
+      return $this->pluginFormFactory->createInstance($paymentGatewayBase, 'configure');
+    }
+    return $paymentGatewayBase;
+  }
+
 }
diff --git a/modules/payment/src/PaymentGatewayStorage.php b/modules/payment/src/PaymentGatewayStorage.php
index c8016d0e..04388ae0 100644
--- a/modules/payment/src/PaymentGatewayStorage.php
+++ b/modules/payment/src/PaymentGatewayStorage.php
@@ -4,14 +4,25 @@ namespace Drupal\commerce_payment;
 
 use Drupal\commerce_order\Entity\OrderInterface;
 use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsStoredPaymentMethodsInterface;
+use Drupal\Component\Plugin\ContextAwarePluginInterface;
+use Drupal\Component\Plugin\Exception\ContextException;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Access\AccessResultForbidden;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheableDependencyInterface;
+use Drupal\Core\Condition\ConditionAccessResolverTrait;
 use Drupal\Core\Config\Entity\ConfigEntityStorage;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Session\AccountInterface;
 use Drupal\user\UserInterface;
 
 /**
- * Defines the payment gateway storage.
+ * Defines the payment method storage.
  */
 class PaymentGatewayStorage extends ConfigEntityStorage implements PaymentGatewayStorageInterface {
 
+  use ConditionAccessResolverTrait;
+
   /**
    * {@inheritdoc}
    */
@@ -31,11 +42,108 @@ class PaymentGatewayStorage extends ConfigEntityStorage implements PaymentGatewa
    */
   public function loadMultipleForOrder(OrderInterface $order) {
     $payment_gateways = $this->loadByProperties(['status' => TRUE]);
-    uasort($payment_gateways, [$this->entityType->getClass(), 'sort']);
     // @todo Invoke the attached conditions to determine eligibility.
     // @todo Fire event.
 
+    $current_user = \Drupal::currentUser();
+    $roles = $current_user->getRoles();
+
+    /* @var $payment_gateway \Drupal\commerce_payment\Entity\PaymentGateway */
+    foreach ($payment_gateways as $key => $payment_gateway) {
+      $access = $this->checkAccess($payment_gateway, $current_user);
+      if ($access instanceof AccessResultForbidden) {
+        unset($payment_gateways[$key]);
+      }
+    }
+    // Sort gateways by weight.
+    usort($payment_gateways, function ($a, $b) {
+      return $a->getWeight() > $b->getWeight();
+    });
     return $payment_gateways;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function checkAccess(EntityInterface $entity, AccountInterface $account) {
+    // Don't grant access to disabled blocks.
+    if (!$entity->status()) {
+      return AccessResult::forbidden()->addCacheableDependency($entity);
+    }
+    else {
+      $conditions = [];
+      $missing_context = FALSE;
+      foreach ($entity->getVisibilityConditions() as $condition_id => $condition) {
+        if ($condition instanceof ContextAwarePluginInterface) {
+          try {
+            $contexts = \Drupal::service('context.repository')->getRuntimeContexts(array_values($condition->getContextMapping()));
+            \Drupal::service('context.handler')->applyContextMapping($condition, $contexts);
+          }
+          catch (ContextException $e) {
+            $missing_context = TRUE;
+          }
+        }
+        $conditions[$condition_id] = $condition;
+      }
+
+      if ($missing_context) {
+        // If any context is missing then we might be missing cacheable
+        // metadata, and don't know based on what conditions the block is
+        // accessible or not. For example, blocks that have a node type
+        // condition will have a missing context on any non-node route like the
+        // frontpage.
+        // @todo Avoid setting max-age 0 for some or all cases, for example by
+        //   treating available contexts without value differently in
+        //   https://www.drupal.org/node/2521956.
+        $access = AccessResult::forbidden()->setCacheMaxAge(0);
+      }
+      elseif ($this->resolveConditions($conditions, 'and') !== FALSE) {
+        // Delegate to the plugin.
+        $gatewayPlugin = $entity->getPlugin();
+        try {
+          if ($gatewayPlugin instanceof ContextAwarePluginInterface) {
+            $contexts = \Drupal::service('context.repository')->getRuntimeContexts(array_values($gatewayPlugin->getContextMapping()));
+            \Drupal::service('context.handler')->applyContextMapping($gatewayPlugin, $contexts);
+          }
+          $access = $gatewayPlugin->access($account, TRUE);
+        }
+        catch (ContextException $e) {
+
+          // Setting access to forbidden if any context is missing for the same
+          // reasons as with conditions (described in the comment above).
+          // @todo Avoid setting max-age 0 for some or all cases, for example by
+          //   treating available contexts without value differently in
+          //   https://www.drupal.org/node/2521956.
+          $access = AccessResult::forbidden()->setCacheMaxAge(0);
+        }
+      }
+      else {
+        $access = AccessResult::forbidden();
+      }
+
+      $this->mergeCacheabilityFromConditions($access, $conditions);
+
+      // Ensure that access is evaluated again when the block changes.
+      return $access->addCacheableDependency($entity);
+    }
+  }
+
+  /**
+   * Merges cacheable metadata from conditions onto the access result object.
+   *
+   * @param \Drupal\Core\Access\AccessResult $access
+   *   The access result object.
+   * @param \Drupal\Core\Condition\ConditionInterface[] $conditions
+   *   List of visibility conditions.
+   */
+  protected function mergeCacheabilityFromConditions(AccessResult $access, array $conditions) {
+    foreach ($conditions as $condition) {
+      if ($condition instanceof CacheableDependencyInterface) {
+        $access->addCacheTags($condition->getCacheTags());
+        $access->addCacheContexts($condition->getCacheContexts());
+        $access->setCacheMaxAge(Cache::mergeMaxAges($access->getCacheMaxAge(), $condition->getCacheMaxAge()));
+      }
+    }
+  }
+
 }
diff --git a/modules/payment/src/Plugin/Commerce/PaymentGateway/PaymentGatewayBase.php b/modules/payment/src/Plugin/Commerce/PaymentGateway/PaymentGatewayBase.php
index e732b561..b2739281 100644
--- a/modules/payment/src/Plugin/Commerce/PaymentGateway/PaymentGatewayBase.php
+++ b/modules/payment/src/Plugin/Commerce/PaymentGateway/PaymentGatewayBase.php
@@ -7,11 +7,13 @@ use Drupal\commerce_payment\Entity\PaymentInterface;
 use Drupal\commerce_payment\PaymentMethodTypeManager;
 use Drupal\commerce_payment\PaymentTypeManager;
 use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Plugin\PluginBase;
 use Drupal\Core\Plugin\PluginWithFormsTrait;
+use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -319,4 +321,32 @@ abstract class PaymentGatewayBase extends PluginBase implements PaymentGatewayIn
     return $operations;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access(AccountInterface $account, $return_as_object = FALSE) {
+    $access = $this->paymentGatewayAccess($account);
+    return $return_as_object ? $access : $access->isAllowed();
+  }
+
+  /**
+   * Indicates whether the payment gateway should be shown.
+   *
+   * Blocks with specific access checking should override this method rather
+   * than access(), in order to avoid repeating the handling of the
+   * $return_as_object argument.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The user session for which to check access.
+   *
+   * @return \Drupal\Core\Access\AccessResult
+   *   The access result.
+   *
+   * @see self::access()
+   */
+  protected function paymentGatewayAccess(AccountInterface $account) {
+    // By default, the block is visible.
+    return AccessResult::allowed();
+  }
+
 }
