diff --git a/src/Plugin/Commerce/CheckoutPane/StripeReview.php b/src/Plugin/Commerce/CheckoutPane/StripeReview.php
index 0d22dde..36a7480 100644
--- a/src/Plugin/Commerce/CheckoutPane/StripeReview.php
+++ b/src/Plugin/Commerce/CheckoutPane/StripeReview.php
@@ -43,6 +43,7 @@ class StripeReview extends CheckoutPaneBase {
     return [
       'button_id' => 'edit-actions-next',
       'auto_submit_review_form' => 0,
+      'setup_future_usage' => '',
     ] + parent::defaultConfiguration();
   }
 
@@ -58,7 +59,16 @@ class StripeReview extends CheckoutPaneBase {
     else {
       $summary .= $this->t('Auto submit: On');
     }
-
+    switch($this->configuration['setup_future_usage']) {
+      case 'on_session':
+        $summary .= $this->t('Setup future usage: On session');
+        break;
+      case 'off_session':
+        $summary .= $this->t('Setup future usage: Off session');
+        break;
+      case '':
+        $summary .= $this->t('Setup future usage: None');
+    }
     return $summary;
   }
 
@@ -81,7 +91,17 @@ class StripeReview extends CheckoutPaneBase {
       '#description' => $this->t('When this is checked, the form the Stripe Review pane is displayed in will automatically submit on load. This starts the SCA process without user intervention.'),
       '#default_value' => $this->configuration['auto_submit_review_form'],
     ];
-
+    $form['setup_future_usage'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Setup future usage'),
+      '#options' => [
+        'on_session' => 'On session',
+        'off_session' => 'Off session',
+      ],
+      '#empty_value' => '',
+      '#default_value' => $this->getConfiguration()['setup_future_usage'],
+      '#description' => 'The intended future usage of the provided payment method. See <a href="https://stripe.com/docs/payments/payment-intents#future-usage">Stripe documentation</a>.',
+    ];
     return $form;
   }
 
@@ -95,6 +115,7 @@ class StripeReview extends CheckoutPaneBase {
       $values = $form_state->getValue($form['#parents']);
       $this->configuration['button_id'] = $values['button_id'];
       $this->configuration['auto_submit_review_form'] = $values['auto_submit_review_form'];
+      $this->configuration['setup_future_usage'] = $values['setup_future_usage'];
     }
   }
 
