diff --git a/api/v3/Stripe/Processpaymentintent.php b/api/v3/Stripe/Processpaymentintent.php
new file mode 100644
index 0000000000000000000000000000000000000000..30cea1f6477256f86429f132cbf954852fca5dd6
--- /dev/null
+++ b/api/v3/Stripe/Processpaymentintent.php
@@ -0,0 +1,160 @@
+<?php
+/**
+ * https://civicrm.org/licensing
+ */
+
+use CRM_Stripe_ExtensionUtil as E;
+
+/**
+ * Stripe.ProcessPaymentIntent API specification
+ *
+ * @param array $spec description of fields supported by this API call
+ *
+ * @return void
+ */
+function _civicrm_api3_stripe_ProcessPaymentIntent_spec(&$spec) {
+  $spec['payment_method_id']['title'] = E::ts("Stripe generated code used to create a payment intent.");
+  $spec['payment_method_id']['type'] = CRM_Utils_Type::T_STRING;
+  $spec['payment_method_id']['api.default'] = NULL; 
+  $spec['payment_intent_id']['title'] = ts("The payment intent id itself, if available.");
+  $spec['payment_intent_id']['type'] = CRM_Utils_Type::T_STRING;
+  $spec['payment_intent_id']['api.default'] = NULL; 
+  $spec['amount']['title'] = ts("The payment amount.");
+  $spec['amount']['type'] = CRM_Utils_Type::T_STRING;
+  $spec['amount']['api.default'] = NULL;
+  $spec['capture']['title'] = ts("Whether we should try to capture the amount, not just confirm it.");
+  $spec['capture']['type'] = CRM_Utils_Type::T_BOOLEAN;
+  $spec['capture']['api.default'] = FALSE;
+  $spec['description']['title'] = ts("Describe the payment.");
+  $spec['description']['type'] = CRM_Utils_Type::T_STRING;
+  $spec['description']['api.default'] = NULL;
+  $spec['currency']['title'] = ts("Whether we should try to capture the amount, not just confirm it.");
+  $spec['currency']['type'] = CRM_Utils_Type::T_STRING;
+  $spec['currency']['api.default'] = CRM_Core_Config::singleton()->defaultCurrency;
+  $spec['payment_processor_id']['title'] = ts("The stripe payment processor id.");
+  $spec['payment_processor_id']['type'] = CRM_Utils_Type::T_INT;
+  $spec['payment_processor_id']['api.required'] = TRUE;
+}
+
+/**
+ * Stripe.ProcessPaymentIntent API
+ *
+ * Generate the paymentIntent for civicrm_stripe.js
+ *
+ * In the normal flow of a CiviContribute form, this will be called with a
+ * payment_method_id (which is generated by Stripe via its javascript code),
+ * in which case it will create a PaymentIntent using that and *attempt* to
+ * 'confirm' it.
+ *
+ * This can also be called with a payment_intent_id instead, in which case it
+ * will retrieve the PaymentIntent and attempt (again) to 'confirm' it. This
+ * is useful to confirm funds after a user has completed SCA in their
+ * browser.
+ *
+ * 'confirming' a PaymentIntent refers to the process by which the funds are
+ * reserved in the cardholder's account, but not actually taken yet.
+ *
+ * Taking the funds ('capturing') should go through without problems once the
+ * transaction has been confirmed - this is done later on in the process.
+ *
+ * Nb. confirmed funds are released and will become available to the
+ * cardholder again if the PaymentIntent is cancelled or is not captured
+ * within 1 week.
+ *
+ * @param array $params
+ *
+ * @return array API result descriptor
+ * @throws \API_Exception
+ * @throws \CiviCRM_API3_Exception
+ * @throws \Stripe\Error\Api
+ */
+function civicrm_api3_stripe_ProcessPaymentIntent($params) {
+  $paymentMethodID = $params['payment_method_id'];
+  $paymentIntentID = $params['payment_intent_id'];
+  $amount = $params['amount'];
+  $capture = $params['capture'];
+  $title = $params['description'];
+  $confirm = TRUE;
+  if (empty($amount)) {
+    $amount = 1;
+    $confirm = FALSE;
+  }
+  $currency = $params['currency'];
+  $processorID = $params['payment_processor_id'];
+  $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $processorID]));
+  $processor->setAPIParams();
+
+  if ($paymentIntentID) {
+    // We already have a PaymentIntent, retrieve and attempt confirm.
+    $intent = \Stripe\PaymentIntent::retrieve($paymentIntentID);
+    if ($intent->status === 'requires_confirmation') {
+      $intent->confirm();
+    }
+    if ($capture && $intent->status === 'requires_capture') {
+      $intent->capture();
+    }
+  }
+  else {
+    // We don't yet have a PaymentIntent, create one using the
+    // Payment Method ID and attempt to confirm it too.
+    $intent = \Stripe\PaymentIntent::create([
+      'payment_method' => $paymentMethodID,
+      'amount' => $processor->getAmount(['amount' => $amount, 'currency' => $currency]),
+      'currency' => $currency,
+      'confirmation_method' => 'manual',
+      'capture_method' => 'manual',
+      // authorize the amount but don't take from card yet
+      'setup_future_usage' => 'off_session',
+      // Setup the card to be saved and used later
+      'confirm' => $confirm,
+    ]);
+  }
+
+  // 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);
+  
+  $out = civicrm_api3_stripe_GeneratePaymentResponse($intent);
+  return civicrm_api3_create_success($out);
+}
+
+/**
+ * Generate the json response for civicrm_stripe.js
+ *
+ * @param \Stripe\PaymentIntent $intent
+ */
+function civicrm_api3_stripe_GeneratePaymentResponse($intent) {
+  if ($intent->status === 'requires_action' &&
+    $intent->next_action->type === 'use_stripe_sdk') {
+    // Tell the client to handle the action
+    return [
+      'requires_action' => true,
+      'payment_intent_client_secret' => $intent->client_secret,
+    ];
+  }
+  elseif (($intent->status === 'requires_capture') || ($intent->status === 'requires_confirmation')) {
+    // paymentIntent = requires_capture / requires_confirmation
+    // The payment intent has been confirmed, we just need to capture the payment
+    // Handle post-payment fulfillment
+    return [
+      'success' => true,
+      'paymentIntent' => ['id' => $intent->id],
+    ];
+  }
+  elseif ($intent->status === 'succeeded') {
+    return [
+      'success' => true,
+      'paymentIntent' => ['id' => $intent->id],
+    ];
+  }
+  else {
+    // Invalid status
+    throw new API_Exception('Invalid PaymentIntent status');
+  }
+}
+