diff --git a/modules/payment_form/src/Entity/Payment/PaymentForm.php b/modules/payment_form/src/Entity/Payment/PaymentForm.php
index 24fdbd7..4e81361 100644
--- a/modules/payment_form/src/Entity/Payment/PaymentForm.php
+++ b/modules/payment_form/src/Entity/Payment/PaymentForm.php
@@ -12,6 +12,7 @@ use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Drupal\payment\Plugin\Payment\MethodSelector\PaymentMethodSelectorManagerInterface;
+use Drupal\payment\ResponseInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -101,7 +102,10 @@ class PaymentForm extends ContentEntityForm {
     $payment_method_selector->submitConfigurationForm($form['payment_method'], $form_state);
     $payment->setPaymentMethod($payment_method_selector->getPaymentMethod());
     $payment->save();
-    $payment->execute();
+    $status = $payment->execute();
+    if ($status instanceof ResponseInterface) {
+      $form_state->setRedirectUrl($status->getRedirectUrl());
+    }
   }
 
   /**
diff --git a/modules/payment_form/tests/src/Unit/Entity/Payment/PaymentFormUnitTest.php b/modules/payment_form/tests/src/Unit/Entity/Payment/PaymentFormUnitTest.php
index 5392b1e..f8e3208 100644
--- a/modules/payment_form/tests/src/Unit/Entity/Payment/PaymentFormUnitTest.php
+++ b/modules/payment_form/tests/src/Unit/Entity/Payment/PaymentFormUnitTest.php
@@ -8,6 +8,8 @@
 namespace Drupal\Tests\payment_form\Unit\Entity\Payment;
 
 use Drupal\Core\Form\FormState;
+use Drupal\Core\Url;
+use Drupal\payment\Response;
 use Drupal\payment_form\Entity\Payment\PaymentForm;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -209,6 +211,9 @@ class PaymentFormUnitTest extends UnitTestCase {
    * @covers ::submitForm
    */
   public function testSubmitForm() {
+    $redirect_url = new Url($this->randomMachineName());
+    $payment_method_execution_status = new Response($redirect_url);
+
     $form = array(
       'payment_method' => array(
         '#type' => $this->randomMachineName(),
@@ -219,6 +224,9 @@ class PaymentFormUnitTest extends UnitTestCase {
       ->method('get')
       ->with('payment_method_selector')
       ->willReturn($this->paymentMethodSelector);
+    $form_state->expects($this->atLeastOnce())
+      ->method('setRedirectUrl')
+      ->with($redirect_url);
 
     $payment_method = $this->getMock('\Drupal\payment\Plugin\Payment\Method\PaymentMethodInterface');
 
@@ -235,7 +243,8 @@ class PaymentFormUnitTest extends UnitTestCase {
     $this->payment->expects($this->once())
       ->method('save');
     $this->payment->expects($this->once())
-      ->method('execute');
+      ->method('execute')
+      ->willReturn($payment_method_execution_status);
 
     $this->form->submitForm($form, $form_state);
   }
diff --git a/payment.routing.yml b/payment.routing.yml
index eac30d0..b7c2831 100644
--- a/payment.routing.yml
+++ b/payment.routing.yml
@@ -62,6 +62,14 @@ payment.payment.refund:
   options:
     _admin_route: TRUE
 
+payment.payment.refund:
+  path: '/payment/{payment}/resume'
+  defaults:
+    _entity_form: 'payment.refund'
+    _title: Resume payment
+  requirements:
+    _entity_access: 'payment.resume'
+
 payment.payment.delete:
   path: '/payment/{payment}/delete'
   defaults:
diff --git a/src/Controller/Payment.php b/src/Controller/Payment.php
index c72d6b4..7a9be42 100644
--- a/src/Controller/Payment.php
+++ b/src/Controller/Payment.php
@@ -11,6 +11,8 @@ use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Drupal\payment\Entity\PaymentInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Response;
 
 /**
  * Returns responses for payment routes.
@@ -60,4 +62,21 @@ class Payment extends ControllerBase {
     ));
   }
 
+  /**
+   * Resumes a payment.
+   *
+   * @param \Drupal\payment\Entity\PaymentInterface $payment
+   *
+   * @return \Symfony\Component\HttpFoundation\Response
+   */
+  public function resume(PaymentInterface $payment) {
+    $status = $payment->getPaymentMethod()->getPaymentExecutionStatus();
+    if ($status->getResponse() instanceof Response) {
+      return $status->getResponse();
+    }
+    else {
+      return new RedirectResponse($status->getRedirectUrl());
+    }
+  }
+
 }
diff --git a/src/Entity/Payment.php b/src/Entity/Payment.php
index b81f779..a9c0e66 100644
--- a/src/Entity/Payment.php
+++ b/src/Entity/Payment.php
@@ -55,6 +55,7 @@ use Drupal\user\UserInterface;
  *     "update-status-form" = "payment.payment.update_status",
  *     "capture-form" = "payment.payment.capture",
  *     "refund-form" = "payment.payment.refund"
+ *     "resume" = "payment.payment.resume"
  *   }
  * )
  */
diff --git a/src/Entity/Payment/PaymentAccessControlHandler.php b/src/Entity/Payment/PaymentAccessControlHandler.php
index 775228c..a11dd5c 100644
--- a/src/Entity/Payment/PaymentAccessControlHandler.php
+++ b/src/Entity/Payment/PaymentAccessControlHandler.php
@@ -50,6 +50,11 @@ class PaymentAccessControlHandler extends EntityAccessControlHandler {
       }
       return AccessResult::forbidden();
     }
