diff --git a/payment/config/install/payment.payment_method_configuration.collect_on_delivery.yml b/payment/config/install/payment.payment_method_configuration.collect_on_delivery.yml
index 458bdf3..73c00b6 100644
--- a/payment/config/install/payment.payment_method_configuration.collect_on_delivery.yml
+++ b/payment/config/install/payment.payment_method_configuration.collect_on_delivery.yml
@@ -7,5 +7,7 @@ pluginConfiguration:
   execute_status_id: payment_pending
   capture: true
   capture_status_id: payment_success
+  refund: false
+  refund_status_id: payment_refunded
 pluginId: payment_basic
 status: false
diff --git a/payment/config/install/payment.payment_method_configuration.no_payment_required.yml b/payment/config/install/payment.payment_method_configuration.no_payment_required.yml
index 96cb287..e461416 100644
--- a/payment/config/install/payment.payment_method_configuration.no_payment_required.yml
+++ b/payment/config/install/payment.payment_method_configuration.no_payment_required.yml
@@ -4,6 +4,8 @@ ownerId: 1
 pluginConfiguration:
   execute_status_id: payment_success
   capture: false
-  capture_status_id: ''
+  capture_status_id: payment_success
+  refund: true
+  refund_status_id: payment_refunded
 pluginId: payment_basic
 status: false
diff --git a/payment/config/schema/payment.schema.yml b/payment/config/schema/payment.schema.yml
index a8a75bd..de82b52 100644
--- a/payment/config/schema/payment.schema.yml
+++ b/payment/config/schema/payment.schema.yml
@@ -23,9 +23,15 @@ payment.payment_method_configuration.*:
         capture:
           label: Capture
           type: boolean
-        execute_status_id:
+        capture_status_id:
           label: Capture payment status
           type: string
+        refund:
+          label: Refund
+          type: boolean
+        refund_status_id:
+          label: Refund payment status
+          type: string
         # This is for payment method entities that use a plugin that extends
         # \Drupal\payment\Plugin\Payment\MethodConfiguration\PaymentMethodConfigurationBase
         message_text:
diff --git a/payment/css/payment.css b/payment/css/payment.css
index 8f0b50b..352870d 100644
--- a/payment/css/payment.css
+++ b/payment/css/payment.css
@@ -38,14 +38,3 @@
 }
 
 /* @end */
