diff --git a/CRM/Core/Payment/Stripe.php b/CRM/Core/Payment/Stripe.php
index 86bc898a849aa1915213c27ca2c27dd24cd36c1c..27eaba15e5f4c5936ebb985ce65c119d572e5297 100644
--- a/CRM/Core/Payment/Stripe.php
+++ b/CRM/Core/Payment/Stripe.php
@@ -560,6 +560,21 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
       $this->handleError($e->getCode(), $e->getMessage(), $params['stripe_error_url']);
     }
 
+    // Update the paymentIntent in the CiviCRM database for later tracking
+    $intentParams = [
+      'paymentintent_id' => $intent->id,
+      'payment_processor_id' => $this->_paymentProcessor['id'],
+      'status' => $intent->status,
+      'contribution_id' => $this->getContributionId($params),
+      'description' => $intentParams['description'],
+      'identifier' => $params['qfKey'],
+      'contact_id' => $this->getContactId($params),
+    ];
+    if (empty($intentParams['contribution_id'])) {
+      $intentParams['flags'][] = 'NC';
+    }
+    CRM_Stripe_BAO_StripePaymentintent::create($intentParams);
+
     // For contribution workflow we have a contributionId so we can set parameters directly.
     // For events/membership workflow we have to return the parameters and they might get set...
     // For a single charge there is no stripe invoice.