@@ -135,8 +156,13 @@ class StripeReview extends CheckoutPaneBase {
     else {
       $payment_process_pane = $this->checkoutFlow->getPane('payment_process');
       assert($payment_process_pane instanceof CheckoutPaneInterface);
-      $capture = $payment_process_pane->getConfiguration()['capture'];
-      $intent = $stripe_plugin->createPaymentIntent($this->order, $capture);
+      $intent_params = [
+        'capture_method' => $payment_process_pane->getConfiguration()['capture'] ? 'automatic' : 'manual',
+      ];
+      if (!empty($this->getConfiguration()['setup_future_usage'])) {
+        $intent_params['setup_future_usage'] = $this->getConfiguration()['setup_future_usage'];
+      }
+      $intent = $stripe_plugin->createPaymentIntent($this->order, $intent_params);
     }
     if ($intent->status === PaymentIntent::STATUS_REQUIRES_PAYMENT_METHOD) {
       $payment_method = $this->order->get('payment_method')->entity;
diff --git a/src/Plugin/Commerce/PaymentGateway/Stripe.php b/src/Plugin/Commerce/PaymentGateway/Stripe.php
index 3536d7b..24c6f07 100755
--- a/src/Plugin/Commerce/PaymentGateway/Stripe.php
+++ b/src/Plugin/Commerce/PaymentGateway/Stripe.php
@@ -205,15 +205,31 @@ class Stripe extends OnsitePaymentGatewayBase implements StripeInterface {
     $order = $payment->getOrder();
     assert($order instanceof OrderInterface);
     $intent_id = $order->getData('stripe_intent');
+    if (empty($intent_id)) {
+      // If createPayment is called and there is no payment intent, it means we
+      // are not in a checkout flow with the stripe review pane, so we can
+      // assume that it's an off_session payment initiated, e.g. by recurring.
+      $intent = $this->createPaymentIntent($order, [
+        // By default assume customer is not available for SCA.
+        'confirm' => TRUE,
+        'off_session' => TRUE,
+        'capture_method' => $capture ? 'automatic' : 'manual',
+      ], $payment);
+      $intent_id = $intent['id'];
+    }
     try {
       $intent = \Stripe\PaymentIntent::retrieve($intent_id);
+
       if ($intent->status === PaymentIntent::STATUS_REQUIRES_CONFIRMATION) {
         $intent = $intent->confirm();
       }
       if ($intent->status === PaymentIntent::STATUS_REQUIRES_ACTION) {
         throw new SoftDeclineException('The payment intent requires action by the customer for authentication');
       }
-      if (!in_array($intent->status, [PaymentIntent::STATUS_REQUIRES_CAPTURE, PaymentIntent::STATUS_SUCCEEDED], TRUE)) {
+      if (!in_array($intent->status, [
+        PaymentIntent::STATUS_REQUIRES_CAPTURE,
+        PaymentIntent::STATUS_SUCCEEDED
+      ], TRUE)) {
         $order->set('payment_method', NULL);
         $this->deletePaymentMethod($payment_method);
         if ($intent->status === PaymentIntent::STATUS_CANCELED) {
@@ -252,8 +268,7 @@ class Stripe extends OnsitePaymentGatewayBase implements StripeInterface {
 
       $order->unsetData('stripe_intent');
       $order->save();
-    }
-    catch (\Stripe\Exception\ApiErrorException $e) {
+    } catch (\Stripe\Exception\ApiErrorException $e) {
       ErrorHelper::handleException($e);
     }
   }
@@ -419,32 +434,25 @@ class Stripe extends OnsitePaymentGatewayBase implements StripeInterface {
   /**
    * {@inheritdoc}
    */
-  public function createPaymentIntent(OrderInterface $order, $capture = TRUE) {
-    /** @var \Drupal\commerce_payment\Entity\PaymentMethodInterface $payment_method */
-    $payment_method = $order->get('payment_method')->entity;
-
-    $payment_method_remote_id = $payment_method->getRemoteId();
-    $customer_remote_id = $this->getRemoteCustomerId($order->getCustomer());
-
-    $amount = $this->toMinorUnits($order->getTotalPrice());
-    $order_id = $order->id();
-    $capture_method = $capture ? 'automatic' : 'manual';
-    $intent_array = [
-      'amount' => $amount,
+  public function createPaymentIntent(OrderInterface $order, array $intent_params = [], $payment = NULL) {
+    $default_intent_params = [
+      'amount' => $this->toMinorUnits($order->getTotalPrice()),
       'currency' => strtolower($order->getTotalPrice()->getCurrencyCode()),
       'payment_method_types' => ['card'],
       'metadata' => [
-        'order_id' => $order_id,
+        'order_id' => $order->id(),
         'store_id' => $order->getStoreId(),
       ],
-      'payment_method' => $payment_method_remote_id,
-      'capture_method' => $capture_method,
+      'payment_method' => $payment ? $payment->getPaymentMethod()->getRemoteId() : $order->get('payment_method')->entity->getRemoteId(),
+      'capture_method' => 'automatic',
     ];
+    $customer_remote_id = $this->getRemoteCustomerId($order->getCustomer());
     if (!empty($customer_remote_id)) {
-      $intent_array['customer'] = $customer_remote_id;
+      $default_intent_params['customer'] = $customer_remote_id;
     }
+    $intent_params = array_replace_recursive($default_intent_params, $intent_params);
     try {
-      $intent = \Stripe\PaymentIntent::create($intent_array);
+      $intent = \Stripe\PaymentIntent::create($intent_params);
       $order->setData('stripe_intent', $intent->id)->save();
     }
     catch (\Stripe\Exception\ApiErrorException $e) {
diff --git a/src/Plugin/Commerce/PaymentGateway/StripeInterface.php b/src/Plugin/Commerce/PaymentGateway/StripeInterface.php
index d342fe3..5341cf3 100755
--- a/src/Plugin/Commerce/PaymentGateway/StripeInterface.php
+++ b/src/Plugin/Commerce/PaymentGateway/StripeInterface.php
@@ -25,12 +25,15 @@ interface StripeInterface extends OnsitePaymentGatewayInterface, SupportsAuthori
    *
    * @param \Drupal\commerce_order\Entity\OrderInterface $order
    *   The order.
-   * @param bool $capture
-   *   Whether the created payment intent capture is automatic or manual.
+   * @param array $intent_params
+   *   (optional) Parameters to create the intent with.
+   * @param \Drupal\commerce_payment\Entity\PaymentInterface|NULL $payment
+   *   (optional) The payment. Useful when creating a new payment.
+   *   If null, the order payment gateway will be used.
    *
    * @return \Stripe\PaymentIntent
    *   The payment intent.
    */
-  public function createPaymentIntent(OrderInterface $order, $capture = TRUE);
+  public function createPaymentIntent(OrderInterface $order, array $intent_params = [], $payment = NULL);
 
 }
diff --git a/tests/src/Kernel/CreatePaymentTest.php b/tests/src/Kernel/CreatePaymentTest.php
index 62e0624..3e52fab 100644
--- a/tests/src/Kernel/CreatePaymentTest.php
+++ b/tests/src/Kernel/CreatePaymentTest.php
@@ -70,7 +70,7 @@ class CreatePaymentTest extends StripeIntegrationTestBase {
       'order_id' => $order->id(),
     ]);
 
-    $intent = $plugin->createPaymentIntent($order, $capture);
+    $intent = $plugin->createPaymentIntent($order, ['capture_method' => $capture ? 'automatic' : 'manual']);
     // Programmatically confirm the intent, the customer would be performing
     // this action on the client side.
     $intent->confirm();
diff --git a/tests/src/Kernel/PaymentIntentTest.php b/tests/src/Kernel/PaymentIntentTest.php
index 2968523..199f306 100644
--- a/tests/src/Kernel/PaymentIntentTest.php
+++ b/tests/src/Kernel/PaymentIntentTest.php
@@ -6,6 +6,7 @@ use Drupal\commerce_order\Adjustment;
 use Drupal\commerce_order\Entity\Order;
 use Drupal\commerce_order\Entity\OrderInterface;
 use Drupal\commerce_order\Entity\OrderItem;
+use Drupal\commerce_payment\Entity\PaymentInterface;
 use Drupal\commerce_payment\Entity\PaymentMethod;
 use Drupal\commerce_payment\Entity\PaymentMethodInterface;
 use Drupal\commerce_price\Price;
@@ -35,7 +36,21 @@ class PaymentIntentTest extends StripeIntegrationTestBase {
    *
    * @dataProvider dataProviderCreatePaymentIntent
    */
-  public function testCreatePaymentIntent($payment_method_token, $capture, $initial_status, $confirmed_status) {
+  public function testCreatePaymentIntent($payment_method_token, $capture, $initial_status, $confirmed_status, $payment_received) {
+    $passed_payment = NULL;
+    $payment_passed_id = 'pm_card_visa';
+    if ($payment_received) {
+      $passed_payment = $this->prophesize(PaymentInterface::class);
+      $passed_payment_method = $this->prophesize(PaymentMethodInterface::class);
+
+      $passed_payment_method->getRemoteId()->willReturn($payment_passed_id);
+      $passed_payment->getPaymentMethod()->willReturn($passed_payment_method->reveal());
+      $passed_payment = $passed_payment->reveal();
+    }
+    // As we are really calling the API, we need a hack to assert if it was
+    // called with the right params. Let's abuse metadata for that.
+    $metadata = ['used_payment_method_remote_id' => $payment_received ? $payment_passed_id : $payment_method_token];
+
     $gateway = $this->generateGateway();
     $plugin = $gateway->getPlugin();
     assert($plugin instanceof StripeInterface);
@@ -53,11 +68,17 @@ class PaymentIntentTest extends StripeIntegrationTestBase {
     $order->setData('stripe_intent', Argument::containingString('pi_'))->willReturn($order->reveal());
     $order->save()->willReturn(NULL);
 
-    $intent = $plugin->createPaymentIntent($order->reveal(), $capture);
+    $intent = $plugin->createPaymentIntent($order->reveal(), ['capture_method' => $capture ? 'automatic' : 'manual', 'metadata' => $metadata], $passed_payment);
     $this->assertEquals($capture ? 'automatic' : 'manual', $intent->capture_method);
     $this->assertEquals($initial_status, $intent->status);
     $this->assertEquals($intent->currency, 'usd');
     $this->assertEquals($intent->amount, 1500);
+    if ($payment_received) {
+      $this->assertEquals($intent->metadata->used_payment_method_remote_id, $payment_passed_id);
+    }
+    else {
+      $this->assertEquals($intent->metadata->used_payment_method_remote_id, $payment_method_token);
+    }
 
     $intent = $intent->confirm();
     $this->assertEquals($confirmed_status, $intent->status);
@@ -202,14 +223,16 @@ class PaymentIntentTest extends StripeIntegrationTestBase {
    */
   public function dataProviderCreatePaymentIntent() {
     // 3DS 2 authentication must be completed for the payment to be successful.
-    yield ['pm_card_threeDSecure2Required', TRUE, PaymentIntent::STATUS_REQUIRES_CONFIRMATION, PaymentIntent::STATUS_REQUIRES_ACTION];
-    yield ['pm_card_threeDSecure2Required', FALSE, PaymentIntent::STATUS_REQUIRES_CONFIRMATION, PaymentIntent::STATUS_REQUIRES_ACTION];
+    yield ['pm_card_threeDSecure2Required', TRUE, PaymentIntent::STATUS_REQUIRES_CONFIRMATION, PaymentIntent::STATUS_REQUIRES_ACTION, FALSE];
+    yield ['pm_card_threeDSecure2Required', FALSE, PaymentIntent::STATUS_REQUIRES_CONFIRMATION, PaymentIntent::STATUS_REQUIRES_ACTION, FALSE];
     // 3DS authentication may still be performed, but is not required.
-    yield ['pm_card_threeDSecureOptional', TRUE, PaymentIntent::STATUS_REQUIRES_CONFIRMATION, PaymentIntent::STATUS_SUCCEEDED];
+    yield ['pm_card_threeDSecureOptional', TRUE, PaymentIntent::STATUS_REQUIRES_CONFIRMATION, PaymentIntent::STATUS_SUCCEEDED, FALSE];
     // 3DS is supported for this card, but this card is not enrolled in 3D Secure
-    yield ['pm_card_visa', TRUE, PaymentIntent::STATUS_REQUIRES_CONFIRMATION, PaymentIntent::STATUS_SUCCEEDED];
+    yield ['pm_card_visa', TRUE, PaymentIntent::STATUS_REQUIRES_CONFIRMATION, PaymentIntent::STATUS_SUCCEEDED, FALSE];
     // 3DS is not supported on this card and cannot be invoked.
-    yield ['pm_card_amex_threeDSecureNotSupported', TRUE, PaymentIntent::STATUS_REQUIRES_CONFIRMATION, PaymentIntent::STATUS_SUCCEEDED];
+    yield ['pm_card_amex_threeDSecureNotSupported', TRUE, PaymentIntent::STATUS_REQUIRES_CONFIRMATION, PaymentIntent::STATUS_SUCCEEDED, FALSE];
+    // A payment was passed, so we use that payment gateway token instead.
+    yield ['pm_card_amex_threeDSecureNotSupported', TRUE, PaymentIntent::STATUS_REQUIRES_CONFIRMATION, PaymentIntent::STATUS_SUCCEEDED, TRUE];
   }
 
   /**