-
-/* @group payment_basic method configuration plugin */
-
-.payment-method-configuration-plugin-payment_basic-capture-status-id {
-    margin-left: 2em; /* LTR */
-}
-[dir="rtl"] .payment-method-configuration-plugin-payment_basic-capture-status-id {
-    margin-right: 2em;
-}
-
-/* @end */
diff --git a/payment/js/payment.js b/payment/js/payment.js
new file mode 100644
index 0000000..c2ca61d
--- /dev/null
+++ b/payment/js/payment.js
@@ -0,0 +1,35 @@
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  Drupal.behaviors.paymentMethodConfigurationBasic = {
+    attach: function (context) {
+      var $context = $(context);
+      var $details = $context.find('details[id$="-execute"]');
+      $details.drupalSetSummary(function (context) {
+        var payment_status_id = $details.find('select[id$="-execute-execute-status-id"]').val();
+        return Drupal.t('Sets payments to %status.', {
+          '%status' : drupalSettings.payment.payment_method_configuration_basic[payment_status_id]
+        });
+      });
+      var i;
+      var operations = ['capture', 'refund'];
+      for (i in operations) {
+        var $details = $context.find('details[id$="-' + operations[i] + '"]');
+        $details.drupalSetSummary(function (details) {
+          var $details = $(details);
+          if ($details.find('input[type=checkbox]').is(":checked")) {
+            var payment_status_id = $details.find('select[id$="-status-id"]').val();
+            return Drupal.t('Sets payments to %status.', {
+              '%status' : drupalSettings.payment.payment_method_configuration_basic[payment_status_id]
+            });
+          }
+          else {
+            return Drupal.t('Disabled.');
+          }
+        });
+      }
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/payment/payment.api.php b/payment/payment.api.php
index 5e7ce8f..3991a87 100644
--- a/payment/payment.api.php
+++ b/payment/payment.api.php
@@ -180,6 +180,21 @@ function hook_payment_pre_execute(PaymentInterface $payment) {}
 function hook_payment_pre_capture(PaymentInterface $payment) {}
 
 /**
+ * Executes before a payment is refunded.
+ *
+ * @param \Drupal\payment\Entity\PaymentInterface $payment
+ *
+ * @see \Drupal\payment\Plugin\Payment\Method\PaymentMethodCapturePaymentInterface::capturePayment()
+ * @see \Drupal\payment\Event\PaymentEvents::PAYMENT_PRE_REFUND
+ * @see \Drupal\payment\Event\PaymentPreRefund
+ *
+ * @deprecated For proper dependency injection and testability, you are advised
+ * to use the \Drupal\payment\Event\PaymentEvents::PAYMENT_PRE_REFUND Symfony
+ * event instead.
+ */
+function hook_payment_pre_refund(PaymentInterface $payment) {}
+
+/**
  * Alters the IDs of payments available for referencing through an instance.
  *
  * @param string $category_id
diff --git a/payment/payment.routing.yml b/payment/payment.routing.yml
index 49d8ef3..eac30d0 100644
--- a/payment/payment.routing.yml
+++ b/payment/payment.routing.yml
@@ -52,6 +52,16 @@ payment.payment.capture:
   options:
     _admin_route: TRUE
 
+payment.payment.refund:
+  path: '/payment/{payment}/refund'
+  defaults:
+    _entity_form: 'payment.refund'
+    _title: Refund payment
+  requirements:
+    _entity_access: 'payment.refund'
+  options:
+    _admin_route: TRUE
+
 payment.payment.delete:
   path: '/payment/{payment}/delete'
   defaults:
diff --git a/payment/src/Entity/Payment.php b/payment/src/Entity/Payment.php
index ca1b63d..ec96854 100644
--- a/payment/src/Entity/Payment.php
+++ b/payment/src/Entity/Payment.php
@@ -32,7 +32,8 @@ use Drupal\user\UserInterface;
  *       "delete" = "Drupal\payment\Entity\Payment\PaymentDeleteForm",
  *       "edit" = "Drupal\payment\Entity\Payment\PaymentEditForm",
  *       "update_status" = "Drupal\payment\Entity\Payment\PaymentStatusForm",
- *       "capture" = "Drupal\payment\Entity\Payment\PaymentCaptureForm"
+ *       "capture" = "Drupal\payment\Entity\Payment\PaymentCaptureForm",
+ *       "refund" = "Drupal\payment\Entity\Payment\PaymentRefundForm"
  *     },
  *     "list_builder" = "Drupal\payment\Entity\Payment\PaymentListBuilder",
  *     "view_builder" = "Drupal\payment\Entity\Payment\PaymentViewBuilder",
@@ -52,7 +53,8 @@ use Drupal\user\UserInterface;
  *     "edit-form" = "payment.payment.edit",
  *     "delete-form" = "payment.payment.delete",
  *     "update-status-form" = "payment.payment.update_status",
- *     "capture-form" = "payment.payment.capture"
+ *     "capture-form" = "payment.payment.capture",
+ *     "refund-form" = "payment.payment.refund"
  *   }
  * )
  */
diff --git a/payment/src/Entity/Payment/PaymentAccess.php b/payment/src/Entity/Payment/PaymentAccess.php
index 29e31d9..5e3b48e 100644
--- a/payment/src/Entity/Payment/PaymentAccess.php
+++ b/payment/src/Entity/Payment/PaymentAccess.php
@@ -12,6 +12,7 @@ use Drupal\Core\Entity\EntityAccessController;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\payment\Entity\PaymentInterface;
 use Drupal\payment\Plugin\Payment\Method\PaymentMethodCapturePaymentInterface;
+use Drupal\payment\Plugin\Payment\Method\PaymentMethodRefundPaymentInterface;
 use Drupal\payment\Plugin\Payment\Method\PaymentMethodUpdatePaymentStatusInterface;
 
 /**
@@ -34,8 +35,14 @@ class PaymentAccess extends EntityAccessController {
     elseif ($operation == 'capture') {
       $payment_method = $payment->getPaymentMethod();
       return $payment_method instanceof PaymentMethodCapturePaymentInterface
-        && $payment_method->capturePaymentAccess($account)
-        && $this->checkAccessPermission($payment, $operation, $account);
+      && $payment_method->capturePaymentAccess($account)
+      && $this->checkAccessPermission($payment, $operation, $account);
+    }
+    elseif ($operation == 'refund') {
+      $payment_method = $payment->getPaymentMethod();
+      return $payment_method instanceof PaymentMethodRefundPaymentInterface
+      && $payment_method->refundPaymentAccess($account)
+      && $this->checkAccessPermission($payment, $operation, $account);
     }
     return $this->checkAccessPermission($payment, $operation, $account);
   }
diff --git a/payment/src/Entity/Payment/PaymentListBuilder.php b/payment/src/Entity/Payment/PaymentListBuilder.php
index 2c198b4..70c9445 100644
--- a/payment/src/Entity/Payment/PaymentListBuilder.php
+++ b/payment/src/Entity/Payment/PaymentListBuilder.php
@@ -176,6 +176,18 @@ class PaymentListBuilder extends EntityListBuilder {
           ),
         ) + $entity->urlInfo('capture-form')->toArray();
     }
+    if ($entity->access('refund')) {
+      $operations['refund'] = array(
+          'title' => $this->t('Refund'),
+          'attributes' => array(
+            'class' => array('use-ajax'),
+            'data-accepts' => 'application/vnd.drupal-modal',
+          ),
+          'query' => array(
+            'destination' => $this->requestStack->getCurrentRequest()->attributes->get('_system_path'),
+          ),
+        ) + $entity->urlInfo('refund-form')->toArray();
+    }
 
     return $operations;
   }
diff --git a/payment/src/Entity/Payment/PaymentRefundForm.php b/payment/src/Entity/Payment/PaymentRefundForm.php
new file mode 100644
index 0000000..579d665
--- /dev/null
+++ b/payment/src/Entity/Payment/PaymentRefundForm.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\payment\Entity\Payment\PaymentRefundForm.
+ */
+
+namespace Drupal\payment\Entity\Payment;
+
+use Drupal\Core\Entity\ContentEntityConfirmFormBase;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides the payment refund form.
+ */
+class PaymentRefundForm extends ContentEntityConfirmFormBase {
+
+  /**
+   * Constructs a new instance.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *    The string translator.
+   */
+  function __construct(EntityManagerInterface $entity_manager, TranslationInterface $string_translation) {
+    parent::__construct($entity_manager);
+    $this->stringTranslation = $string_translation;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('entity.manager'), $container->get('string_translation'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuestion() {
+    return $this->t('Do you really want to refund payment #!payment_id?', array(
+      '!payment_id' => $this->getEntity()->id(),
+    ));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfirmText() {
+    return $this->t('Refund');
+  }
+
+  /**
+   * Returns the route to go to if the user cancels the action.
+   *
+   * @return \Drupal\Core\Url
+   *   A URL object.
+   */
+  public function getCancelRoute() {
+    return $this->getEntity()->urlInfo();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submit(array $form, array &$form_state) {
+    /** @var \Drupal\payment\Entity\PaymentInterface $payment */
+    $payment = $this->getEntity();
+    /** @var \Drupal\payment\Plugin\Payment\Method\PaymentMethodRefundPaymentInterface $payment_method */
+    $payment_method = $payment->getPaymentMethod();
+    $payment_method->refundPayment();
+
+    $form_state['redirect_route'] = $this->getEntity()->urlInfo();
+  }
+
+}
diff --git a/payment/src/Event/PaymentEvents.php b/payment/src/Event/PaymentEvents.php
index e210f5d..63c2e73 100644
--- a/payment/src/Event/PaymentEvents.php
+++ b/payment/src/Event/PaymentEvents.php
@@ -38,6 +38,14 @@ final class PaymentEvents {
   const PAYMENT_PRE_CAPTURE = 'drupal.payment.payment_pre_capture';
 
   /**
+   * The name of the event that is fired before a payment is refunded.
+   *
+   * @see hook_payment_pre_refunded()
+   * @see \Drupal\payment\Event\PaymentPreRefund
+   */
+  const PAYMENT_PRE_REFUND = 'drupal.payment.payment_pre_refund';
+
+  /**
    * The name of the event that is fired after a new payment status is set.
    *
    * @see hook_payment_status_set()
diff --git a/payment/src/Event/PaymentPreRefund.php b/payment/src/Event/PaymentPreRefund.php
new file mode 100644
index 0000000..af07c9c
--- /dev/null
+++ b/payment/src/Event/PaymentPreRefund.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\payment\Event\PaymentPreRefund.
+ */
+
+namespace Drupal\payment\Event;
+
+use Drupal\payment\Entity\PaymentInterface;
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Provides an event that is dispatched before a payment is refunded.
+ *
+ * @see \Drupal\payment\Event\PaymentEvents::PAYMENT_PRE_REFUND
+ */
+class PaymentPreRefund extends Event {
+
+  /**
+   * The payment.
+   *
+   * @var \Drupal\payment\Entity\PaymentInterface
+   */
+  protected $payment;
+
+  /**-
+   * Constructs a new class instance.
+   *
+   * @param \Drupal\payment\Entity\PaymentInterface $payment
+   *   The payment that will be refunded.
+   *
+   * @param \Drupal\Core\Session\AccountInterface
+   */
+  public function __construct(PaymentInterface $payment) {
+    $this->payment = $payment;
+  }
+
+  /**
+   * Gets the payment that will be refunded.
+   *
+   * @return \Drupal\payment\Entity\PaymentInterface
+   */
+  public function getPayment() {
+    return $this->payment;
+  }
+
+}
diff --git a/payment/src/Hook/HookInfo.php b/payment/src/Hook/HookInfo.php
index 9e4dcf7..57fb4d5 100644
--- a/payment/src/Hook/HookInfo.php
+++ b/payment/src/Hook/HookInfo.php
@@ -39,6 +39,9 @@ class HookInfo {
     $hooks['payment_pre_execute'] = array(
       'group' => 'payment',
     );
+    $hooks['payment_pre_refund'] = array(
+      'group' => 'payment',
+    );
     $hooks['payment_queue_payment_ids_alter'] = array(
       'group' => 'payment',
     );
diff --git a/payment/src/Hook/Permission.php b/payment/src/Hook/Permission.php
index 571182c..04c79af 100644
--- a/payment/src/Hook/Permission.php
+++ b/payment/src/Hook/Permission.php
@@ -70,6 +70,9 @@ class Permission {
       'payment.payment.capture.any' => array(
         'title' => $this->t('Capture payments'),
       ),
+      'payment.payment.refund.any' => array(
+        'title' => $this->t('Refund payments'),
+      ),
       'payment.payment_method_configuration.update.any' => array(
         'title' => $this->t('Update any payment method configuration'),
         'restrict access' => TRUE,
diff --git a/payment/src/Plugin/Payment/Method/Basic.php b/payment/src/Plugin/Payment/Method/Basic.php
index efd97d4..96d9f60 100644
--- a/payment/src/Plugin/Payment/Method/Basic.php
+++ b/payment/src/Plugin/Payment/Method/Basic.php
@@ -10,6 +10,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Utility\Token;
+use Drupal\payment\Entity\PaymentInterface;
 use Drupal\payment\Plugin\Payment\Status\PaymentStatusManagerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -25,6 +26,9 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  * - capture: (boolean) Whether or not payment capture is supported.
  * - capture_status_id: (string) The ID of the payment status plugin to set at
  *   payment capture.
+ * - refund: (boolean) Whether or not payment refunds are supported.
+ * - refund_status_id: (string) The ID of the payment status plugin to set at
+ *   payment refund.
  *
  * @PaymentMethod(
  *   deriver = "Drupal\payment\Plugin\Payment\Method\BasicDeriver",
@@ -32,7 +36,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  *   operations_provider = "\Drupal\payment\Plugin\Payment\Method\BasicOperationsProvider",
  * )
  */
-class Basic extends PaymentMethodBase implements ContainerFactoryPluginInterface, PaymentMethodCapturePaymentInterface {
+class Basic extends PaymentMethodBase implements ContainerFactoryPluginInterface, PaymentMethodCapturePaymentInterface, PaymentMethodRefundPaymentInterface, PaymentMethodUpdatePaymentStatusInterface {
 
   /**
    * The payment status manager.
@@ -119,6 +123,26 @@ class Basic extends PaymentMethodBase implements ContainerFactoryPluginInterface
   }
 
   /**
+   * Gets the status to set on payment refund.
+   *
+   * @return string
+   *   The plugin ID of the payment status to set.
+   */
+  public function getRefundStatusId() {
+    return $this->pluginDefinition['refund_status_id'];
+  }
+
+  /**
+   * Gets whether or not capture is supported.
+   *
+   * @param bool
+   *   Whether or not to support capture.
+   */
+  public function getRefund() {
+    return $this->pluginDefinition['refund'];
+  }
+
+  /**
    * {@inheritdoc}
    */
   protected function doExecutePayment() {
@@ -139,7 +163,36 @@ class Basic extends PaymentMethodBase implements ContainerFactoryPluginInterface
    * {@inheritdoc}
    */
   public function doCapturePaymentAccess(AccountInterface $account) {
-    return $this->getCapture() && $this->getPayment()->getStatus()->getPluginId() != $this->getCaptureStatusId();
+    return $this->getCapture() && $this->getPayment()->getStatus()->getPluginId() == $this->getExecuteStatusId();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function doRefundPayment() {
+    $this->getPayment()->setStatus($this->paymentStatusManager->createInstance($this->getRefundStatusId()));
+    $this->getPayment()->save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function doRefundPaymentAccess(AccountInterface $account) {
+    return $this->getRefund() && $this->getPayment()->getStatus()->getPluginId() == $this->getCaptureStatusId();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function updatePaymentStatusAccess(AccountInterface $account) {
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSettablePaymentStatuses(AccountInterface $account, PaymentInterface $payment) {
+    return array();
   }
 
 }
diff --git a/payment/src/Plugin/Payment/Method/BasicDeriver.php b/payment/src/Plugin/Payment/Method/BasicDeriver.php
index 805c719..85ff2ab 100644
--- a/payment/src/Plugin/Payment/Method/BasicDeriver.php
+++ b/payment/src/Plugin/Payment/Method/BasicDeriver.php
@@ -67,9 +67,11 @@ class BasicDeriver extends DeriverBase implements ContainerDeriverInterface {
           'label' => $configuration_plugin->getBrandLabel() ? $configuration_plugin->getBrandLabel() : $payment_method->label(),
           'message_text' => $configuration_plugin->getMessageText(),
           'message_text_format' => $configuration_plugin->getMessageTextFormat(),
-            'execute_status_id' => $configuration_plugin->getExecuteStatusId(),
-            'capture' => $configuration_plugin->getCapture(),
-            'capture_status_id' => $configuration_plugin->getCaptureStatusId(),
+          'execute_status_id' => $configuration_plugin->getExecuteStatusId(),
+          'capture' => $configuration_plugin->getCapture(),
+          'capture_status_id' => $configuration_plugin->getCaptureStatusId(),
+          'refund' => $configuration_plugin->getRefund(),
+          'refund_status_id' => $configuration_plugin->getRefundStatusId(),
         ) + $base_plugin_definition;
       }
     }
diff --git a/payment/src/Plugin/Payment/Method/PaymentMethodBase.php b/payment/src/Plugin/Payment/Method/PaymentMethodBase.php
index 5eb3653..6a4f14b 100644
--- a/payment/src/Plugin/Payment/Method/PaymentMethodBase.php
+++ b/payment/src/Plugin/Payment/Method/PaymentMethodBase.php
@@ -17,6 +17,7 @@ use Drupal\payment\Event\PaymentEvents;
 use Drupal\payment\Event\PaymentExecuteAccess;
 use Drupal\payment\Event\PaymentPreCapture;
 use Drupal\payment\Event\PaymentPreExecute;
+use Drupal\payment\Event\PaymentPreRefund;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
@@ -29,7 +30,7 @@ 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 AccessInterface, ContainerFactoryPluginInterface, PaymentMethodInterface {
+abstract class PaymentMethodBase extends PluginBase implements AccessInterface, ContainerFactoryPluginInterface, PaymentMethodInterface, PaymentMethodCapturePaymentInterface, PaymentMethodRefundPaymentInterface {
 
   /**
    * The event dispatcher.
@@ -229,14 +230,14 @@ abstract class PaymentMethodBase extends PluginBase implements AccessInterface,
   abstract protected function doExecutePayment();
 
   /**
-   * Implements \Drupal\payment\Plugin\Payment\Method\PaymentMethodCapturePaymentInterface::capturePaymentAccess().
+   * {@inheritdoc}
    */
   public function capturePaymentAccess(AccountInterface $account) {
     if (!$this->getPayment()) {
       throw new \LogicException('Trying to check access for a non-existing payment. A payment must be set trough self::setPayment() first.');
     }
 
-    return $this->getPayment()->access('capture') && $this->doCapturePaymentAccess($account);
+    return $this->doCapturePaymentAccess($account);
   }
 
   /**
@@ -249,7 +250,7 @@ abstract class PaymentMethodBase extends PluginBase implements AccessInterface,
   abstract protected function doCapturePaymentAccess(AccountInterface $account);
 
   /**
-   * Implements \Drupal\payment\Plugin\Payment\Method\PaymentMethodCapturePaymentInterface::capturePayment().
+   * {@inheritdoc}
    */
   public function capturePayment() {
     if (!$this->getPayment()) {
@@ -268,6 +269,45 @@ abstract class PaymentMethodBase extends PluginBase implements AccessInterface,
   abstract protected function doCapturePayment();
 
   /**
+   * {@inheritdoc}
+   */
+  public function refundPaymentAccess(AccountInterface $account) {
+    if (!$this->getPayment()) {
+      throw new \LogicException('Trying to check access for a non-existing payment. A payment must be set trough self::setPayment() first.');
+    }
+
+    return $this->doRefundPaymentAccess($account);
+  }
+
+  /**
+   * Performs a payment method-specific access check for payment refunds.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *
+   * @return bool
+   */
+  abstract protected function doRefundPaymentAccess(AccountInterface $account);
+
+  /**
+   * {@inheritdoc}
+   */
+  public function refundPayment() {
+    if (!$this->getPayment()) {
+      throw new \LogicException('Trying to refund a non-existing payment. A payment must be set trough self::setPayment() first.');
+    }
+    $event = new PaymentPreRefund($this->getPayment());
+    $this->eventDispatcher->dispatch(PaymentEvents::PAYMENT_PRE_REFUND, $event);
+    $this->moduleHandler->invokeAll('payment_pre_refund', array($this->getPayment()));
+    // @todo invoke Rules event.
+    $this->doRefundPayment();
+  }
+
+  /**
+   * Performs the actual payment refund.
+   */
+  abstract protected function doRefundPayment();
+
+  /**
    * Checks a payment's currency against this plugin.
    *
    * @param \Drupal\Core\Session\AccountInterface $account
diff --git a/payment/src/Plugin/Payment/Method/PaymentMethodCapturePaymentInterface.php b/payment/src/Plugin/Payment/Method/PaymentMethodCapturePaymentInterface.php
index 281d367..dea47e1 100644
--- a/payment/src/Plugin/Payment/Method/PaymentMethodCapturePaymentInterface.php
+++ b/payment/src/Plugin/Payment/Method/PaymentMethodCapturePaymentInterface.php
@@ -11,11 +11,8 @@ use Drupal\Core\Session\AccountInterface;
 /**
  * Defines a payment method that can capture authorized payments.
  *
- * Users can capture payments if they have the "payment.payment.capture.any"
- * permissions. By implementing this interface, payment methods can exercise
- * additional control on top of this permission.
- *
- * @see \Drupal\payment\Plugin\Payment\Method\PaymentMethodCapturePaymentTrait
+ * Users can refund payments if they have the "payment.payment.capture.any"
+ * permissions and self::capturePaymentAccess() returns TRUE.
  */
 interface PaymentMethodCapturePaymentInterface {
 
diff --git a/payment/src/Plugin/Payment/Method/PaymentMethodRefundPaymentInterface.php b/payment/src/Plugin/Payment/Method/PaymentMethodRefundPaymentInterface.php
new file mode 100644
index 0000000..fcae98d
--- /dev/null
+++ b/payment/src/Plugin/Payment/Method/PaymentMethodRefundPaymentInterface.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\Payment\Method\PaymentMethodRefundPaymentInterface.
+ */
+
+namespace Drupal\payment\Plugin\Payment\Method;
+
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Defines a payment method that can Refund authorized payments.
+ *
+ * Users can refund payments if they have the "payment.payment.refund.any"
+ * permissions and self::refundPaymentAccess() returns TRUE.
+ */
+interface PaymentMethodRefundPaymentInterface {
+
+  /**
+   * Checks if the payment can be refunded.
+   *
+   * The payment method must have been configured and the payment must have been
+   * captured prior to refunding it.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *
+   * @return bool
+   *
+   * @see self::refundPayment
+   */
+  public function refundPaymentAccess(AccountInterface $account);
+
+  /**
+   * Refunds the payment.
+   *
+   * Implementations must invoke hook_payment_pre_refund() and dispatch the
+   * \Drupal\payment\Event\PaymentEvents::PAYMENT_PRE_REFUND Symfony event
+   * before refunding the payment.
+   *
+   * @see self::refundPaymentAccess
+   */
+  public function refundPayment();
+
+}
diff --git a/payment/src/Plugin/Payment/MethodConfiguration/Basic.php b/payment/src/Plugin/Payment/MethodConfiguration/Basic.php
index 38f568a..4869d87 100644
--- a/payment/src/Plugin/Payment/MethodConfiguration/Basic.php
+++ b/payment/src/Plugin/Payment/MethodConfiguration/Basic.php
@@ -69,6 +69,8 @@ class Basic extends PaymentMethodConfigurationBase implements ContainerFactoryPl
       'execute_status_id' => 'payment_pending',
       'capture' => FALSE,
       'capture_status_id' => 'payment_success',
+      'refund' => FALSE,
+      'refund_status_id' => 'payment_refunded',
     );
   }
 
@@ -145,17 +147,106 @@ class Basic extends PaymentMethodConfigurationBase implements ContainerFactoryPl
   }
 
   /**
+   * Sets the status to set on payment refund.
+   *
+   * @param string $status
+   *   The plugin ID of the payment status to set.
+   *
+   * @return $this
+   */
+  public function setRefundStatusId($status) {
+    $this->configuration['refund_status_id'] = $status;
+
+    return $this;
+  }
+
+  /**
+   * Gets the status to set on payment refund.
+   *
+   * @return string
+   *   The plugin ID of the payment status to set.
+   */
+  public function getRefundStatusId() {
+    return $this->configuration['refund_status_id'];
+  }
+
+  /**
+   * Sets whether or not refunds are supported.
+   *
+   * @param bool $refund
+   *   Whether or not to support refunds.
+   *
+   * @return $this
+   */
+  public function setRefund($refund) {
+    $this->configuration['refund'] = $refund;
+
+    return $this;
+  }
+
+  /**
+   * Gets whether or not refunds are supported.
+   *
+   * @param bool
+   *   Whether or not to support refunds.
+   */
+  public function getRefund() {
+    return $this->configuration['refund'];
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function buildConfigurationForm(array $form, array &$form_state) {
-    $elements = parent::buildConfigurationForm($form, $form_state);
-    $elements['brand_label'] = array(
+    $form = parent::buildConfigurationForm($form, $form_state);
+    $form['plugin_form'] = array(
+      '#process' => array(array($this, 'processBuildConfigurationForm')),
+      '#type' => 'container',
+    );
+
+    return $form;
+  }
+
+  /**
+   * Implements a form API #process callback.
+   */
+  public function processBuildConfigurationForm(array &$element, array &$form_state, array &$form) {
+    $element['brand_label'] = array(
       '#default_value' => $this->getBrandLabel(),
       '#description' => $this->t('The label that payers will see when choosing a payment method. Defaults to the payment method label.'),
       '#title' => $this->t('Brand label'),
       '#type' => 'textfield',
     );
-    $elements['execute_status_id'] = array(
+    $labels = array();
+    foreach ($this->paymentStatusManager->getDefinitions() as $definition) {
+      $labels[$definition['id']] = (string) $definition['label'];
+    }
+    $workflow_group = implode('][', array_merge($element['#parents'], array('workflow')));
+    $workflow_id = drupal_html_id('workflow');
+    $element['workflow'] = array(
+      '#attached' => array(
+        'js' => array(
+          drupal_get_path('module', 'payment') . '/js/payment.js',
+          array(
+            'data' => array(
+              'payment' => array(
+                'payment_method_configuration_basic' => $labels,
+              ),
+            ),
+            'type' => 'setting',
+          ),
+        ),
+      ),
+      '#id' => $workflow_id,
+      '#type' => 'vertical_tabs',
+    );
+    $element['execute'] = array(
+      '#group' => $workflow_group,
+      '#open' => TRUE,
+      '#type' => 'details',
+      '#title' => $this->t('Execution'),
+    );
+    $element['execute']['execute_status_id'] = array(
       '#default_value' => !$this->getExecuteStatusId() ?: $this->getExecuteStatusId(),
       '#description' => $this->t('The status to set payments to after being executed by this payment method.'),
       '#empty_value' => '',
@@ -164,27 +255,22 @@ class Basic extends PaymentMethodConfigurationBase implements ContainerFactoryPl
       '#title' => $this->t('Payment execution status'),
       '#type' => 'select',
     );
+    $element['capture'] = array(
+      '#group' => $workflow_group,
+      '#open' => TRUE,
+      '#type' => 'details',
+      '#title' => $this->t('Capture'),
+    );
     $capture_id = drupal_html_id('capture');
-    $elements['capture'] = array(
+    $element['capture']['capture'] = array(
       '#id' => $capture_id,
       '#type' => 'checkbox',
       '#title' => $this->t('Add an additional capture step after payments have been executed.'),
       '#default_value' => $this->getCapture(),
     );
-    $elements['capture_status_id_wrapper'] = array(
-      '#attributes' => array(
-        'class' => array('payment-method-configuration-plugin-payment_basic-capture-status-id'),
-      ),
-      '#type' => 'container',
-    );
-    $elements['capture_status_id_wrapper']['capture_status_id'] = array(
-      '#attached' => array(
-        'css' => array(
-          __DIR__ . '/../../../../css/payment.css',
-        ),
-      ),
+    $element['capture']['capture_status_id'] = array(
       '#description' => $this->t('The status to set payments to after being captured by this payment method.'),
-      '#default_value' => $this->getCaptureStatusId() ? $this->getCaptureStatusId() : 'payment_success',
+      '#default_value' => $this->getCaptureStatusId(),
       '#options' => $this->paymentStatusManager->options(),
       '#required' => TRUE,
       '#states' => array(
@@ -197,8 +283,36 @@ class Basic extends PaymentMethodConfigurationBase implements ContainerFactoryPl
       '#title' => $this->t('Payment capture status'),
       '#type' => 'select',
     );
+    $refund_id = drupal_html_id('refund');
+    $element['refund'] = array(
+      '#group' => $workflow_group,
+      '#open' => TRUE,
+      '#type' => 'details',
+      '#title' => $this->t('Refund'),
+    );
+    $element['refund']['refund'] = array(
+      '#id' => $refund_id,
+      '#type' => 'checkbox',
+      '#title' => $this->t('Add an additional refund step after payments have been executed.'),
+      '#default_value' => $this->getRefund(),
+    );
+    $element['refund']['refund_status_id'] = array(
+      '#description' => $this->t('The status to set payments to after being refunded by this payment method.'),
+      '#default_value' => $this->getRefundStatusId(),
+      '#options' => $this->paymentStatusManager->options(),
+      '#required' => TRUE,
+      '#states' => array(
+        'visible' => array(
+          '#' . $refund_id => array(
+            'checked' => TRUE,
+          ),
+        ),
+      ),
+      '#title' => $this->t('Payment refund status'),
+      '#type' => 'select',
+    );
 
-    return $elements;
+    return $element;
   }
 
   /**
@@ -206,12 +320,14 @@ class Basic extends PaymentMethodConfigurationBase implements ContainerFactoryPl
    */
   public function submitConfigurationForm(array &$form, array &$form_state) {
     parent::submitConfigurationForm($form, $form_state);
-    $parents = $form['brand_label']['#parents'];
+    $parents = $form['plugin_form']['brand_label']['#parents'];
     array_pop($parents);
     $values = NestedArray::getValue($form_state['values'], $parents);
-    $this->setExecuteStatusId($values['execute_status_id']);
-    $this->setCapture($values['capture']);
-    $this->setCaptureStatusId($values['capture_status_id_wrapper']['capture_status_id']);
+    $this->setExecuteStatusId($values['execute']['execute_status_id']);
+    $this->setCapture($values['capture']['capture']);
+    $this->setCaptureStatusId($values['capture']['capture_status_id']);
+    $this->setRefund($values['refund']['refund']);
+    $this->setRefundStatusId($values['refund']['refund_status_id']);
     $this->setBrandLabel($values['brand_label']);
   }
 
@@ -236,4 +352,5 @@ class Basic extends PaymentMethodConfigurationBase implements ContainerFactoryPl
 
     return $this;
   }
+
 }
diff --git a/payment/src/Plugin/Payment/Status/Refunded.php b/payment/src/Plugin/Payment/Status/Refunded.php
new file mode 100644
index 0000000..16ce1f6
--- /dev/null
+++ b/payment/src/Plugin/Payment/Status/Refunded.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * Contains \Drupal\payment\Plugin\Payment\Status\Refunded.
+ */
+
+namespace Drupal\payment\Plugin\Payment\Status;
+
+/**
+ * A refunded payment.
+ *
+ * @PaymentStatus(
+ *   id = "payment_refunded",
+ *   label = @Translation("Refunded"),
+ *   parent_id = "payment_no_money_transferred"
+ * )
+ */
+class Refunded extends PaymentStatusBase {
+}
diff --git a/payment/src/Tests/Controller/PaymentMethodWebTest.php b/payment/src/Tests/Controller/PaymentMethodWebTest.php
index 13cde97..123283f 100644
--- a/payment/src/Tests/Controller/PaymentMethodWebTest.php
+++ b/payment/src/Tests/Controller/PaymentMethodWebTest.php
@@ -183,16 +183,18 @@ class PaymentMethodWebTest extends WebTestBase {
     $brand_label = $this->randomString();
     $execute_status_id = 'payment_failed';
     $capture_status_id = 'payment_success';
+    $refund_status_id = 'payment_cancelled';
     $id = strtolower($this->randomName());
     $this->drupalPostForm(NULL, array(
       'label' => $label,
       'id' => $id,
       'owner' => $user->label(),
-      'plugin_form[brand_label]' => $brand_label,
-      'plugin_form[execute_status_id]' => $execute_status_id,
-      'plugin_form[capture]' => TRUE,
-      'plugin_form[capture_status_id_wrapper][capture_status_id]' => $capture_status_id,
-      'plugin_form[capture_status_id_wrapper][capture_status_id]' => $capture_status_id,
+      'plugin_form[plugin_form][brand_label]' => $brand_label,
+      'plugin_form[plugin_form][execute][execute_status_id]' => $execute_status_id,
+      'plugin_form[plugin_form][capture][capture]' => TRUE,
+      'plugin_form[plugin_form][capture][capture_status_id]' => $capture_status_id,
+      'plugin_form[plugin_form][refund][refund]' => TRUE,
+      'plugin_form[plugin_form][refund][refund_status_id]' => $refund_status_id,
     ), t('Save'));
     /** @var \Drupal\payment\Entity\PaymentMethodConfigurationInterface $payment_method */
     $payment_method = entity_load('payment_method_configuration', $id);
diff --git a/payment/tests/src/Entity/Payment/PaymentAccessUnitTest.php b/payment/tests/src/Entity/Payment/PaymentAccessUnitTest.php
index 765123d..f001228 100644
--- a/payment/tests/src/Entity/Payment/PaymentAccessUnitTest.php
+++ b/payment/tests/src/Entity/Payment/PaymentAccessUnitTest.php
@@ -10,6 +10,7 @@ namespace Drupal\payment\Tests\Entity\Payment;
 use Drupal\payment\Entity\Payment\PaymentAccess;
 use Drupal\payment\Plugin\Payment\Method\PaymentMethodCapturePaymentInterface;
 use Drupal\payment\Plugin\Payment\Method\PaymentMethodInterface;
+use Drupal\payment\Plugin\Payment\Method\PaymentMethodRefundPaymentInterface;
 use Drupal\payment\Plugin\Payment\Method\PaymentMethodUpdatePaymentStatusInterface;
 use Drupal\Tests\UnitTestCase;
 
@@ -96,6 +97,56 @@ class PaymentAccessUnitTest extends UnitTestCase {
 
   /**
    * @covers ::checkAccess
+   *
+   * @dataProvider providerTestCheckAccessRefund
+   */
+  public function testCheckAccessRefund($expected, $payment_method_interface, $payment_method_refund_access, $has_permissions) {
+    $operation = 'refund';
+    $language_code = $this->randomName();
+
+    $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
+    $map = array(
+      array('payment.payment.refund.any', $has_permissions),
+      array('payment.payment.refund.own', $has_permissions),
+    );
+    $account->expects($this->any())
+      ->method('hasPermission')
+      ->will($this->returnValueMap($map));
+
+    $payment_method = $this->getMock($payment_method_interface);
+    $payment_method->expects($this->any())
+      ->method('refundPaymentAccess')
+      ->with($account)
+      ->will($this->returnValue($payment_method_refund_access));
+
+    $payment = $this->getMockBuilder('\Drupal\payment\Entity\Payment')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $payment->expects($this->atLeastOnce())
+      ->method('getPaymentMethod')
+      ->will($this->returnValue($payment_method));
+
+    $method = new \ReflectionMethod($this->accessController, 'checkAccess');
+    $method->setAccessible(TRUE);
+
+    $this->assertSame($expected, $method->invokeArgs($this->accessController, array($payment, $operation, $language_code, $account)));
+  }
+
+  /**
+   * Provides data to self::testCheckAccessRefund().
+   */
+  public function providerTestCheckAccessRefund() {
+    return array(
+      array(TRUE, '\Drupal\payment\Tests\Entity\Payment\PaymentAccessUnitTestDummyPaymentMethodRefundPaymentInterface', TRUE, TRUE),
+      array(FALSE, '\Drupal\payment\Tests\Entity\Payment\PaymentAccessUnitTestDummyPaymentMethodRefundPaymentInterface', FALSE, TRUE),
+      array(FALSE, '\Drupal\payment\Tests\Entity\Payment\PaymentAccessUnitTestDummyPaymentMethodRefundPaymentInterface', TRUE, FALSE),
+      array(FALSE, '\Drupal\payment\Tests\Entity\Payment\PaymentAccessUnitTestDummyPaymentMethodRefundPaymentInterface', FALSE, FALSE),
+      array(FALSE, '\Drupal\payment\Plugin\Payment\Method\PaymentMethodInterface', TRUE, TRUE),
+    );
+  }
+
+  /**
+   * @covers ::checkAccess
    */
   public function testCheckAccessUpdateStatusWithAccess() {
     $operation = 'update_status';
@@ -276,3 +327,9 @@ interface PaymentAccessUnitTestDummyPaymentMethodUpdateStatusInterface extends P
  */
 interface PaymentAccessUnitTestDummyPaymentMethodCapturePaymentInterface extends PaymentMethodCapturePaymentInterface, PaymentMethodInterface {
 }
+
+/**
+ * Extends two interfaces, because we can only mock one.
+ */
+interface PaymentAccessUnitTestDummyPaymentMethodRefundPaymentInterface extends PaymentMethodRefundPaymentInterface, PaymentMethodInterface {
+}
diff --git a/payment/tests/src/Entity/Payment/PaymentRefundFormUnitTest.php b/payment/tests/src/Entity/Payment/PaymentRefundFormUnitTest.php
new file mode 100644
index 0000000..149e811
--- /dev/null
+++ b/payment/tests/src/Entity/Payment/PaymentRefundFormUnitTest.php
@@ -0,0 +1,150 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\payment\Tests\Entity\Payment\PaymentRefundFormUnitTest.
+ */
+
+namespace Drupal\payment\Tests\Entity\Payment;
+
+use Drupal\Core\Url;
+use Drupal\payment\Entity\Payment\PaymentRefundForm;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+  /**
+ * @coversDefaultClass \Drupal\payment\Entity\Payment\PaymentRefundForm
+ */
+class PaymentRefundFormUnitTest extends UnitTestCase {
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\payment\Entity\Payment\PaymentRefundForm
+   */
+  protected $form;
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entityManager;
+
+  /**
+   * The payment.
+   *
+   * @var \Drupal\payment\Entity\Payment|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $payment;
+
+  /**
+   * The string translator.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $stringTranslation;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'description' => '',
+      'group' => 'Payment',
+      'name' => '\Drupal\payment\Entity\Payment\PaymentRefundForm unit test',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @covers ::__construct
+   */
+  public function setUp() {
+    $this->entityManager = $this->getMock('\Drupal\Core\Entity\EntityManagerInterface');
+
+    $this->stringTranslation = $this->getMock('\Drupal\Core\StringTranslation\TranslationInterface');
+    $this->stringTranslation->expects($this->any())
+      ->method('translate')
+      ->will($this->returnArgument(0));
+
+    $this->payment = $this->getMockBuilder('\Drupal\payment\Entity\Payment')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->form = new PaymentRefundForm($this->entityManager, $this->stringTranslation);
+    $this->form->setEntity($this->payment);
+  }
+
+  /**
+   * @covers ::create
+   */
+  function testCreate() {
+    $container = $this->getMock('\Symfony\Component\DependencyInjection\ContainerInterface');
+    $map = array(
+      array('entity.manager', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $this->entityManager),
+      array('string_translation', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $this->stringTranslation),
+    );
+    $container->expects($this->any())
+      ->method('get')
+      ->will($this->returnValueMap($map));
+
+    $form = PaymentRefundForm::create($container);
+    $this->assertInstanceOf('\Drupal\payment\Entity\Payment\PaymentRefundForm', $form);
+  }
+
+  /**
+   * @covers ::getConfirmText
+   */
+  function testGetConfirmText() {
+    $this->assertInternalType('string', $this->form->getConfirmText());
+  }
+
+  /**
+   * @covers ::getQuestion
+   */
+  function testGetQuestion() {
+    $this->assertInternalType('string', $this->form->getQuestion());
+  }
+
+  /**
+   * @covers ::getCancelRoute
+   */
+  function testGetCancelRoute() {
+    $url = new Url($this->randomName());
+
+    $this->payment->expects($this->atLeastOnce())
+      ->method('urlInfo')
+      ->with('canonical')
+      ->will($this->returnValue($url));
+
+    $this->assertSame($url, $this->form->getCancelRoute());
+  }
+
+  /**
+   * @covers ::submit
+   */
+  function testSubmit() {
+    $payment_method = $this->getMock('\Drupal\payment\Plugin\Payment\Method\PaymentMethodRefundPaymentInterface');
+    $payment_method->expects($this->once())
+      ->method('refundPayment');
+
+    $url = new Url($this->randomName());
+
+    $this->payment->expects($this->atLeastOnce())
+      ->method('getPaymentMethod')
+      ->will($this->returnValue($payment_method));
+    $this->payment->expects($this->atLeastOnce())
+      ->method('urlInfo')
+      ->with('canonical')
+      ->will($this->returnValue($url));
+
+    $form = array();
+    $form_state = array();
+
+    $this->form->submit($form, $form_state);
+    $this->assertSame($url, $form_state['redirect_route']);
+  }
+
+}
diff --git a/payment/tests/src/Event/PaymentPreRefundUnitTest.php b/payment/tests/src/Event/PaymentPreRefundUnitTest.php
new file mode 100644
index 0000000..46a6248
--- /dev/null
+++ b/payment/tests/src/Event/PaymentPreRefundUnitTest.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\payment\Event\PaymentPreRefundUnitTest.
+ */
+
+namespace Drupal\payment\Tests\Event;
+
+use Drupal\payment\Event\PaymentPreRefund;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\payment\Event\PaymentPreRefund
+ */
+class PaymentPreRefundUnitTest extends UnitTestCase {
+
+  /**
+   * The event under test.
+   *
+   * @var \Drupal\payment\Event\PaymentPreRefund
+   */
+  protected $event;
+
+  /**
+   * The payment.
+   *
+   * @var \Drupal\payment\Entity\PaymentInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $payment;
+
+  /**
+   * {@inheritdoc}
+   */
+  static function getInfo() {
+    return array(
+      'description' => '',
+      'group' => 'Payment',
+      'name' => '\Drupal\payment\Event\PaymentPreRefund unit test',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @covers ::__construct
+   */
+  public function setUp() {
+    $this->payment = $this->getMockBuilder('\Drupal\payment\Entity\Payment')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->event = new PaymentPreRefund($this->payment);
+  }
+
+  /**
+   * @covers ::getPayment
+   */
+  public function testGetPayment() {
+    $this->assertSame($this->payment, $this->event->getPayment());
+  }
+
+}
diff --git a/payment/tests/src/Plugin/Payment/Method/BasicDeriverUnitTest.php b/payment/tests/src/Plugin/Payment/Method/BasicDeriverUnitTest.php
index 83facc3..24c3a42 100644
--- a/payment/tests/src/Plugin/Payment/Method/BasicDeriverUnitTest.php
+++ b/payment/tests/src/Plugin/Payment/Method/BasicDeriverUnitTest.php
@@ -96,6 +96,8 @@ class BasicDeriverUnitTest extends UnitTestCase {
     $execute_status_id = $this->randomName();
     $capture = TRUE;
     $capture_status_id = $this->randomName();
+    $refund = TRUE;
+    $refund_status_id = $this->randomName();
 
     $payment_method_enabled_basic = $this->getMock('\Drupal\payment\Entity\PaymentMethodConfigurationInterface');
     $payment_method_enabled_basic->expects($this->any())
@@ -113,6 +115,8 @@ class BasicDeriverUnitTest extends UnitTestCase {
         'execute_status_id' => $execute_status_id,
         'capture' => $capture,
         'capture_status_id' => $capture_status_id,
+        'refund' => $refund,
+        'refund_status_id' => $refund_status_id,
       )));
     $payment_method_enabled_basic->expects($this->any())
       ->method('getPluginId')
@@ -134,6 +138,8 @@ class BasicDeriverUnitTest extends UnitTestCase {
         'execute_status_id' => $execute_status_id,
         'capture' => $capture,
         'capture_status_id' => $capture_status_id,
+        'refund' => $refund,
+        'refund_status_id' => $refund_status_id,
       )));
     $payment_method_disabled_basic->expects($this->any())
       ->method('getPluginId')
@@ -172,6 +178,12 @@ class BasicDeriverUnitTest extends UnitTestCase {
     $payment_method_plugin->expects($this->any())
       ->method('getCapture')
       ->will($this->returnValue($capture));
+    $payment_method_plugin->expects($this->any())
+      ->method('getRefundStatusId')
+      ->will($this->returnValue($refund_status_id));
+    $payment_method_plugin->expects($this->any())
+      ->method('getRefund')
+      ->will($this->returnValue($refund));
 
     $this->paymentMethodConfigurationManager->expects($this->any())
       ->method('createInstance')
diff --git a/payment/tests/src/Plugin/Payment/Method/BasicUnitTest.php b/payment/tests/src/Plugin/Payment/Method/BasicUnitTest.php
index a16d3fa..6ec5886 100644
--- a/payment/tests/src/Plugin/Payment/Method/BasicUnitTest.php
+++ b/payment/tests/src/Plugin/Payment/Method/BasicUnitTest.php
@@ -53,6 +53,8 @@ class BasicUnitTest extends PaymentMethodBaseUnitTestBase {
       'execute_status_id' => $this->randomName(),
       'capture' => TRUE,
       'capture_status_id' => $this->randomName(),
+      'refund' => TRUE,
+      'refund_status_id' => $this->randomName(),
     );
 
     $this->paymentStatusManager = $this->getMock('\Drupal\payment\Plugin\Payment\Status\PaymentStatusManagerInterface');
@@ -108,6 +110,20 @@ class BasicUnitTest extends PaymentMethodBaseUnitTestBase {
   }
 
   /**
+   * @covers ::getRefundStatusId
+   */
+  public function testGetRefundStatusId() {
+    $this->assertSame($this->pluginDefinition['refund_status_id'], $this->paymentMethod->getRefundStatusId());
+  }
+
+  /**
+   * @covers ::getRefund
+   */
+  public function testGetRefund() {
+    $this->assertSame($this->pluginDefinition['refund'], $this->paymentMethod->getRefund());
+  }
+
+  /**
    * @covers ::doExecutePayment
    */
   public function testDoExecutePayment() {
@@ -175,14 +191,20 @@ class BasicUnitTest extends PaymentMethodBaseUnitTestBase {
    *
    * @dataProvider providerDoCapturePaymentAccess
    */
-  public function testDoCapturePaymentAccess($expected, $capture, $current_status_id, $capture_status_id) {
+  public function testDoCapturePaymentAccess($expected, $capture, $current_status_id, $execute_status_id, $capture_status_id) {
+    $this->pluginDefinition['execute_status_id'] = $execute_status_id;
     $this->pluginDefinition['capture'] = $capture;
     $this->pluginDefinition['capture_status_id'] = $capture_status_id;
 
     $this->paymentMethod = new Basic(array(), '', $this->pluginDefinition, $this->moduleHandler, $this->eventDispatcher, $this->token, $this->paymentStatusManager);
 
-    $payment_status = $this->getMock('\Drupal\payment\Plugin\Payment\Status\PaymentStatusInterface');
-    $payment_status->expects($this->any())
+    $capture_payment_status = $this->getMock('\Drupal\payment\Plugin\Payment\Status\PaymentStatusInterface');
+    $capture_payment_status->expects($this->any())
+      ->method('getPluginId')
+      ->will($this->returnValue($current_status_id));
+
+    $capture_payment_status = $this->getMock('\Drupal\payment\Plugin\Payment\Status\PaymentStatusInterface');
+    $capture_payment_status->expects($this->any())
       ->method('getPluginId')
       ->will($this->returnValue($current_status_id));
 
@@ -191,7 +213,7 @@ class BasicUnitTest extends PaymentMethodBaseUnitTestBase {
       ->getMock();
     $payment->expects($this->any())
       ->method('getStatus')
-      ->will($this->returnValue($payment_status));
+      ->will($this->returnValue($capture_payment_status));
 
     $this->paymentMethod->setPayment($payment);
 
@@ -209,14 +231,113 @@ class BasicUnitTest extends PaymentMethodBaseUnitTestBase {
   public function providerDoCapturePaymentAccess() {
     $status_id_a = $this->randomName();
     $status_id_b = $this->randomName();
+    $status_id_c = $this->randomName();
+    return array(
+      array(TRUE, TRUE, $status_id_a, $status_id_a, $status_id_b),
+      array(FALSE, TRUE, $status_id_a, $status_id_b, $status_id_c),
+      array(FALSE, FALSE, $status_id_a, $status_id_a, $status_id_b),
+    );
+  }
+
+  /**
+   * @covers ::doRefundPayment
+   */
+  public function testDoRefundPayment() {
+    $payment_status = $this->getMock('\Drupal\payment\Plugin\Payment\Status\PaymentStatusInterface');
+
+    $this->paymentStatusManager->expects($this->once())
+      ->method('createInstance')
+      ->with($this->pluginDefinition['refund_status_id'])
+      ->will($this->returnValue($payment_status));
+
+    $payment = $this->getMockBuilder('\Drupal\payment\Entity\Payment')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $payment->expects($this->once())
+      ->method('save');
+    $payment->expects($this->once())
+      ->method('setStatus')
+      ->with($payment_status);
+
+    $this->paymentMethod->setPayment($payment);
+
+    $method = new \ReflectionMethod($this->paymentMethod, 'doRefundPayment');
+    $method->setAccessible(TRUE);
+
+    $method->invoke($this->paymentMethod, $payment);
+  }
+
+  /**
+   * @covers ::doRefundPaymentAccess
+   *
+   * @dataProvider providerDoRefundPaymentAccess
+   */
+  public function testDoRefundPaymentAccess($expected, $refund, $current_status_id, $capture_status_id, $refund_status_id) {
+    $this->pluginDefinition['capture_status_id'] = $capture_status_id;
+    $this->pluginDefinition['refund'] = $refund;
+    $this->pluginDefinition['refund_status_id'] = $refund_status_id;
+
+    $this->paymentMethod = new Basic(array(), '', $this->pluginDefinition, $this->moduleHandler, $this->eventDispatcher, $this->token, $this->paymentStatusManager);
+
+    $payment_status = $this->getMock('\Drupal\payment\Plugin\Payment\Status\PaymentStatusInterface');
+    $payment_status->expects($this->any())
+      ->method('getPluginId')
+      ->will($this->returnValue($current_status_id));
+
+    $payment = $this->getMockBuilder('\Drupal\payment\Entity\Payment')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $payment->expects($this->any())
+      ->method('getStatus')
+      ->will($this->returnValue($payment_status));
+
+    $this->paymentMethod->setPayment($payment);
+
+    $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
+
+    $method = new \ReflectionMethod($this->paymentMethod, 'doRefundPaymentAccess');
+    $method->setAccessible(TRUE);
+
+    $this->assertSame($expected, $method->invoke($this->paymentMethod, $account));
+  }
+
+  /**
+   * Provides data to self::testDoRefundPaymentAccess().
+   */
+  public function providerDoRefundPaymentAccess() {
+    $status_id_a = $this->randomName();
+    $status_id_b = $this->randomName();
+    $status_id_c = $this->randomName();
     return array(
-      array(TRUE, TRUE, $status_id_a, $status_id_b),
-      array(FALSE, FALSE, $status_id_a, $status_id_b),
-      array(FALSE, TRUE, $status_id_a, $status_id_a),
+      array(TRUE, TRUE, $status_id_a, $status_id_a, $status_id_b),
+      array(FALSE, TRUE, $status_id_a, $status_id_b, $status_id_c),
+      array(FALSE, FALSE, $status_id_a, $status_id_a, $status_id_b),
     );
   }
 
   /**
+   * @covers ::updatePaymentStatusAccess
+   */
+  public function testUpdatePaymentStatusAccess() {
+    $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
+
+    $this->assertFalse($this->paymentMethod->updatePaymentStatusAccess($account));
+  }
+
+  /**
+   * @covers ::getSettablePaymentStatuses
+   */
+  public function testGetSettablePaymentStatuses() {
+    $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
+
+    $payment = $this->getMockBuilder('\Drupal\payment\Entity\Payment')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->assertSame(array(), $this->paymentMethod->getSettablePaymentStatuses($account, $payment));
+  }
+
+  /**
    * @covers ::getSupportedCurrencies
    */
   public function testGetSupportedCurrencies() {
diff --git a/payment/tests/src/Plugin/Payment/Method/PaymentMethodBaseUnitTest.php b/payment/tests/src/Plugin/Payment/Method/PaymentMethodBaseUnitTest.php
index 8f265c0..93d0c8b 100644
--- a/payment/tests/src/Plugin/Payment/Method/PaymentMethodBaseUnitTest.php
+++ b/payment/tests/src/Plugin/Payment/Method/PaymentMethodBaseUnitTest.php
@@ -267,14 +267,10 @@ class PaymentMethodBaseUnitTest extends PaymentMethodBaseUnitTestBase {
    *
    * @dataProvider providerTestCapturePaymentAccess
    */
-  public function testCapturePaymentAccess($expected, $update_access, $do) {
+  public function testCapturePaymentAccess($expected, $do) {
     $payment = $this->getMockBuilder('\Drupal\payment\Entity\Payment')
       ->disableOriginalConstructor()
       ->getMock();
-    $payment->expects($this->once())
-      ->method('access')
-      ->with('capture')
-      ->will($this->returnValue($update_access));
 
     $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
 
@@ -293,10 +289,8 @@ class PaymentMethodBaseUnitTest extends PaymentMethodBaseUnitTestBase {
    */
   public function providerTestCapturePaymentAccess() {
     return array(
-      array(TRUE, TRUE, TRUE),
-      array(FALSE, FALSE, TRUE),
-      array(FALSE, TRUE, FALSE),
-      array(FALSE, FALSE, FALSE),
+      array(TRUE, TRUE),
+      array(FALSE, FALSE),
     );
   }
 
@@ -312,6 +306,81 @@ class PaymentMethodBaseUnitTest extends PaymentMethodBaseUnitTestBase {
   }
 
   /**
+   * @covers ::refundPayment
+   */
+  public function testRefundPayment() {
+    $payment = $this->getMockBuilder('\Drupal\payment\Entity\Payment')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->eventDispatcher->expects($this->once())
+      ->method('dispatch')
+      ->with(PaymentEvents::PAYMENT_PRE_REFUND, $this->isInstanceOf('\Drupal\payment\Event\PaymentPreRefund'));
+
+    $this->moduleHandler->expects($this->once())
+      ->method('invokeAll')
+      ->with('payment_pre_refund', array($payment));
+
+    $this->plugin->setPayment($payment);
+    $this->plugin->expects($this->once())
+      ->method('doRefundPayment');
+
+    $this->plugin->refundPayment();
+  }
+
+  /**
+   * @covers ::refundPayment
+   *
+   * @expectedException \Exception
+   */
+  public function testRefundPaymentWithoutPayment() {
+    $this->plugin->refundPayment();
+  }
+
+  /**
+   * @covers ::refundPaymentAccess
+   *
+   * @dataProvider providerTestRefundPaymentAccess
+   */
+  public function testRefundPaymentAccess($expected, $do) {
+    $payment = $this->getMockBuilder('\Drupal\payment\Entity\Payment')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
+
+    /** @var \Drupal\payment\Plugin\Payment\Method\PaymentMethodBase|\PHPUnit_Framework_MockObject_MockObject $payment_method */
+    $this->plugin->setPayment($payment);
+    $this->plugin->expects($this->any())
+      ->method('doRefundPaymentAccess')
+      ->with($account)
+      ->will($this->returnValue($do));
+
+    $this->assertSame($expected, $this->plugin->refundPaymentAccess($account));
+  }
+
+  /**
+   * Provides data to self::testRefundPaymentAccess().
+   */
+  public function providerTestRefundPaymentAccess() {
+    return array(
+      array(TRUE, TRUE),
+      array(FALSE, FALSE),
+    );
+  }
+
+  /**
+   * @covers ::refundPaymentAccess
+   *
+   * @expectedException \Exception
+   */
+  public function testRefundPaymentAccessWithoutPayment() {
+    $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
+
+    $this->plugin->refundPaymentAccess($account);
+  }
+
+  /**
    * @covers ::executePaymentAccessEvent
    *
    * @dataProvider providerTestExecutePaymentAccessEvent
diff --git a/payment/tests/src/Plugin/Payment/MethodConfiguration/BasicUnitTest.php b/payment/tests/src/Plugin/Payment/MethodConfiguration/BasicUnitTest.php
index ff9b80c..5ef6863 100644
--- a/payment/tests/src/Plugin/Payment/MethodConfiguration/BasicUnitTest.php
+++ b/payment/tests/src/Plugin/Payment/MethodConfiguration/BasicUnitTest.php
@@ -136,19 +136,70 @@ class BasicUnitTest extends UnitTestCase {
   }
 
   /**
+   * @covers ::getRefundStatusId
+   * @covers ::setRefundStatusId
+   */
+  public function testGetRefundStatusId() {
+    $status = $this->randomName();
+    $this->assertSame($this->paymentMethodConfiguration, $this->paymentMethodConfiguration->setRefundStatusId($status));
+    $this->assertSame($status, $this->paymentMethodConfiguration->getRefundStatusId());
+  }
+
+  /**
+   * @covers ::getRefund
+   * @covers ::setRefund
+   */
+  public function testGetRefund() {
+    $refund = TRUE;
+    $this->assertSame($this->paymentMethodConfiguration, $this->paymentMethodConfiguration->setRefund($refund));
+    $this->assertSame($refund, $this->paymentMethodConfiguration->getRefund());
+  }
+
+  /**
    * @covers ::buildConfigurationForm
    */
   public function testBuildConfigurationForm() {
     $form = array();
     $form_state = array();
     $elements = $this->paymentMethodConfiguration->buildConfigurationForm($form, $form_state);
+    $form['plugin_form']['#process'][] = array($this->paymentMethodConfiguration, 'processBuildConfigurationForm');
+    $this->assertArrayHasKey('message', $elements);
+    $this->assertArrayHasKey('plugin_form', $elements);
+    $this->assertSame(array(array($this->paymentMethodConfiguration, 'processBuildConfigurationForm')), $elements['plugin_form']['#process']);
+  }
+
+  /**
+   * @covers ::processBuildConfigurationForm
+   */
+  public function testProcessBuildConfigurationForm() {
+    $definitions = array(
+      array(
+        'id' => $this->randomName(),
+        'label' => $this->randomName(),
+      ),
+      array(
+        'id' => $this->randomName(),
+        'label' => $this->randomName(),
+      ),
+    );
+    $this->paymentStatusManager->expects($this->atLeastOnce())
+      ->method('getDefinitions')
+      ->will($this->returnValue($definitions));
+
+    $element = array(
+      '#parents' => array('foo', 'bar'),
+    );
+    $form = array();
+    $form_state = array();
+
+    $method = new \ReflectionMethod($this->paymentMethodConfiguration ,'processBuildConfigurationForm');
+    $method->setAccessible(TRUE);
+    $elements = $method->invokeArgs($this->paymentMethodConfiguration, array(&$element, &$form_state, &$form));
     $this->assertInternalType('array', $elements);
-    foreach (array('brand_label', 'message', 'execute_status_id', 'capture_status_id_wrapper') as $key) {
+    foreach (array('brand_label', 'execute', 'capture', 'refund') as $key) {
       $this->assertArrayHasKey($key, $elements);
       $this->assertInternalType('array', $elements[$key]);
     }
-    $this->assertArrayHasKey('capture_status_id', $elements['capture_status_id_wrapper']);
-    $this->assertInternalType('array', $elements['capture_status_id_wrapper']['capture_status_id']);
   }
 
   /**
@@ -160,14 +211,18 @@ class BasicUnitTest extends UnitTestCase {
     $execute_status_id = $this->randomName();
     $capture = TRUE;
     $capture_status_id = $this->randomName();
+    $refund = TRUE;
+    $refund_status_id = $this->randomName();
 
     $form = array(
-      'brand_label' => array(
-        '#parents' => array('foo', 'bar', 'status')
-      ),
       'message' => array(
         '#parents' => array('foo', 'bar', 'message')
       ),
+      'plugin_form' => array(
+        'brand_label' => array(
+          '#parents' => array('foo', 'bar', 'status')
+        ),
+      ),
     );
     $form_state = array(
       'values' => array(
@@ -175,11 +230,17 @@ class BasicUnitTest extends UnitTestCase {
           'bar' => array(
             'brand_label' => $brand_label,
             'message' => $message,
-            'execute_status_id' => $execute_status_id,
-            'capture' => $capture,
-            'capture_status_id_wrapper' => array(
+            'execute' => array(
+              'execute_status_id' => $execute_status_id,
+            ),
+            'capture' => array(
+              'capture' => $capture,
               'capture_status_id' => $capture_status_id,
             ),
+            'refund' => array(
+              'refund' => $refund,
+              'refund_status_id' => $refund_status_id,
+            ),
           ),
         ),
       ),
@@ -208,6 +269,9 @@ class BasicUnitTest extends UnitTestCase {
 
 namespace {
 
+  if (!function_exists('drupal_get_path')) {
+    function drupal_get_path() {}
+  }
   if (!function_exists('drupal_html_id')) {
     function drupal_html_id() {}
   }
diff --git a/payment_test/src/Plugin/Payment/Method/PaymentTest.php b/payment_test/src/Plugin/Payment/Method/PaymentTest.php
index eb88864..eea6aec 100644
--- a/payment_test/src/Plugin/Payment/Method/PaymentTest.php
+++ b/payment_test/src/Plugin/Payment/Method/PaymentTest.php
@@ -45,4 +45,17 @@ class PaymentTest extends PaymentMethodBase {
     return FALSE;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function doRefundPayment() {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doRefundPaymentAccess(AccountInterface $account) {
+    return FALSE;
+  }
+
 }