diff --git a/CRM/Stripe/AJAX.php b/CRM/Stripe/AJAX.php
index 2e5b701ff1d15904e655c2637e16f432e2f9ff56..eda4e00aab3984951f1b92574d94321783ee7ec0 100644
--- a/CRM/Stripe/AJAX.php
+++ b/CRM/Stripe/AJAX.php
@@ -43,6 +43,7 @@ class CRM_Stripe_AJAX {
     $paymentIntentID = CRM_Utils_Request::retrieveValue('payment_intent_id', 'String');
     $amount = CRM_Utils_Request::retrieveValue('amount', 'Money');
     $capture = CRM_Utils_Request::retrieveValue('capture', 'Boolean', FALSE);
+    $title = CRM_Utils_Request::retrieveValue('description', 'String');
     $confirm = TRUE;
     if (empty($amount)) {
       $amount = 1;
@@ -59,10 +60,7 @@ class CRM_Stripe_AJAX {
       if ($intent->status === 'requires_confirmation') {
         $intent->confirm();
       }
-      if ($intent->status === 'requires_action') {
-        self::generatePaymentResponse($intent);
-      }
-      if ($capture) {
+      if ($capture && $intent->status === 'requires_capture') {
         $intent->capture();
       }
     }
@@ -86,6 +84,15 @@ class CRM_Stripe_AJAX {
       }
     }
 
+    // Save the generated paymentIntent in the CiviCRM database for later tracking
+    $intentParams = [
+      'paymentintent_id' => $intent->id,
+      'payment_processor_id' => $processorID,
+      'status' => $intent->status,
+      'description' => $title,
+    ];
+    CRM_Stripe_BAO_StripePaymentintent::create($intentParams);
+
     self::generatePaymentResponse($intent);
   }
 
diff --git a/CRM/Stripe/BAO/StripePaymentintent.php b/CRM/Stripe/BAO/StripePaymentintent.php
new file mode 100644
index 0000000000000000000000000000000000000000..192ab5b9bd54313af3edb4d35f82b7ace521a07b
--- /dev/null
+++ b/CRM/Stripe/BAO/StripePaymentintent.php
@@ -0,0 +1,81 @@
+<?php
+use CRM_Stripe_ExtensionUtil as E;
+
+class CRM_Stripe_BAO_StripePaymentintent extends CRM_Stripe_DAO_StripePaymentintent {
+
+  public static function getEntityName() {
+    return 'StripePaymentintent';
+  }
+  /**
+   * Create a new StripePaymentintent based on array-data
+   *
+   * @param array $params key-value pairs
+   * @return CRM_Stripe_DAO_StripePaymentintent|NULL
+   *
+  public static function create($params) {
+    $className = 'CRM_Stripe_DAO_StripePaymentintent';
+    $entityName = 'StripePaymentintent';
+    $hook = empty($params['id']) ? 'create' : 'edit';
+
+    CRM_Utils_Hook::pre($hook, $entityName, CRM_Utils_Array::value('id', $params), $params);
+    $instance = new $className();
+    $instance->copyValues($params);
+    $instance->save();
+    CRM_Utils_Hook::post($hook, $entityName, $instance->id, $instance);
+
+    return $instance;
+  } */
+
+  public static function test() {
+  }
+
+  /**
+   * Create a new StripePaymentintent based on array-data
+   *
+   * @param array $params key-value pairs
+   *
+   * @return \CRM_Stripe_BAO_StripePaymentintent
+   */
+  public static function create($params) {
+    $instance = new self;
+    try {
+      if ($params['id']) {
+        $instance->id = $params['id'];
+      }
+      elseif ($params['paymentintent_id']) {
+        $instance->id = civicrm_api3('StripePaymentintent', 'getvalue', [
+          'return' => "id",
+          'paymentintent_id' => $params['paymentintent_id'],
+        ]);
+      }
+      if ($instance->id) {
+        if ($instance->find()) {
+          $instance->fetch();
+        }
+      }
+    }
+    catch (Exception $e) {
+      // do nothing, we're creating a new one
+    }
+
+    $flags = empty($instance->flags) ? [] : unserialize($instance->flags);
+    if (!empty($params['flags']) && is_array($params['flags'])) {
+      foreach ($params['flags'] as $flag) {
+        if (!in_array($flag, $flags)) {
+          $flags[] = 'NC';
+        }
+      }
+      unset($params['flags']);
+    }
+    $instance->flags = serialize($flags);
+
+    $hook = empty($instance->id) ? 'create' : 'edit';
+    CRM_Utils_Hook::pre($hook, self::getEntityName(), CRM_Utils_Array::value('id', $params), $params);
+    $instance->copyValues($params);
+    $instance->save();
+
+    CRM_Utils_Hook::post($hook, self::getEntityName(), $instance->id, $instance);
+
+    return $instance;
+  }
+}
diff --git a/CRM/Stripe/Customer.php b/CRM/Stripe/Customer.php
index bf3002506e27ae1fd24c7acf8b4ad91b0ef76759..f9f49b9de50a79c275d8dd49f721d0e6c3f2cb1b 100644
--- a/CRM/Stripe/Customer.php
+++ b/CRM/Stripe/Customer.php
@@ -109,6 +109,7 @@ class CRM_Stripe_Customer {
       2 => [$params['customer_id'], 'String'],
       3 => [$params['processor_id'], 'Integer'],
     ];
+
     CRM_Core_DAO::executeQuery("INSERT INTO civicrm_stripe_customers
           (contact_id, id, processor_id) VALUES (%1, %2, %3)", $queryParams);
   }
diff --git a/CRM/Stripe/DAO/StripePaymentintent.php b/CRM/Stripe/DAO/StripePaymentintent.php
new file mode 100644
index 0000000000000000000000000000000000000000..b59cbc42715263ac1854bb950b0b67dab94040ad
--- /dev/null
+++ b/CRM/Stripe/DAO/StripePaymentintent.php
@@ -0,0 +1,345 @@
+<?php
+
+/**
+ * @package CRM
+ * @copyright CiviCRM LLC (c) 2004-2019
+ *
+ * Generated from /home/dev/civicrm/civicrm-buildkit/build/dmaster/sites/default/files/civicrm/ext/civicrm-stripe/xml/schema/CRM/Stripe/StripePaymentintent.xml
+ * DO NOT EDIT.  Generated by CRM_Core_CodeGen
+ * (GenCodeChecksum:9eb72d282f516624404a0f9cf806f534)
+ */
+
+/**
+ * Database access object for the StripePaymentintent entity.
+ */
+class CRM_Stripe_DAO_StripePaymentintent extends CRM_Core_DAO {
+
+  /**
+   * Static instance to hold the table name.
+   *
+   * @var string
+   */
+  public static $_tableName = 'civicrm_stripe_paymentintent';
+
+  /**
+   * Should CiviCRM log any modifications to this table in the civicrm_log table.
+   *
+   * @var bool
+   */
+  public static $_log = TRUE;
+
+  /**
+   * Unique ID
+   *
+   * @var int
+   */
+  public $id;
+
+  /**
+   * The PaymentIntent ID
+   *
+   * @var string
+   */
+  public $paymentintent_id;
+
+  /**
+   * FK ID from civicrm_contribution
+   *
+   * @var int
+   */
+  public $contribution_id;
+
+  /**
+   * Foreign key to civicrm_payment_processor.id
+   *
+   * @var int
+   */
+  public $payment_processor_id;
+
+  /**
+   * Description of this paymentIntent
+   *
+   * @var string
+   */
+  public $description;
+
+  /**
+   * The status of the paymentIntent
+   *
+   * @var string
+   */
+  public $status;
+
+  /**
+   * An identifier that we can use in CiviCRM to find the paymentIntent if we do not have the ID (eg. session key)
+   *
+   * @var string
+   */
+  public $identifier;
+
+  /**
+   * FK to Contact
+   *
+   * @var int
+   */
+  public $contact_id;
+
+  /**
+   * When was paymentIntent created
+   *
+   * @var timestamp
+   */
+  public $created_date;
+
+  /**
+   * Flags associated with this PaymentIntent (NC=no contributionID when doPayment called)
+   *
+   * @var string
+   */
+  public $flags;
+
+  /**
+   * Class constructor.
+   */
+  public function __construct() {
+    $this->__table = 'civicrm_stripe_paymentintent';
+    parent::__construct();
+  }
+
+  /**
+   * Returns foreign keys and entity references.
+   *
+   * @return array
+   *   [CRM_Core_Reference_Interface]
+   */
+  public static function getReferenceColumns() {
+    if (!isset(Civi::$statics[__CLASS__]['links'])) {
+      Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__);
+      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'payment_processor_id', 'civicrm_payment_processor', 'id');
+      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id', 'civicrm_contact', 'id');
+      CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']);
+    }
+    return Civi::$statics[__CLASS__]['links'];
+  }
+
+  /**
+   * Returns all the column names of this table
+   *
+   * @return array
+   */
+  public static function &fields() {
+    if (!isset(Civi::$statics[__CLASS__]['fields'])) {
+      Civi::$statics[__CLASS__]['fields'] = [
+        'id' => [
+          'name' => 'id',
+          'type' => CRM_Utils_Type::T_INT,
+          'description' => CRM_Stripe_ExtensionUtil::ts('Unique ID'),
+          'required' => TRUE,
+          'where' => 'civicrm_stripe_paymentintent.id',
+          'table_name' => 'civicrm_stripe_paymentintent',
+          'entity' => 'StripePaymentintent',
+          'bao' => 'CRM_Stripe_DAO_StripePaymentintent',
+          'localizable' => 0,
+        ],
+        'paymentintent_id' => [
+          'name' => 'paymentintent_id',
+          'type' => CRM_Utils_Type::T_STRING,
+          'title' => CRM_Stripe_ExtensionUtil::ts('PaymentIntent ID'),
+          'description' => CRM_Stripe_ExtensionUtil::ts('The PaymentIntent ID'),
+          'maxlength' => 255,
+          'size' => CRM_Utils_Type::HUGE,
+          'where' => 'civicrm_stripe_paymentintent.paymentintent_id',
+          'table_name' => 'civicrm_stripe_paymentintent',
+          'entity' => 'StripePaymentintent',
+          'bao' => 'CRM_Stripe_DAO_StripePaymentintent',
+          'localizable' => 0,
+        ],
+        'contribution_id' => [
+          'name' => 'contribution_id',
+          'type' => CRM_Utils_Type::T_INT,
+          'title' => CRM_Stripe_ExtensionUtil::ts('Contribution ID'),
+          'description' => CRM_Stripe_ExtensionUtil::ts('FK ID from civicrm_contribution'),
+          'where' => 'civicrm_stripe_paymentintent.contribution_id',
+          'table_name' => 'civicrm_stripe_paymentintent',
+          'entity' => 'StripePaymentintent',
+          'bao' => 'CRM_Stripe_DAO_StripePaymentintent',
+          'localizable' => 0,
+        ],
+        'payment_processor_id' => [
+          'name' => 'payment_processor_id',
+          'type' => CRM_Utils_Type::T_INT,
+          'title' => CRM_Stripe_ExtensionUtil::ts('Payment Processor'),
+          'description' => CRM_Stripe_ExtensionUtil::ts('Foreign key to civicrm_payment_processor.id'),
+          'where' => 'civicrm_stripe_paymentintent.payment_processor_id',
+          'table_name' => 'civicrm_stripe_paymentintent',
+          'entity' => 'StripePaymentintent',
+          'bao' => 'CRM_Stripe_DAO_StripePaymentintent',
+          'localizable' => 0,
+          'pseudoconstant' => [
+            'table' => 'civicrm_payment_processor',
+            'keyColumn' => 'id',
+            'labelColumn' => 'name',
+          ],
+        ],
+        'description' => [
+          'name' => 'description',
+          'type' => CRM_Utils_Type::T_STRING,
+          'title' => CRM_Stripe_ExtensionUtil::ts('Description'),
+          'description' => CRM_Stripe_ExtensionUtil::ts('Description of this paymentIntent'),
+          'required' => FALSE,
+          'maxlength' => 255,
+          'size' => CRM_Utils_Type::HUGE,
+          'where' => 'civicrm_stripe_paymentintent.description',
+          'table_name' => 'civicrm_stripe_paymentintent',
+          'entity' => 'StripePaymentintent',
+          'bao' => 'CRM_Stripe_DAO_StripePaymentintent',
+          'localizable' => 0,
+        ],
+        'status' => [
+          'name' => 'status',
+          'type' => CRM_Utils_Type::T_STRING,
+          'title' => CRM_Stripe_ExtensionUtil::ts('Status'),
+          'description' => CRM_Stripe_ExtensionUtil::ts('The status of the paymentIntent'),
+          'required' => FALSE,
+          'maxlength' => 25,
+          'size' => CRM_Utils_Type::MEDIUM,
+          'where' => 'civicrm_stripe_paymentintent.status',
+          'table_name' => 'civicrm_stripe_paymentintent',
+          'entity' => 'StripePaymentintent',
+          'bao' => 'CRM_Stripe_DAO_StripePaymentintent',
+          'localizable' => 0,
+        ],
+        'identifier' => [
+          'name' => 'identifier',
+          'type' => CRM_Utils_Type::T_STRING,
+          'title' => CRM_Stripe_ExtensionUtil::ts('Identifier'),
+          'description' => CRM_Stripe_ExtensionUtil::ts('An identifier that we can use in CiviCRM to find the paymentIntent if we do not have the ID (eg. session key)'),
+          'required' => FALSE,
+          'maxlength' => 255,
+          'size' => CRM_Utils_Type::HUGE,
+          'where' => 'civicrm_stripe_paymentintent.identifier',
+          'table_name' => 'civicrm_stripe_paymentintent',
+          'entity' => 'StripePaymentintent',
+          'bao' => 'CRM_Stripe_DAO_StripePaymentintent',
+          'localizable' => 0,
+        ],
+        'contact_id' => [
+          'name' => 'contact_id',
+          'type' => CRM_Utils_Type::T_INT,
+          'description' => CRM_Stripe_ExtensionUtil::ts('FK to Contact'),
+          'where' => 'civicrm_stripe_paymentintent.contact_id',
+          'table_name' => 'civicrm_stripe_paymentintent',
+          'entity' => 'StripePaymentintent',
+          'bao' => 'CRM_Stripe_DAO_StripePaymentintent',
+          'localizable' => 0,
+        ],
+        'created_date' => [
+          'name' => 'created_date',
+          'type' => CRM_Utils_Type::T_TIMESTAMP,
+          'title' => CRM_Stripe_ExtensionUtil::ts('Created Date'),
+          'description' => CRM_Stripe_ExtensionUtil::ts('When was paymentIntent created'),
+          'where' => 'civicrm_stripe_paymentintent.created_date',
+          'default' => 'CURRENT_TIMESTAMP',
+          'table_name' => 'civicrm_stripe_paymentintent',
+          'entity' => 'StripePaymentintent',
+          'bao' => 'CRM_Stripe_DAO_StripePaymentintent',
+          'localizable' => 0,
+        ],
+        'flags' => [
+          'name' => 'flags',
+          'type' => CRM_Utils_Type::T_STRING,
+          'title' => CRM_Stripe_ExtensionUtil::ts('Flags'),
+          'description' => CRM_Stripe_ExtensionUtil::ts('Flags associated with this PaymentIntent (NC=no contributionID when doPayment called)'),
+          'required' => FALSE,
+          'maxlength' => 100,
+          'size' => CRM_Utils_Type::HUGE,
+          'where' => 'civicrm_stripe_paymentintent.flags',
+          'table_name' => 'civicrm_stripe_paymentintent',
+          'entity' => 'StripePaymentintent',
+          'bao' => 'CRM_Stripe_DAO_StripePaymentintent',
+          'localizable' => 0,
+        ],
+      ];
+      CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']);
+    }
+    return Civi::$statics[__CLASS__]['fields'];
+  }
+
+  /**
+   * Return a mapping from field-name to the corresponding key (as used in fields()).
+   *
+   * @return array
+   *   Array(string $name => string $uniqueName).
+   */
+  public static function &fieldKeys() {
+    if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) {
+      Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields()));
+    }
+    return Civi::$statics[__CLASS__]['fieldKeys'];
+  }
+
+  /**
+   * Returns the names of this table
+   *
+   * @return string
+   */
+  public static function getTableName() {
+    return self::$_tableName;
+  }
+
+  /**
+   * Returns if this table needs to be logged
+   *
+   * @return bool
+   */
+  public function getLog() {
+    return self::$_log;
+  }
+
+  /**
+   * Returns the list of fields that can be imported
+   *
+   * @param bool $prefix
+   *
+   * @return array
+   */
+  public static function &import($prefix = FALSE) {
+    $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'stripe_paymentintent', $prefix, []);
+    return $r;
+  }
+
+  /**
+   * Returns the list of fields that can be exported
+   *
+   * @param bool $prefix
+   *
+   * @return array
+   */
+  public static function &export($prefix = FALSE) {
+    $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'stripe_paymentintent', $prefix, []);
+    return $r;
+  }
+
+  /**
+   * Returns the list of indices
+   *
+   * @param bool $localize
+   *
+   * @return array
+   */
+  public static function indices($localize = TRUE) {
+    $indices = [
+      'UI_paymentintent_id' => [
+        'name' => 'UI_paymentintent_id',
+        'field' => [
+          0 => 'paymentintent_id',
+        ],
+        'localizable' => FALSE,
+        'unique' => TRUE,
+        'sig' => 'civicrm_stripe_paymentintent::1::paymentintent_id',
+      ],
+    ];
+    return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices;
+  }
+
+}
diff --git a/CRM/Stripe/PaymentIntent.php b/CRM/Stripe/PaymentIntent.php
new file mode 100644
index 0000000000000000000000000000000000000000..9a8f2da4c3f5b41dca302c3b94b224a3486e8106
--- /dev/null
+++ b/CRM/Stripe/PaymentIntent.php
@@ -0,0 +1,171 @@
+<?php
+/**
+ * https://civicrm.org/licensing
+ */
+
+/**
+ * Manage the civicrm_stripe_paymentintent database table which records all created paymentintents
+ * Class CRM_Stripe_PaymentIntent
+ */
+class CRM_Stripe_PaymentIntent {
+
+  /**
+   * Add a paymentIntent to the database
+   *
+   * @param $params
+   *
+   * @throws \Civi\Payment\Exception\PaymentProcessorException
+   */
+  public static function add($params) {
+    $requiredParams = ['id', 'payment_processor_id'];
+    foreach ($requiredParams as $required) {
+      if (empty($params[$required])) {
+        throw new \Civi\Payment\Exception\PaymentProcessorException('Stripe PaymentIntent (add): Missing required parameter: ' . $required);
+      }
+    }
+
+    $count = 0;
+    foreach ($params as $key => $value) {
+      switch ($key) {
+        case 'id':
+          $queryParams[] = [$value, 'String'];
+          break;
+
+        case 'payment_processor_id':
+          $queryParams[] = [$value, 'Integer'];
+          break;
+
+        case 'contribution_id':
+          if (empty($value)) {
+            continue 2;
+          }
+          $queryParams[] = [$value, 'Integer'];
+          break;
+
+        case 'description':
+          $queryParams[] = [$value, 'String'];
+          break;
+
+        case 'status':
+          $queryParams[] = [$value, 'String'];
+          break;
+
+        case 'identifier':
+          $queryParams[] = [$value, 'String'];
+          break;
+      }
+      $keys[] = $key;
+      $update[] = "{$key} = '{$value}'";
+      $values[] = "%{$count}";
+      $count++;
+    }
+
+    $query = "INSERT INTO civicrm_stripe_paymentintent
+          (" . implode(',', $keys) . ") VALUES (" . implode(',', $values) . ")";
+    $query .= " ON DUPLICATE KEY UPDATE " . implode(',', $update);
+    CRM_Core_DAO::executeQuery($query, $queryParams);
+  }
+
+  /**
+   * @param array $params
+   *
+   * @throws \Civi\Payment\Exception\PaymentProcessorException
+   */
+  public static function create($params) {
+    self::add($params);
+  }
+
+  /**
+   * Delete a Stripe paymentintent from the CiviCRM database
+   *
+   * @param array $params
+   *
+   * @throws \Civi\Payment\Exception\PaymentProcessorException
+   */
+  public static function delete($params) {
+    $requiredParams = ['id'];
+    foreach ($requiredParams as $required) {
+      if (empty($params[$required])) {
+        throw new \Civi\Payment\Exception\PaymentProcessorException('Stripe PaymentIntent (delete): Missing required parameter: ' . $required);
+      }
+    }
+
+    $queryParams = [
+      1 => [$params['id'], 'String'],
+    ];
+    $sql = "DELETE FROM civicrm_stripe_paymentintent WHERE id = %1";
+    CRM_Core_DAO::executeQuery($sql, $queryParams);
+  }
+
+  /**
+   * @param array $params
+   * @param \CRM_Core_Payment_Stripe $stripe
+   *
+   * @throws \Civi\Payment\Exception\PaymentProcessorException
+   */
+  public static function stripeCancel($params, $stripe) {
+    $requiredParams = ['id'];
+    foreach ($requiredParams as $required) {
+      if (empty($params[$required])) {
+        throw new \Civi\Payment\Exception\PaymentProcessorException('Stripe PaymentIntent (getFromStripe): Missing required parameter: ' . $required);
+      }
+    }
+
+    $stripe->setAPIParams();
+
+    $intent = \Stripe\PaymentIntent::retrieve($params['id']);
+    $intent->cancel();
+  }
+
+  /**
+   * @param array $params
+   * @param \CRM_Core_Payment_Stripe $stripe
+   *
+   * @throws \Civi\Payment\Exception\PaymentProcessorException
+   */
+  public static function stripeGet($params, $stripe) {
+    $requiredParams = ['id'];
+    foreach ($requiredParams as $required) {
+      if (empty($params[$required])) {
+        throw new \Civi\Payment\Exception\PaymentProcessorException('Stripe PaymentIntent (getFromStripe): Missing required parameter: ' . $required);
+      }
+    }
+
+    $stripe->setAPIParams();
+
+    $intent = \Stripe\PaymentIntent::retrieve($params['id']);
+    $paymentIntent = self::get($params);
+    $params['status'] = $intent->status;
+    self::add($params);
+  }
+
+  /**
+   * Get an existing Stripe paymentIntent from the CiviCRM database
+   *
+   * @param $params
+   *
+   * @return array
+   * @throws \Civi\Payment\Exception\PaymentProcessorException
+   */
+  public static function get($params) {
+    $requiredParams = ['id'];
+    foreach ($requiredParams as $required) {
+      if (empty($params[$required])) {
+        throw new \Civi\Payment\Exception\PaymentProcessorException('Stripe PaymentIntent (get): Missing required parameter: ' . $required);
+      }
+    }
+    if (empty($params['contact_id'])) {
+      throw new \Civi\Payment\Exception\PaymentProcessorException('Stripe PaymentIntent (get): contact_id is required');
+    }
+    $queryParams = [
+      1 => [$params['id'], 'String'],
+    ];
+
+    $dao = CRM_Core_DAO::executeQuery("SELECT *
+      FROM civicrm_stripe_paymentintent
+      WHERE id = %1", $queryParams);
+
+    return $dao->toArray();
+  }
+
+}
diff --git a/api/v3/Job/ProcessStripe.mgd.php b/api/v3/Job/ProcessStripe.mgd.php
new file mode 100644
index 0000000000000000000000000000000000000000..402c79ca3a9267839343da3d85cbc225e955033d
--- /dev/null
+++ b/api/v3/Job/ProcessStripe.mgd.php
@@ -0,0 +1,20 @@
+<?php
+
+return [
+  0 =>
+  [
+    'name' => 'ProcessStripe',
+    'entity' => 'Job',
+    'params' =>
+    [
+      'version' => 3,
+      'name' => 'ProcessStripe',
+      'description' => 'Process Stripe functions',
+      'run_frequency' => 'Hourly',
+      'api_entity' => 'Job',
+      'api_action' => 'process_stripe',
+      'parameters' => 'delete_old=-3 month
+cancel_incomplete=-1 day',
+    ],
+  ],
+];
diff --git a/api/v3/Job/ProcessStripe.php b/api/v3/Job/ProcessStripe.php
new file mode 100644
index 0000000000000000000000000000000000000000..44214953b4e6a11302a91852f73bc669a655c015
--- /dev/null
+++ b/api/v3/Job/ProcessStripe.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * This job performs various housekeeping actions related to the Stripe payment processor
+ *
+ * @param array $params
+ *
+ * @return array
+ *   API result array.
+ * @throws CiviCRM_API3_Exception
+ */
+function civicrm_api3_job_process_stripe($params) {
+  $results = [];
+
+  if ($params['delete_old'] !== 0 && !empty($params['delete_old'])) {
+    // Delete all locally recorded paymentIntents that are older than 3 months
+    $oldPaymentIntents = civicrm_api3('StripePaymentintent', 'get', [
+      'status' => ['IN' => ["succeeded", "cancelled"]],
+      'created_date' => ['<' => $params['delete_old']],
+    ]);
+    foreach ($oldPaymentIntents['values'] as $id => $detail) {
+      civicrm_api3('StripePaymentintent', 'delete', ['id' => $id]);
+      $results['deleted'][$id] = $detail['paymentintent_id'];
+    }
+  }
+
+  if ($params['cancel_incomplete'] !== 0 && !empty($params['cancel_incomplete'])) {
+    // Cancel incomplete paymentIntents after 1 day
+    $incompletePaymentIntents = civicrm_api3('StripePaymentintent', 'get', [
+      'status' => ['NOT IN' => ["succeeded", "cancelled"]],
+      'created_date' => ['<' => $params['cancel_incomplete']],
+    ]);
+    foreach ($incompletePaymentIntents['values'] as $id => $detail) {
+      try {
+        /** @var \CRM_Core_Payment_Stripe $paymentProcessor */
+        $paymentProcessor = Civi\Payment\System::singleton()
+          ->getById($detail['payment_processor_id']);
+        $paymentProcessor->setAPIParams();
+        $intent = \Stripe\PaymentIntent::retrieve($detail['paymentintent_id']);
+        $intent->cancel(['cancellation_reason' => 'abandoned']);
+      } catch (Exception $e) {
+      }
+      civicrm_api3('StripePaymentintent', 'create', [
+        'id' => $id,
+        'status' => 'cancelled'
+      ]);
+      $results['cancelled'][$id] = $detail['paymentintent_id'];
+    }
+  }
+
+  return civicrm_api3_create_success($results, $params);
+}
+
+/**
+ * Action Payment.
+ *
+ * @param array $params
+ *
+ * @return array
+ */
+function _civicrm_api3_job_process_stripe_spec(&$params) {
+  $params['delete_old']['api.default'] = '-3 month';
+  $params['delete_old']['title'] = 'Delete old records after (default: -3 month)';
+  $params['delete_old']['description'] = 'Delete old records from database. Specify 0 to disable. Default is "-3 month"';
+  $params['cancel_incomplete']['api.default'] = '-1 day';
+  $params['cancel_incomplete']['title'] = 'Cancel incomplete records after (default: -1 day)';
+  $params['cancel_incomplete']['description'] = 'Cancel incomplete paymentIntents in your stripe account. Specify 0 to disable. Default is "-1 day"';
+}
diff --git a/api/v3/StripePaymentintent.php b/api/v3/StripePaymentintent.php
new file mode 100644
index 0000000000000000000000000000000000000000..18e19aa3bb5a3773e0c06845d0e6e809daadadd4
--- /dev/null
+++ b/api/v3/StripePaymentintent.php
@@ -0,0 +1,47 @@
+<?php
+use CRM_Stripe_ExtensionUtil as E;
+
+/**
+ * StripePaymentintent.create API specification (optional)
+ * This is used for documentation and validation.
+ *
+ * @param array $spec description of fields supported by this API call
+ * @return void
+ * @see http://wiki.civicrm.org/confluence/display/CRMDOC/API+Architecture+Standards
+ */
+function _civicrm_api3_stripe_paymentintent_create_spec(&$spec) {
+  // $spec['some_parameter']['api.required'] = 1;
+}
+
+/**
+ * StripePaymentintent.create API
+ *
+ * @param array $params
+ * @return array API result descriptor
+ * @throws API_Exception
+ */
+function civicrm_api3_stripe_paymentintent_create($params) {
+  return _civicrm_api3_basic_create(_civicrm_api3_get_BAO(__FUNCTION__), $params);
+}
+
+/**
+ * StripePaymentintent.delete API
+ *
+ * @param array $params
+ * @return array API result descriptor
+ * @throws API_Exception
+ */
+function civicrm_api3_stripe_paymentintent_delete($params) {
+  return _civicrm_api3_basic_delete(_civicrm_api3_get_BAO(__FUNCTION__), $params);
+}
+
+/**
+ * StripePaymentintent.get API
+ *
+ * @param array $params
+ * @return array API result descriptor
+ * @throws API_Exception
+ */
+function civicrm_api3_stripe_paymentintent_get($params) {
+  return _civicrm_api3_basic_get(_civicrm_api3_get_BAO(__FUNCTION__), $params);
+}
diff --git a/js/civicrmStripeConfirm.js b/js/civicrmStripeConfirm.js
index ffa09bdbb1a58f2316c43388ee1d6ece6b0f0635..a26b6edb03b8341726a351dcd575a056b120b179 100644
--- a/js/civicrmStripeConfirm.js
+++ b/js/civicrmStripeConfirm.js
@@ -1,8 +1,17 @@
 /**
- * JS Integration between CiviCRM & Stripe.
+ * This handles confirmation actions on the "Thankyou" pages for contribution/event workflows.
  */
 CRM.$(function($) {
-  debugging("civicrm_stripe loaded, dom-ready function firing.");
+  debugging("civicrmStripeConfirm loaded");
+
+  if (typeof CRM.vars.stripe === 'undefined') {
+    debugging('CRM.vars.stripe not defined! Not a Stripe processor?');
+    return;
+  }
+  if (CRM.vars.stripe.paymentIntentStatus === 'succeeded') {
+    debugging('already succeeded');
+    return;
+  }
 
   checkAndLoad();
 
@@ -62,11 +71,6 @@ CRM.$(function($) {
   }
 
   function checkAndLoad() {
-    if (typeof CRM.vars.stripe === 'undefined') {
-      debugging('CRM.vars.stripe not defined! Not a Stripe processor?');
-      return;
-    }
-
     if (typeof Stripe === 'undefined') {
       if (stripeLoading) {
         return;
diff --git a/js/civicrmStripeConfirm.min.js b/js/civicrmStripeConfirm.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..708b8a7c4f45dc20fa47023522266d1d9f305b1f
--- /dev/null
+++ b/js/civicrmStripeConfirm.min.js
@@ -0,0 +1 @@
+CRM.$(function(g){f("civicrmStripeConfirm loaded");if(typeof CRM.vars.stripe==="undefined"){f("CRM.vars.stripe not defined! Not a Stripe processor?");return}if(CRM.vars.stripe.paymentIntentStatus==="succeeded"){f("already succeeded");return}a();if(typeof h==="undefined"){h=Stripe(CRM.vars.stripe.publishableKey)}c();var h;var e=false;window.onbeforeunload=null;function d(i){f("handleServerResponse");if(i.error){}else{if(i.requires_action){b(i)}else{f("success - payment captured")}}}function b(i){h.handleCardAction(i.payment_intent_client_secret).then(function(j){if(j.error){c()}else{f("card action success");c()}})}function c(){f("handle card confirm");var i=CRM.url("civicrm/stripe/confirm-payment");g.post(i,{payment_intent_id:CRM.vars.stripe.paymentIntentID,capture:true,id:CRM.vars.stripe.id}).then(function(j){d(j)})}function a(){if(typeof Stripe==="undefined"){if(e){return}e=true;f("Stripe.js is not loaded!");g.getScript("https://js.stripe.com/v3",function(){f("Script loaded and executed.");e=false})}}function f(i){if((typeof(CRM.vars.stripe)==="undefined")||(Boolean(CRM.vars.stripe.jsDebug)===true)){console.log(new Date().toISOString()+" civicrm_stripe.js: "+i)}}});
\ No newline at end of file
diff --git a/js/civicrm_stripe.js b/js/civicrm_stripe.js
index 76fcb4d57238a6e6045e576fca6dfce46e8f3fb4..3d1e73919392ed3ec47a137630deafbcc79c6343 100644
--- a/js/civicrm_stripe.js
+++ b/js/civicrm_stripe.js
@@ -92,6 +92,7 @@ CRM.$(function($) {
             amount: getTotalAmount(),
             currency: CRM.vars.stripe.currency,
             id: CRM.vars.stripe.id,
+            description: document.title,
           }).then(function (result) {
             // Handle server response (see Step 3)
             handleServerResponse(result);
diff --git a/js/civicrm_stripe.min.js b/js/civicrm_stripe.min.js
index c53be3e27d89d9adff9fffca8d27e990a8a0d10c..0f94ba55731e2ce718e6c30d946bd43dfedbaccb 100644
--- a/js/civicrm_stripe.min.js
+++ b/js/civicrm_stripe.min.js
@@ -1 +1 @@
-CRM.$(function(d){f("civicrm_stripe loaded, dom-ready function firing.");if(window.civicrmStripeHandleReload){f("calling existing civicrmStripeHandleReload.");window.civicrmStripeHandleReload();return}var q;var b;var c;var a;var o=false;window.onbeforeunload=null;window.civicrmStripeHandleReload=function(){f("civicrmStripeHandleReload");var A=document.getElementById("card-element");if((typeof A!=="undefined")&&(A)){if(!A.children.length){f("checkAndLoad from document.ready");m()}}};window.civicrmStripeHandleReload();function v(C,A){f(C+": success - submitting form");var B=document.createElement("input");B.setAttribute("type","hidden");B.setAttribute("name",C);B.setAttribute("value",A.id);c.appendChild(B);c.submit()}function p(){a.setAttribute("disabled",true);return c.submit()}function h(A){f("error: "+A.error.message);var B=document.getElementById("card-errors");B.style.display="block";B.textContent=A.error.message;document.querySelector("#billing-payment-block").scrollIntoView();window.scrollBy(0,-50);c.dataset.submitted=false;a.removeAttribute("disabled")}function y(){f("handle card payment");q.createPaymentMethod("card",b).then(function(A){if(A.error){h(A)}else{if(g()===true){v("paymentMethodID",A.paymentMethod)}else{var B=CRM.url("civicrm/stripe/confirm-payment");d.post(B,{payment_method_id:A.paymentMethod.id,amount:r(),currency:CRM.vars.stripe.currency,id:CRM.vars.stripe.id}).then(function(C){w(C)})}}})}function w(A){f("handleServerResponse");if(A.error){h(A)}else{if(A.requires_action){s(A)}else{v("paymentIntentID",A.paymentIntent)}}}function s(A){q.handleCardAction(A.payment_intent_client_secret).then(function(B){if(B.error){h(B)}else{v("paymentIntentID",B.paymentIntent)}})}d(document).ajaxComplete(function(C,D,B){if((B.url.match("civicrm(/|%2F)payment(/|%2F)form")!==null)||(B.url.match("civicrm(/|%2F)contact(/|%2F)view(/|%2F)participant")!==null)){if(typeof CRM.vars.stripe==="undefined"){return}var A=k();if(A!==null){if(A!==parseInt(CRM.vars.stripe.id)){f("payment processor changed to id: "+A);if(A===0){return l()}CRM.api3("PaymentProcessor","getvalue",{"return":"user_name",id:A,payment_processor_type_id:CRM.vars.stripe.paymentProcessorTypeID}).done(function(E){var F=E.result;if(F){f("Setting new stripe key to: "+F);CRM.vars.stripe.publishableKey=F}else{return l()}f("checkAndLoad from ajaxComplete");m()})}}}});function l(){f("New payment processor is not Stripe, clearing CRM.vars.stripe");if((typeof b!=="undefined")&&(b)){f("destroying card element");b.destroy();b=undefined}delete (CRM.vars.stripe)}function m(){if(typeof CRM.vars.stripe==="undefined"){f("CRM.vars.stripe not defined! Not a Stripe processor?");return}if(typeof Stripe==="undefined"){if(o){return}o=true;f("Stripe.js is not loaded!");d.getScript("https://js.stripe.com/v3",function(){f("Script loaded and executed.");o=false;e()})}else{e()}}function e(){f("loadStripeBillingBlock");if(typeof q==="undefined"){q=Stripe(CRM.vars.stripe.publishableKey)}var G=q.elements();var D={base:{fontSize:"20px"}};b=G.create("card",{style:D});b.mount("#card-element");f("created new card element",b);document.getElementsByClassName("billing_postal_code-"+CRM.vars.stripe.billingAddressID+"-section")[0].setAttribute("hidden",true);b.addEventListener("change",function(H){u(H)});c=j();if(typeof c.length==="undefined"||c.length===0){f("No billing form!");return}a=z();c.dataset.submitdontprocess=false;var A=c.querySelectorAll('[type="submit"][formnovalidate="1"], [type="submit"][formnovalidate="formnovalidate"], [type="submit"].cancel, [type="submit"].webform-previous'),C;for(C=0;C<A.length;++C){A[C].addEventListener("click",F())}function F(){f("adding submitdontprocess");c.dataset.submitdontprocess=true}a.addEventListener("click",B);function B(H){if(c.dataset.submitted===true){return}c.dataset.submitted=true;if(typeof CRM.vars.stripe==="undefined"){return p()}f("clearing submitdontprocess");c.dataset.submitdontprocess=false;return E(H)}a.removeAttribute("onclick");n();if(x()){d("[type=submit]").click(function(){t(this.value)});c.addEventListener("keydown",function(H){if(H.keyCode===13){t(this.value);E(event)}});d("#billingcheckbox:input").hide();d('label[for="billingcheckbox"]').hide()}function E(J){J.preventDefault();f("submit handler");if(d(c).valid()===false){f("Form not valid");return false}if(typeof CRM.vars.stripe==="undefined"){f("Submitting - not a stripe processor");return true}if(c.dataset.submitted===true){f("form already submitted");return false}var L=parseInt(CRM.vars.stripe.id);var I=null;if(x()){if(!d('input[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]').length){I=L}else{I=parseInt(c.querySelector('input[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]:checked').value)}}else{if((c.querySelector(".crm-section.payment_processor-section")!==null)||(c.querySelector(".crm-section.credit_card_info-section")!==null)){L=CRM.vars.stripe.id;if(c.querySelector('input[name="payment_processor_id"]:checked')!==null){I=parseInt(c.querySelector('input[name="payment_processor_id"]:checked').value)}}}if((I===0)||(L===null)||((I===null)&&(L===null))){f("Not a Stripe transaction, or pay-later");return p()}else{f("Stripe is the selected payprocessor")}if(typeof CRM.vars.stripe.publishableKey==="undefined"){f("submit missing stripe-pub-key element or value");return true}if(c.dataset.submitdontprocess===true){f("non-payment submit detected - not submitting payment");return true}if(x()){if(d("#billing-payment-block").is(":hidden")){f("no payment processor on webform");return true}var K=d('[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]');if(K.length){if(K.filter(":checked").val()==="0"||K.filter(":checked").val()===0){f("no payment processor selected");return true}}}var H=r();if(H=="0"){f("Total amount is 0");return true}if(c.dataset.submitted===true){alert("Form already submitted. Please wait.");return false}else{c.dataset.submitted=true}a.setAttribute("disabled",true);y();return true}}function x(){if(c!==null){return c.classList.contains("webform-client-form")||c.classList.contains("webform-submission-form")}return false}function j(){var A=d("div#card-element").closest("form").prop("id");if((typeof A==="undefined")||(!A.length)){A=d("input[name=hidden_processor]").closest("form").prop("id")}return document.getElementById(A)}function z(){var A=null;if(x()){A=c.querySelector('[type="submit"].webform-submit');if(!A){A=c.querySelector('[type="submit"].webform-button--submit')}}else{A=c.querySelector('[type="submit"].validate')}return A}function r(){var A=null;if((document.getElementById("additional_participants")!==null)&&(document.getElementById("additional_participants").value.length!==0)){f("Cannot setup paymentIntent because we don't know the final price");return A}if(typeof calculateTotalFee=="function"){A=calculateTotalFee()}else{if(x()){d(".line-item:visible","#wf-crm-billing-items").each(function(){A+=parseFloat(d(this).data("amount"))})}else{if(document.getElementById("total_amount")){A=document.getElementById("total_amount").value}}}return A}function g(){if(document.getElementById("is_recur")!==null){return Boolean(document.getElementById("is_recur").checked)}return false}function u(A){if(!A.complete){return}document.getElementById("billing_postal_code-"+CRM.vars.stripe.billingAddressID).value=A.value.postalCode}function n(){cividiscountElements=c.querySelectorAll("input#discountcode");var A=function(B){if(B.keyCode===13){B.preventDefault();f("adding submitdontprocess");c.dataset.submitdontprocess=true}};for(i=0;i<cividiscountElements.length;++i){cividiscountElements[i].addEventListener("keydown",A)}}function f(A){if((typeof(CRM.vars.stripe)==="undefined")||(Boolean(CRM.vars.stripe.jsDebug)===true)){console.log(new Date().toISOString()+" civicrm_stripe.js: "+A)}}function t(B){var A=null;if(document.getElementById("action")!==null){A=document.getElementById("action")}else{A=document.createElement("input")}A.setAttribute("type","hidden");A.setAttribute("name","op");A.setAttribute("id","action");A.setAttribute("value",B);c.appendChild(A)}function k(){if((typeof c==="undefined")||(!c)){c=j();if(!c){return null}}var A=c.querySelector('input[name="payment_processor_id"]:checked');if(A!==null){return parseInt(A.value)}return null}});
\ No newline at end of file
+CRM.$(function(d){f("civicrm_stripe loaded, dom-ready function firing.");if(window.civicrmStripeHandleReload){f("calling existing civicrmStripeHandleReload.");window.civicrmStripeHandleReload();return}var q;var b;var c;var a;var o=false;window.onbeforeunload=null;window.civicrmStripeHandleReload=function(){f("civicrmStripeHandleReload");var A=document.getElementById("card-element");if((typeof A!=="undefined")&&(A)){if(!A.children.length){f("checkAndLoad from document.ready");m()}}};window.civicrmStripeHandleReload();function v(C,A){f(C+": success - submitting form");var B=document.createElement("input");B.setAttribute("type","hidden");B.setAttribute("name",C);B.setAttribute("value",A.id);c.appendChild(B);c.submit()}function p(){a.setAttribute("disabled",true);return c.submit()}function h(A){f("error: "+A.error.message);var B=document.getElementById("card-errors");B.style.display="block";B.textContent=A.error.message;document.querySelector("#billing-payment-block").scrollIntoView();window.scrollBy(0,-50);c.dataset.submitted=false;a.removeAttribute("disabled")}function y(){f("handle card payment");q.createPaymentMethod("card",b).then(function(A){if(A.error){h(A)}else{if(g()===true){v("paymentMethodID",A.paymentMethod)}else{var B=CRM.url("civicrm/stripe/confirm-payment");d.post(B,{payment_method_id:A.paymentMethod.id,amount:r(),currency:CRM.vars.stripe.currency,id:CRM.vars.stripe.id,description:document.title}).then(function(C){w(C)})}}})}function w(A){f("handleServerResponse");if(A.error){h(A)}else{if(A.requires_action){s(A)}else{v("paymentIntentID",A.paymentIntent)}}}function s(A){q.handleCardAction(A.payment_intent_client_secret).then(function(B){if(B.error){h(B)}else{v("paymentIntentID",B.paymentIntent)}})}d(document).ajaxComplete(function(C,D,B){if((B.url.match("civicrm(/|%2F)payment(/|%2F)form")!==null)||(B.url.match("civicrm(/|%2F)contact(/|%2F)view(/|%2F)participant")!==null)){if(typeof CRM.vars.stripe==="undefined"){return}var A=k();if(A!==null){if(A!==parseInt(CRM.vars.stripe.id)){f("payment processor changed to id: "+A);if(A===0){return l()}CRM.api3("PaymentProcessor","getvalue",{"return":"user_name",id:A,payment_processor_type_id:CRM.vars.stripe.paymentProcessorTypeID}).done(function(E){var F=E.result;if(F){f("Setting new stripe key to: "+F);CRM.vars.stripe.publishableKey=F}else{return l()}f("checkAndLoad from ajaxComplete");m()})}}}});function l(){f("New payment processor is not Stripe, clearing CRM.vars.stripe");if((typeof b!=="undefined")&&(b)){f("destroying card element");b.destroy();b=undefined}delete (CRM.vars.stripe)}function m(){if(typeof CRM.vars.stripe==="undefined"){f("CRM.vars.stripe not defined! Not a Stripe processor?");return}if(typeof Stripe==="undefined"){if(o){return}o=true;f("Stripe.js is not loaded!");d.getScript("https://js.stripe.com/v3",function(){f("Script loaded and executed.");o=false;e()})}else{e()}}function e(){f("loadStripeBillingBlock");if(typeof q==="undefined"){q=Stripe(CRM.vars.stripe.publishableKey)}var G=q.elements();var D={base:{fontSize:"20px"}};b=G.create("card",{style:D});b.mount("#card-element");f("created new card element",b);document.getElementsByClassName("billing_postal_code-"+CRM.vars.stripe.billingAddressID+"-section")[0].setAttribute("hidden",true);b.addEventListener("change",function(H){u(H)});c=j();if(typeof c.length==="undefined"||c.length===0){f("No billing form!");return}a=z();c.dataset.submitdontprocess=false;var A=c.querySelectorAll('[type="submit"][formnovalidate="1"], [type="submit"][formnovalidate="formnovalidate"], [type="submit"].cancel, [type="submit"].webform-previous'),C;for(C=0;C<A.length;++C){A[C].addEventListener("click",F())}function F(){f("adding submitdontprocess");c.dataset.submitdontprocess=true}a.addEventListener("click",B);function B(H){if(c.dataset.submitted===true){return}c.dataset.submitted=true;if(typeof CRM.vars.stripe==="undefined"){return p()}f("clearing submitdontprocess");c.dataset.submitdontprocess=false;return E(H)}a.removeAttribute("onclick");n();if(x()){d("[type=submit]").click(function(){t(this.value)});c.addEventListener("keydown",function(H){if(H.keyCode===13){t(this.value);E(event)}});d("#billingcheckbox:input").hide();d('label[for="billingcheckbox"]').hide()}function E(J){J.preventDefault();f("submit handler");if(d(c).valid()===false){f("Form not valid");return false}if(typeof CRM.vars.stripe==="undefined"){f("Submitting - not a stripe processor");return true}if(c.dataset.submitted===true){f("form already submitted");return false}var L=parseInt(CRM.vars.stripe.id);var I=null;if(x()){if(!d('input[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]').length){I=L}else{I=parseInt(c.querySelector('input[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]:checked').value)}}else{if((c.querySelector(".crm-section.payment_processor-section")!==null)||(c.querySelector(".crm-section.credit_card_info-section")!==null)){L=CRM.vars.stripe.id;if(c.querySelector('input[name="payment_processor_id"]:checked')!==null){I=parseInt(c.querySelector('input[name="payment_processor_id"]:checked').value)}}}if((I===0)||(L===null)||((I===null)&&(L===null))){f("Not a Stripe transaction, or pay-later");return p()}else{f("Stripe is the selected payprocessor")}if(typeof CRM.vars.stripe.publishableKey==="undefined"){f("submit missing stripe-pub-key element or value");return true}if(c.dataset.submitdontprocess===true){f("non-payment submit detected - not submitting payment");return true}if(x()){if(d("#billing-payment-block").is(":hidden")){f("no payment processor on webform");return true}var K=d('[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]');if(K.length){if(K.filter(":checked").val()==="0"||K.filter(":checked").val()===0){f("no payment processor selected");return true}}}var H=r();if(H=="0"){f("Total amount is 0");return true}if(c.dataset.submitted===true){alert("Form already submitted. Please wait.");return false}else{c.dataset.submitted=true}a.setAttribute("disabled",true);y();return true}}function x(){if(c!==null){return c.classList.contains("webform-client-form")||c.classList.contains("webform-submission-form")}return false}function j(){var A=d("div#card-element").closest("form").prop("id");if((typeof A==="undefined")||(!A.length)){A=d("input[name=hidden_processor]").closest("form").prop("id")}return document.getElementById(A)}function z(){var A=null;if(x()){A=c.querySelector('[type="submit"].webform-submit');if(!A){A=c.querySelector('[type="submit"].webform-button--submit')}}else{A=c.querySelector('[type="submit"].validate')}return A}function r(){var A=null;if((document.getElementById("additional_participants")!==null)&&(document.getElementById("additional_participants").value.length!==0)){f("Cannot setup paymentIntent because we don't know the final price");return A}if(typeof calculateTotalFee=="function"){A=calculateTotalFee()}else{if(x()){d(".line-item:visible","#wf-crm-billing-items").each(function(){A+=parseFloat(d(this).data("amount"))})}else{if(document.getElementById("total_amount")){A=document.getElementById("total_amount").value}}}return A}function g(){if(document.getElementById("is_recur")!==null){return Boolean(document.getElementById("is_recur").checked)}return false}function u(A){if(!A.complete){return}document.getElementById("billing_postal_code-"+CRM.vars.stripe.billingAddressID).value=A.value.postalCode}function n(){cividiscountElements=c.querySelectorAll("input#discountcode");var A=function(B){if(B.keyCode===13){B.preventDefault();f("adding submitdontprocess");c.dataset.submitdontprocess=true}};for(i=0;i<cividiscountElements.length;++i){cividiscountElements[i].addEventListener("keydown",A)}}function f(A){if((typeof(CRM.vars.stripe)==="undefined")||(Boolean(CRM.vars.stripe.jsDebug)===true)){console.log(new Date().toISOString()+" civicrm_stripe.js: "+A)}}function t(B){var A=null;if(document.getElementById("action")!==null){A=document.getElementById("action")}else{A=document.createElement("input")}A.setAttribute("type","hidden");A.setAttribute("name","op");A.setAttribute("id","action");A.setAttribute("value",B);c.appendChild(A)}function k(){if((typeof c==="undefined")||(!c)){c=j();if(!c){return null}}var A=c.querySelector('input[name="payment_processor_id"]:checked');if(A!==null){return parseInt(A.value)}return null}});
\ No newline at end of file
diff --git a/sql/auto_install.sql b/sql/auto_install.sql
index 2ca7777e06349921f6909da6eaa8d357ef357c05..0d5adf63aeca6745610b8c2d3a2a4868e8682317 100644
--- a/sql/auto_install.sql
+++ b/sql/auto_install.sql
@@ -1,9 +1,104 @@
-/* Create required tables for Stripe */
-  CREATE TABLE IF NOT EXISTS `civicrm_stripe_customers` (
-    `id` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
-    `contact_id` int(10) UNSIGNED DEFAULT NULL COMMENT 'FK ID from civicrm_contact',
-    `processor_id` int(10) DEFAULT NULL COMMENT 'ID from civicrm_payment_processor',
-    UNIQUE KEY `id` (`id`),
-    CONSTRAINT `FK_civicrm_stripe_customers_contact_id` FOREIGN KEY (`contact_id`)
-      REFERENCES `civicrm_contact` (`id`) ON DELETE CASCADE
-    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+-- +--------------------------------------------------------------------+
+-- | CiviCRM version 5                                                  |
+-- +--------------------------------------------------------------------+
+-- | Copyright CiviCRM LLC (c) 2004-2019                                |
+-- +--------------------------------------------------------------------+
+-- | This file is a part of CiviCRM.                                    |
+-- |                                                                    |
+-- | CiviCRM is free software; you can copy, modify, and distribute it  |
+-- | under the terms of the GNU Affero General Public License           |
+-- | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+-- |                                                                    |
+-- | CiviCRM is distributed in the hope that it will be useful, but     |
+-- | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+-- | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+-- | See the GNU Affero General Public License for more details.        |
+-- |                                                                    |
+-- | You should have received a copy of the GNU Affero General Public   |
+-- | License and the CiviCRM Licensing Exception along                  |
+-- | with this program; if not, contact CiviCRM LLC                     |
+-- | at info[AT]civicrm[DOT]org. If you have questions about the        |
+-- | GNU Affero General Public License or the licensing of CiviCRM,     |
+-- | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+-- +--------------------------------------------------------------------+
+--
+-- Generated from schema.tpl
+-- DO NOT EDIT.  Generated by CRM_Core_CodeGen
+--
+
+
+-- +--------------------------------------------------------------------+
+-- | CiviCRM version 5                                                  |
+-- +--------------------------------------------------------------------+
+-- | Copyright CiviCRM LLC (c) 2004-2019                                |
+-- +--------------------------------------------------------------------+
+-- | This file is a part of CiviCRM.                                    |
+-- |                                                                    |
+-- | CiviCRM is free software; you can copy, modify, and distribute it  |
+-- | under the terms of the GNU Affero General Public License           |
+-- | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+-- |                                                                    |
+-- | CiviCRM is distributed in the hope that it will be useful, but     |
+-- | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+-- | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+-- | See the GNU Affero General Public License for more details.        |
+-- |                                                                    |
+-- | You should have received a copy of the GNU Affero General Public   |
+-- | License and the CiviCRM Licensing Exception along                  |
+-- | with this program; if not, contact CiviCRM LLC                     |
+-- | at info[AT]civicrm[DOT]org. If you have questions about the        |
+-- | GNU Affero General Public License or the licensing of CiviCRM,     |
+-- | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+-- +--------------------------------------------------------------------+
+--
+-- Generated from drop.tpl
+-- DO NOT EDIT.  Generated by CRM_Core_CodeGen
+--
+-- /*******************************************************
+-- *
+-- * Clean up the exisiting tables
+-- *
+-- *******************************************************/
+
+SET FOREIGN_KEY_CHECKS=0;
+
+DROP TABLE IF EXISTS `civicrm_stripe_paymentintent`;
+
+SET FOREIGN_KEY_CHECKS=1;
+-- /*******************************************************
+-- *
+-- * Create new tables
+-- *
+-- *******************************************************/
+
+-- /*******************************************************
+-- *
+-- * civicrm_stripe_paymentintent
+-- *
+-- * Stripe PaymentIntents
+-- *
+-- *******************************************************/
+CREATE TABLE `civicrm_stripe_paymentintent` (
+
+
+     `id` int unsigned NOT NULL AUTO_INCREMENT  COMMENT 'Unique ID',
+     `paymentintent_id` varchar(255)    COMMENT 'The PaymentIntent ID',
+     `contribution_id` int unsigned    COMMENT 'FK ID from civicrm_contribution',
+     `payment_processor_id` int unsigned    COMMENT 'Foreign key to civicrm_payment_processor.id',
+     `description` varchar(255) NULL   COMMENT 'Description of this paymentIntent',
+     `status` varchar(25) NULL   COMMENT 'The status of the paymentIntent',
+     `identifier` varchar(255) NULL   COMMENT 'An identifier that we can use in CiviCRM to find the paymentIntent if we do not have the ID (eg. session key)',
+     `contact_id` int unsigned    COMMENT 'FK to Contact',
+     `created_date` timestamp   DEFAULT CURRENT_TIMESTAMP COMMENT 'When was paymentIntent created',
+     `flags` varchar(100) NULL   COMMENT 'Flags associated with this PaymentIntent (NC=no contributionID when doPayment called)' 
+,
+        PRIMARY KEY (`id`)
+ 
+    ,     UNIQUE INDEX `UI_paymentintent_id`(
+        paymentintent_id
+  )
+  
+,          CONSTRAINT FK_civicrm_stripe_paymentintent_payment_processor_id FOREIGN KEY (`payment_processor_id`) REFERENCES `civicrm_payment_processor`(`id`) ON DELETE SET NULL,          CONSTRAINT FK_civicrm_stripe_paymentintent_contact_id FOREIGN KEY (`contact_id`) REFERENCES `civicrm_contact`(`id`) ON DELETE CASCADE  
+)    ;
+
+ 
diff --git a/sql/auto_uninstall.sql b/sql/auto_uninstall.sql
index 11145c7ef0e27b294bfcbf986421972fb614c66c..275f79f4ce72a2cccb513dbb9c7092aa565ad92f 100644
--- a/sql/auto_uninstall.sql
+++ b/sql/auto_uninstall.sql
@@ -1,4 +1,38 @@
-/* Remove Stripe tables on uninstall. */
-DROP TABLE IF EXISTS civicrm_stripe_customers;
-DROP TABLE IF EXISTS civicrm_stripe_plans;
-DROP TABLE IF EXISTS civicrm_stripe_subscriptions;
\ No newline at end of file
+-- +--------------------------------------------------------------------+
+-- | CiviCRM version 5                                                  |
+-- +--------------------------------------------------------------------+
+-- | Copyright CiviCRM LLC (c) 2004-2019                                |
+-- +--------------------------------------------------------------------+
+-- | This file is a part of CiviCRM.                                    |
+-- |                                                                    |
+-- | CiviCRM is free software; you can copy, modify, and distribute it  |
+-- | under the terms of the GNU Affero General Public License           |
+-- | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+-- |                                                                    |
+-- | CiviCRM is distributed in the hope that it will be useful, but     |
+-- | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+-- | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+-- | See the GNU Affero General Public License for more details.        |
+-- |                                                                    |
+-- | You should have received a copy of the GNU Affero General Public   |
+-- | License and the CiviCRM Licensing Exception along                  |
+-- | with this program; if not, contact CiviCRM LLC                     |
+-- | at info[AT]civicrm[DOT]org. If you have questions about the        |
+-- | GNU Affero General Public License or the licensing of CiviCRM,     |
+-- | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+-- +--------------------------------------------------------------------+
+--
+-- Generated from drop.tpl
+-- DO NOT EDIT.  Generated by CRM_Core_CodeGen
+--
+-- /*******************************************************
+-- *
+-- * Clean up the exisiting tables
+-- *
+-- *******************************************************/
+
+SET FOREIGN_KEY_CHECKS=0;
+
+DROP TABLE IF EXISTS `civicrm_stripe_paymentintent`;
+
+SET FOREIGN_KEY_CHECKS=1;
diff --git a/stripe.civix.php b/stripe.civix.php
index 51b73c90b884ed8404ea4c409dea4fe540a206d0..6de525c8f17aa6577c5c659e1a7f3ed6e5569ad1 100644
--- a/stripe.civix.php
+++ b/stripe.civix.php
@@ -457,5 +457,11 @@ function _stripe_civix_civicrm_alterSettingsFolders(&$metaDataFolders = NULL) {
 
 function _stripe_civix_civicrm_entityTypes(&$entityTypes) {
   $entityTypes = array_merge($entityTypes, array (
+    'CRM_Stripe_DAO_StripePaymentintent' => 
+    array (
+      'name' => 'StripePaymentintent',
+      'class' => 'CRM_Stripe_DAO_StripePaymentintent',
+      'table' => 'civicrm_stripe_paymentintent',
+    ),
   ));
 }
diff --git a/stripe.php b/stripe.php
index 35c383bbbc61b8a2f851237e0616daba5d8dac93..c729768a4745e298decb1e237ac941e00a88f1c6 100644
--- a/stripe.php
+++ b/stripe.php
@@ -70,6 +70,14 @@ function stripe_civicrm_managed(&$entities) {
   _stripe_civix_civicrm_managed($entities);
 }
 
+
+/**
+ * Implements hook_civicrm_entityTypes().
+ */
+function stripe_civicrm_entityTypes(&$entityTypes) {
+  _stripe_civix_civicrm_entityTypes($entityTypes);
+}
+
 /**
  * Implements hook_civicrm_alterSettingsFolders().
  */
@@ -132,10 +140,27 @@ function stripe_civicrm_buildForm($formName, &$form) {
     case 'CRM_Event_Form_Registration_ThankYou':
       \Civi::resources()->addScriptFile(E::LONG_NAME, 'js/civicrmStripeConfirm.js');
 
-      // @todo: Not working yet because the paymentIntentID doesn't get passed - let's save/retrieve from db (use contribution and/or session key)
+      // This is a fairly nasty way of matching and retrieving our paymentIntent as it is no longer available.
+      $qfKey = CRM_Utils_Request::retrieve('qfKey', 'String');
+      if (!empty($qfKey)) {
+        $paymentIntent = civicrm_api3('StripePaymentintent', 'getsingle', ['return' => ['paymentintent_id', 'status', 'contribution_id'], 'identifier' => $qfKey]);
+      }
+
+      if (empty($paymentIntent['contribution_id'])) {
+        // If we now have a contribution ID try and update it so we can cross-reference the paymentIntent
+        $contributionId = $form->getVar('_values')['contributionId'];
+        if (!empty($contributionId)) {
+          civicrm_api3('StripePaymentintent', 'create', [
+            'id' => $paymentIntent['id'],
+            'contribution_id' => $contributionId
+          ]);
+        }
+      }
+
       $jsVars = [
         'id' => $form->_paymentProcessor['id'],
-        'paymentIntentID' => \Civi::$statics['paymentIntentID'],
+        'paymentIntentID' => $paymentIntent['paymentintent_id'],
+        'paymentIntentStatus' => $paymentIntent['status'],
         'publishableKey' => CRM_Core_Payment_Stripe::getPublicKeyById($form->_paymentProcessor['id']),
         'jsDebug' => (boolean) \Civi::settings()->get('stripe_jsdebug'),
       ];
diff --git a/xml/schema/CRM/Stripe/StripePaymentintent.entityType.php b/xml/schema/CRM/Stripe/StripePaymentintent.entityType.php
new file mode 100644
index 0000000000000000000000000000000000000000..999e2947592e5ff12da38733b10576bace0ffd65
--- /dev/null
+++ b/xml/schema/CRM/Stripe/StripePaymentintent.entityType.php
@@ -0,0 +1,11 @@
+<?php
+// This file declares a new entity type. For more details, see "hook_civicrm_entityTypes" at:
+// http://wiki.civicrm.org/confluence/display/CRMDOC/Hook+Reference
+return array (
+  0 => 
+  array (
+    'name' => 'StripePaymentintent',
+    'class' => 'CRM_Stripe_DAO_StripePaymentintent',
+    'table' => 'civicrm_stripe_paymentintent',
+  ),
+);
diff --git a/xml/schema/CRM/Stripe/StripePaymentintent.xml b/xml/schema/CRM/Stripe/StripePaymentintent.xml
new file mode 100644
index 0000000000000000000000000000000000000000..cbfdf4842991abd766debb843cf832c876204797
--- /dev/null
+++ b/xml/schema/CRM/Stripe/StripePaymentintent.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="iso-8859-1" ?>
+
+<table>
+  <base>CRM/Stripe</base>
+  <class>StripePaymentintent</class>
+  <name>civicrm_stripe_paymentintent</name>
+  <comment>Stripe PaymentIntents</comment>
+  <log>true</log>
+
+  <field>
+    <name>id</name>
+    <type>int unsigned</type>
+    <required>true</required>
+    <comment>Unique ID</comment>
+  </field>
+  <primaryKey>
+    <name>id</name>
+    <autoincrement>true</autoincrement>
+  </primaryKey>
+
+  <field>
+    <name>paymentintent_id</name>
+    <title>PaymentIntent ID</title>
+    <type>varchar</type>
+    <length>255</length>
+    <comment>The PaymentIntent ID</comment>
+  </field>
+  <index>
+    <name>UI_paymentintent_id</name>
+    <fieldName>paymentintent_id</fieldName>
+    <unique>true</unique>
+  </index>
+
+  <field>
+    <name>contribution_id</name>
+    <title>Contribution ID</title>
+    <type>int unsigned</type>
+    <comment>FK ID from civicrm_contribution</comment>
+  </field>
+
+  <field>
+    <name>payment_processor_id</name>
+    <title>Payment Processor</title>
+    <type>int unsigned</type>
+    <comment>Foreign key to civicrm_payment_processor.id</comment>
+    <pseudoconstant>
+      <table>civicrm_payment_processor</table>
+      <keyColumn>id</keyColumn>
+      <labelColumn>name</labelColumn>
+    </pseudoconstant>
+  </field>
+  <foreignKey>
+    <name>payment_processor_id</name>
+    <table>civicrm_payment_processor</table>
+    <key>id</key>
+    <onDelete>SET NULL</onDelete>
+  </foreignKey>
+
+  <field>
+    <name>description</name>
+    <title>Description</title>
+    <type>varchar</type>
+    <required>false</required>
+    <length>255</length>
+    <comment>Description of this paymentIntent</comment>
+  </field>
+
+  <field>
+    <name>status</name>
+    <type>varchar</type>
+    <length>25</length>
+    <required>false</required>
+    <comment>The status of the paymentIntent</comment>
+  </field>
+
+  <field>
+    <name>identifier</name>
+    <type>varchar</type>
+    <length>255</length>
+    <required>false</required>
+    <comment>An identifier that we can use in CiviCRM to find the paymentIntent if we do not have the ID (eg. session key)</comment>
+  </field>
+
+  <field>
+    <name>contact_id</name>
+    <type>int unsigned</type>
+    <comment>FK to Contact</comment>
+  </field>
+  <foreignKey>
+    <name>contact_id</name>
+    <table>civicrm_contact</table>
+    <key>id</key>
+    <onDelete>CASCADE</onDelete>
+  </foreignKey>
+
+  <field>
+    <name>created_date</name>
+    <title>Created Date</title>
+    <type>timestamp</type>
+    <default>CURRENT_TIMESTAMP</default>
+    <comment>When was paymentIntent created</comment>
+  </field>
+
+  <field>
+    <name>flags</name>
+    <type>varchar</type>
+    <length>100</length>
+    <required>false</required>
+    <comment>Flags associated with this PaymentIntent (NC=no contributionID when doPayment called)</comment>
+  </field>
+
+</table>