+    elseif ($operation == 'resume') {
+      return AccessResult::allowedIf($payment->getOwnerId() == $account->id())
+        ->andIf($payment->getPaymentMethod())
+        ->andIf($payment->getPaymentMethod()->getPaymentExecutionStatus() === TRUE);
+    }
     return $this->checkAccessPermission($payment, $operation, $account);
   }
 
diff --git a/src/Entity/Payment/PaymentListBuilder.php b/src/Entity/Payment/PaymentListBuilder.php
index 219fde6..d606570 100644
--- a/src/Entity/Payment/PaymentListBuilder.php
+++ b/src/Entity/Payment/PaymentListBuilder.php
@@ -192,6 +192,12 @@ class PaymentListBuilder extends EntityListBuilder {
           'url' => $entity->urlInfo('refund-form'),
         );
     }
+    if ($entity->access('resume')) {
+      $operations['resume'] = array(
+        'title' => $this->t('Resume'),
+        'url' => $entity->urlInfo('resume'),
+      );
+    }
 
     return $operations;
   }
diff --git a/src/Plugin/Payment/Method/Basic.php b/src/Plugin/Payment/Method/Basic.php
index ef05cce..8ea59ad 100644
--- a/src/Plugin/Payment/Method/Basic.php
+++ b/src/Plugin/Payment/Method/Basic.php
@@ -155,7 +155,13 @@ class Basic extends PaymentMethodBase implements ContainerFactoryPluginInterface
   protected function doExecutePayment() {
     $this->getPayment()->setPaymentStatus($this->paymentStatusManager->createInstance($this->getExecuteStatusId()));
     $this->getPayment()->save();
-    $this->getPayment()->getPaymentType()->resumeContext();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPaymentExecutionStatus() {
+    return $this->getPayment()->getPaymentStatus()->getPluginId() == $this->getExecuteStatusId();
   }
 
   /**
diff --git a/src/Plugin/Payment/Method/PaymentMethodBase.php b/src/Plugin/Payment/Method/PaymentMethodBase.php
index 0572b6d..07a9293 100644
--- a/src/Plugin/Payment/Method/PaymentMethodBase.php
+++ b/src/Plugin/Payment/Method/PaymentMethodBase.php
@@ -235,6 +235,8 @@ abstract class PaymentMethodBase extends PluginBase implements ContainerFactoryP
     $this->eventDispatcher->dispatch(PaymentEvents::PAYMENT_PRE_EXECUTE, $event);
     // @todo invoke Rules event.
     $this->doExecutePayment();
+
+    return $this->getPaymentExecutionStatus();
   }
 
   /**
diff --git a/src/Plugin/Payment/Method/PaymentMethodInterface.php b/src/Plugin/Payment/Method/PaymentMethodInterface.php
index 3e45743..3ae65e9 100644
--- a/src/Plugin/Payment/Method/PaymentMethodInterface.php
+++ b/src/Plugin/Payment/Method/PaymentMethodInterface.php
@@ -43,12 +43,29 @@ interface PaymentMethodInterface extends PluginInspectionInterface, Configurable
    * Executes the payment.
    *
    * When executing a payment, it may be authorized, or authorized and captured.
+   * After calling this method, more action may be required depending on the
+   * return value of self::getPaymentExecutionStatus().
+   *
+   * @return \Drupal\payment\ResponseInterface|bool
+   *   TRUE if payment execution was completed, FALSE if execution failed, or an
+   *   instance of \Drupal\payment\ResponseInterface with information to resume
+   *   execution.
    *
    * @see self::executePaymentAccess
    */
   public function executePayment();
 
   /**
+   * Gets the payment execution status.
+   *
+   * @return \Drupal\payment\ResponseInterface|bool
+   *   TRUE if payment execution was completed, FALSE if execution failed, or an
+   *   instance of \Drupal\payment\ResponseInterface with information to resume
+   *   execution.
+   */
+  public function getPaymentExecutionStatus();
+
+  /**
    * Whether payment execution interrupts the payment type's context.
    *
    * If payment execution interrupts the context's workflow, this must return
diff --git a/src/Plugin/Payment/Method/Unavailable.php b/src/Plugin/Payment/Method/Unavailable.php
index 7ec1fa2..52b000d 100644
--- a/src/Plugin/Payment/Method/Unavailable.php
+++ b/src/Plugin/Payment/Method/Unavailable.php
@@ -93,6 +93,13 @@ class Unavailable extends PluginBase implements PaymentMethodInterface {
   /**
    * {@inheritdoc}
    */
+  public function getPaymentExecutionStatus() {
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function capturePaymentAccess(AccountInterface $account) {
     return FALSE;
   }
diff --git a/src/Plugin/Payment/Type/PaymentTypeInterface.php b/src/Plugin/Payment/Type/PaymentTypeInterface.php
index 0aff7d7..fca3d1a 100644
--- a/src/Plugin/Payment/Type/PaymentTypeInterface.php
+++ b/src/Plugin/Payment/Type/PaymentTypeInterface.php
@@ -43,7 +43,7 @@ interface PaymentTypeInterface extends PluginInspectionInterface, ConfigurablePl
   public function paymentDescription($language_code = NULL);
 
   /**
-   * Checks if the payment type context can be resumed..
+   * Checks if the payment type context can be resumed.
    *
    * @param \Drupal\Core\Session\AccountInterface $account
    *
@@ -54,7 +54,14 @@ interface PaymentTypeInterface extends PluginInspectionInterface, ConfigurablePl
   public function resumeContextAccess(AccountInterface $account);
 
   /**
-   * Resumes the payment context's workflow.
+   * Resumes the payer's original workflow.
+   *
+   * The returned response MUST contain all necessary information to return the
+   * payer to where they were when they started payment execution. Because this
+   * method does not always have to be called, it MUST NOT perform any other
+   * critical operations.
+   *
+   * @return \Drupal\payment\ResponseInterface
    *
    * @see self::resumeContextAccess
    */
diff --git a/src/Response.php b/src/Response.php
new file mode 100644
index 0000000..1503f99
--- /dev/null
+++ b/src/Response.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\payment\Response.
+ */
+
+namespace Drupal\payment;
+
+use Drupal\Core\Url;
+use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
+
+/**
+ * Provides a payment response.
+ */
+class Response implements ResponseInterface {
+
+  /**
+   * The redirect URL.
+   *
+   * @var \Drupal\Core\Url
+   */
+  protected $redirectUrl;
+
+  /**
+   * The response.
+   *
+   * @var \Symfony\Component\HttpFoundation\Response|null
+   */
+  protected $response;
+
+  /**
+   * Constructs a new instance.
+   *
+   * @param \Drupal\Core\Url $redirect_url
+   * @param \Symfony\Component\HttpFoundation\Response|null $response
+   */
+  public function __construct(Url $redirect_url, SymfonyResponse $response = NULL) {
+    $this->redirectUrl = $redirect_url;
+    $this->response = $response;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRedirectUrl() {
+    return $this->redirectUrl;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getResponse() {
+    return $this->response;
+  }
+
+}
diff --git a/src/ResponseInterface.php b/src/ResponseInterface.php
new file mode 100644
index 0000000..27aab41
--- /dev/null
+++ b/src/ResponseInterface.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\payment\ResponseInterface.
+ */
+
+namespace Drupal\payment;
+
+/**
+ * Defines a payment response.
+ *
+ * When returning a instance of a class that implements this interface,
+ * self::getRedirectUrl() MUST return a value and calling code MUST be able to
+ * perform a redirect using this value. All other functionality is optional.
+ */
+interface ResponseInterface {
+
+  /**
+   * Gets the URL the calling code may redirect to after payment execution.
+   *
+   * @return \Drupal\Core\Url
+   */
+  public function getRedirectUrl();
+
+  /**
+   * Gets the response
+   *
+   * @return \Symfony\Component\HttpFoundation\Response|null
+   */
+  public function getResponse();
+
+}
diff --git a/tests/src/Unit/Controller/PaymentUnitTest.php b/tests/src/Unit/Controller/PaymentUnitTest.php
index b7d856c..d9120ba 100644
--- a/tests/src/Unit/Controller/PaymentUnitTest.php
+++ b/tests/src/Unit/Controller/PaymentUnitTest.php
@@ -104,4 +104,32 @@ class PaymentUnitTest extends UnitTestCase {
     $this->assertSame($string, $this->controller->editTitle($payment));
   }
 
+  /**
+   * @covers ::resume
+   */
+  public function testResumeWithCompletedExecution() {
+    $status = TRUE;
+
+    $payment_method = $this->getMock('\Drupal\payment\Plugin\Payment\Method\PaymentMethodInterface');
+    $payment_method->expects($this->atLeastOnce())
+      ->method('getPaymentExecutionStatus')
+      ->willReturn($status);
+
+    $payment = $this->getMockBuilder('\Drupal\payment\Entity\Payment')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $payment->expects($this->once())
+      ->method('id')
+      ->will($this->returnValue($id));
+
+    $this->stringTranslation->expects($this->any())
+      ->method('translate')
+      ->with($string, array(
+        '!payment_id' => $id,
+      ))
+      ->will($this->returnArgument(0));
+
+    $this->assertSame($string, $this->controller->editTitle($payment));
+  }
+
 }
diff --git a/tests/src/Unit/Plugin/Payment/Method/BasicUnitTest.php b/tests/src/Unit/Plugin/Payment/Method/BasicUnitTest.php
index a16042d..69ded14 100644
--- a/tests/src/Unit/Plugin/Payment/Method/BasicUnitTest.php
+++ b/tests/src/Unit/Plugin/Payment/Method/BasicUnitTest.php
@@ -141,19 +141,12 @@ class BasicUnitTest extends PaymentMethodBaseUnitTestBase {
       ->with($this->pluginDefinition['execute_status_id'])
       ->will($this->returnValue($payment_status));
 
-    $payment_type = $this->getMock('\Drupal\payment\Plugin\Payment\Type\PaymentTypeInterface');
-    $payment_type->expects($this->once())
-      ->method('resumeContext');
-
     $payment = $this->getMockBuilder('\Drupal\payment\Entity\Payment')
       ->disableOriginalConstructor()
       ->getMock();
     $payment->expects($this->once())
       ->method('save');
     $payment->expects($this->once())
-      ->method('getPaymentType')
-      ->will($this->returnValue($payment_type));
-    $payment->expects($this->once())
       ->method('setPaymentStatus')
       ->with($payment_status);
 
@@ -194,6 +187,48 @@ class BasicUnitTest extends PaymentMethodBaseUnitTestBase {
   }
 
   /**
+   * @covers ::getPaymentExecutionStatus
+   */
+  public function testGetPaymentExecutionStatusWithCompletedExecution() {
+    $payment_status = $this->getMock('\Drupal\payment\Plugin\Payment\Status\PaymentStatusInterface');
+    $payment_status->expects($this->atLeastOnce())
+      ->method('getPluginId')
+      ->willReturn($this->pluginDefinition['execute_status_id']);
+
+    $payment = $this->getMockBuilder('\Drupal\payment\Entity\Payment')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $payment->expects($this->once())
+      ->method('getPaymentStatus')
+      ->willReturn($payment_status);
+
+    $this->paymentMethod->setPayment($payment);
+
+    $this->assertTrue($this->paymentMethod->getPaymentExecutionStatus());
+  }
+
+  /**
+   * @covers ::getPaymentExecutionStatus
+   */
+  public function testGetPaymentExecutionStatusWithIncompleteExecution() {
+    $payment_status = $this->getMock('\Drupal\payment\Plugin\Payment\Status\PaymentStatusInterface');
+    $payment_status->expects($this->atLeastOnce())
+      ->method('getPluginId')
+      ->willReturn($this->randomMachineName());
+
+    $payment = $this->getMockBuilder('\Drupal\payment\Entity\Payment')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $payment->expects($this->once())
+      ->method('getPaymentStatus')
+      ->willReturn($payment_status);
+
+    $this->paymentMethod->setPayment($payment);
+
+    $this->assertFalse($this->paymentMethod->getPaymentExecutionStatus());
+  }
+
+  /**
    * @covers ::doCapturePaymentAccess
    *
    * @dataProvider providerDoCapturePaymentAccess
diff --git a/tests/src/Unit/Plugin/Payment/Method/UnavailableUnitTest.php b/tests/src/Unit/Plugin/Payment/Method/UnavailableUnitTest.php
index b61d18d..cacff46 100644
--- a/tests/src/Unit/Plugin/Payment/Method/UnavailableUnitTest.php
+++ b/tests/src/Unit/Plugin/Payment/Method/UnavailableUnitTest.php
@@ -188,4 +188,11 @@ class UnavailableUnitTest extends UnitTestCase {
     $this->plugin->capturePayment();
   }
 
+  /**
+   * @covers ::getPaymentExecutionStatus
+   */
+  public function testGetPaymentExecutionStatus() {
+    $this->assertInternalType('bool', $this->plugin->getPaymentExecutionStatus());
+  }
+
 }
diff --git a/tests/src/Unit/ResponseUnitTest.php b/tests/src/Unit/ResponseUnitTest.php
new file mode 100644
index 0000000..d6f0196
--- /dev/null
+++ b/tests/src/Unit/ResponseUnitTest.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\payment\Unit\ResponseUnitTest.
+ */
+
+namespace Drupal\Tests\payment\Unit;
+
+use Drupal\Core\Url;
+use Drupal\payment\Response;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @coversDefaultClass \Drupal\payment\Response
+ *
+ * @group Payment
+ */
+class ResponseUnitTest extends UnitTestCase {
+
+  /**
+   * The redirect URL.
+   *
+   * @var \Drupal\Core\Url
+   */
+  protected $redirectUrl;
+
+  /**
+   * The response.
+   *
+   * @var \Symfony\Component\HttpFoundation\Response|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $symfonyResponse;
+
+  /**
+   * The response under test.
+   *
+   * @var \Drupal\payment\Response
+   */
+  protected $response;
+
+  /**
+   * {@inheritdoc}
+   *
+   * @covers ::__construct
+   */
+  public function setUp() {
+    $this->redirectUrl = new Url($this->randomMachineName());
+
+    $this->symfonyResponse = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Response')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->response = new Response($this->redirectUrl, $this->symfonyResponse);
+  }
+
+  /**
+   * @covers ::getRedirectUrl
+   */
+  function testGetRedirectUrl() {
+    $this->assertSame($this->redirectUrl, $this->response->getRedirectUrl());
+  }
+
+  /**
+   * @covers ::getResponse
+   */
+  function testGetResponse() {
+    $this->assertSame($this->symfonyResponse, $this->response->getResponse());
+  }
+
+}
