diff --git a/CRM/Core/Payment/Stripe.php b/CRM/Core/Payment/Stripe.php
index 8fbd2930a12e2c05a09aa635c39fb194846a3d74..f17f07bd7a1e73ef07243afd519f5c0d342064c7 100644
--- a/CRM/Core/Payment/Stripe.php
+++ b/CRM/Core/Payment/Stripe.php
@@ -3,18 +3,20 @@
  * https://civicrm.org/licensing
  */
 
+use CRM_Stripe_ExtensionUtil as E;
+
 /**
  * Class CRM_Core_Payment_Stripe
  */
 class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
 
-  use CRM_Core_Payment_StripeTrait;
+  use CRM_Core_Payment_MJWTrait;
 
   /**
    *
    * @var string
    */
-  const API_VERSION = '2019-05-16';
+  const API_VERSION = '2019-09-09';
 
   /**
    * Mode of operation: live or test.
@@ -31,13 +33,14 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
    *
    * @param string $mode
    *   The mode of operation: live or test.
+   * @param array $paymentProcessor
    *
    * @return void
    */
-  public function __construct($mode, &$paymentProcessor) {
+  public function __construct($mode, $paymentProcessor) {
     $this->_mode = $mode;
     $this->_paymentProcessor = $paymentProcessor;
-    $this->_processorName = ts('Stripe');
+    $this->_processorName = E::SHORT_NAME;
   }
 
   /**
@@ -46,7 +49,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
    * @return string
    */
   public static function getSecretKey($paymentProcessor) {
-    return trim(CRM_Utils_Array::value('user_name', $paymentProcessor));
+    return trim(CRM_Utils_Array::value('password', $paymentProcessor));
   }
 
   /**
@@ -55,7 +58,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
    * @return string
    */
   public static function getPublicKey($paymentProcessor) {
-    return trim(CRM_Utils_Array::value('password', $paymentProcessor));
+    return trim(CRM_Utils_Array::value('user_name', $paymentProcessor));
   }
 
   /**
@@ -105,7 +108,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
    *   The error message if any.
    */
   public function checkConfig() {
-    $error = array();
+    $error = [];
 
     if (!empty($error)) {
       return implode('<p>', $error);
@@ -120,7 +123,9 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
    * @return bool
    */
   public function supportsBackOffice() {
-    return TRUE;
+    // @fixme Make this work again with stripe elements / 6.0
+    return FALSE;
+    // return TRUE;
   }
 
   /**
@@ -131,8 +136,21 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
     return FALSE;
   }
 
+  public function supportsRecurring() {
+    return TRUE;
+  }
+
+  /**
+   * Does this payment processor support refund?
+   *
+   * @return bool
+   */
+  public function supportsRefund() {
+    return TRUE;
+  }
+
   /**
-   * We can configure a start date for a smartdebit mandate
+   * Can we set a future recur start date?  Stripe allows this but we don't (yet) support it.
    * @return bool
    */
   public function supportsFutureRecurStartDate() {
@@ -173,14 +191,8 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
    *
    * @return string errorMessage (or statusbounce if URL is specified)
    */
-  public static function handleErrorNotification($err, $bounceURL = NULL) {
-    $debugMsg = $err['type'] . ' ' . $err['code'] . ' ' . $err['message'];
-    Civi::log()->debug('Stripe Payment Error: ' . $debugMsg);
-
-    if ($bounceURL) {
-      CRM_Core_Error::statusBounce($err['message'], $bounceURL, 'Payment Error');
-    }
-    return $debugMsg;
+  public function handleErrorNotification($err, $bounceURL = NULL) {
+    return self::handleError("{$err['type']} {$err['code']}", $err['message'], $bounceURL);
   }
 
   /**
@@ -215,9 +227,6 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
   public function createPlan($params, $amount) {
     $currency = strtolower($params['currencyID']);
     $planId = "every-{$params['frequency_interval']}-{$params['frequency_unit']}-{$amount}-" . $currency;
-    if (isset($params['membership_type_tag'])) {
-      $planId = $params['membership_type_tag'] . $planId;
-    }
 
     if ($this->_paymentProcessor['is_test']) {
       $planId .= '-test';
@@ -236,19 +245,19 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
         if ($this->_paymentProcessor['is_test']) {
           $productName .= '-test';
         }
-        $product = \Stripe\Product::create(array(
+        $product = \Stripe\Product::create([
           "name" => $productName,
           "type" => "service"
-        ));
+        ]);
         // Create a new Plan.
-        $stripePlan = array(
+        $stripePlan = [
           'amount' => $amount,
           'interval' => $params['frequency_unit'],
           'product' => $product->id,
           'currency' => $currency,
           'id' => $planId,
           'interval_count' => $params['frequency_interval'],
-        );
+        ];
         $plan = \Stripe\Plan::create($stripePlan);
       }
     }
@@ -261,15 +270,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
    * @return array
    */
   public function getPaymentFormFields() {
-    return array(
-      'credit_card_type',
-      'credit_card_number',
-      'cvv2',
-      'credit_card_exp_date',
-      'stripe_token',
-      'stripe_pub_key',
-      'stripe_id',
-    );
+    return [];
   }
 
   /**
@@ -281,86 +282,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
    *   field metadata
    */
   public function getPaymentFormFieldsMetadata() {
-    $creditCardType = array('' => ts('- select -')) + CRM_Contribute_PseudoConstant::creditCard();
-    return array(
-      'credit_card_number' => array(
-        'htmlType' => 'text',
-        'name' => 'credit_card_number',
-        'title' => ts('Card Number'),
-        'cc_field' => TRUE,
-        'attributes' => array(
-          'size' => 20,
-          'maxlength' => 20,
-          'autocomplete' => 'off',
-        ),
-        'is_required' => TRUE,
-      ),
-      'cvv2' => array(
-        'htmlType' => 'text',
-        'name' => 'cvv2',
-        'title' => ts('Security Code'),
-        'cc_field' => TRUE,
-        'attributes' => array(
-          'size' => 5,
-          'maxlength' => 10,
-          'autocomplete' => 'off',
-        ),
-        'is_required' => TRUE,
-      ),
-      'credit_card_exp_date' => array(
-        'htmlType' => 'date',
-        'name' => 'credit_card_exp_date',
-        'title' => ts('Expiration Date'),
-        'cc_field' => TRUE,
-        'attributes' => CRM_Core_SelectValues::date('creditCard'),
-        'is_required' => TRUE,
-        'month_field' => 'credit_card_exp_date_M',
-        'year_field' => 'credit_card_exp_date_Y',
-        'extra' => ['class' => 'crm-form-select'],
-      ),
-
-      'credit_card_type' => array(
-        'htmlType' => 'select',
-        'name' => 'credit_card_type',
-        'title' => ts('Card Type'),
-        'cc_field' => TRUE,
-        'attributes' => $creditCardType,
-        'is_required' => FALSE,
-      ),
-      'stripe_token' => array(
-        'htmlType' => 'hidden',
-        'name' => 'stripe_token',
-        'title' => 'Stripe Token',
-        'attributes' => array(
-          'id' => 'stripe-token',
-          'class' => 'payproc-metadata',
-        ),
-        'cc_field' => TRUE,
-        'is_required' => TRUE,
-      ),
-      'stripe_id' => array(
-        'htmlType' => 'hidden',
-        'name' => 'stripe_id',
-        'title' => 'Stripe ID',
-        'attributes' => array(
-          'id' => 'stripe-id',
-          'class' => 'payproc-metadata',
-        ),
-        'cc_field' => TRUE,
-        'is_required' => TRUE,
-      ),
-      'stripe_pub_key' => array(
-        'htmlType' => 'hidden',
-        'name' => 'stripe_pub_key',
-        'title' => 'Stripe Public Key',
-        'attributes' => array(
-          'id' => 'stripe-pub-key',
-          'class' => 'payproc-metadata',
-        ),
-        'cc_field' => TRUE,
-        'is_required' => TRUE,
-      ),
-    );
+    return [];
   }
 
   /**
@@ -394,14 +316,28 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
    * @param \CRM_Core_Form $form
    */
   public function buildForm(&$form) {
-    // Set default values
-    $paymentProcessorId = CRM_Utils_Array::value('id', $form->_paymentProcessor);
-    $publishableKey = CRM_Core_Payment_Stripe::getPublicKeyById($paymentProcessorId);
-    $defaults = [
-      'stripe_id' => $paymentProcessorId,
-      'stripe_pub_key' => $publishableKey,
+    $jsVars = [
+      'id' => $form->_paymentProcessor['id'],
+      'currency' => $this->getDefaultCurrencyForForm($form),
+      'billingAddressID' => CRM_Core_BAO_LocationType::getBilling(),
+      'publishableKey' => CRM_Core_Payment_Stripe::getPublicKeyById($form->_paymentProcessor['id']),
+      'jsDebug' => (boolean) \Civi::settings()->get('stripe_jsdebug'),
     ];
-    $form->setDefaults($defaults);
+    \Civi::resources()->addVars(E::SHORT_NAME, $jsVars);
+    // Assign to smarty so we can add via Card.tpl for drupal webform because addVars doesn't work in that context
+    $form->assign('stripeJSVars', $jsVars);
+
+    // Add help and javascript
+    CRM_Core_Region::instance('billing-block')->add(
+      ['template' => 'CRM/Core/Payment/Stripe/Card.tpl', 'weight' => -1]);
+    // Add CSS via region (it won't load on drupal webform if added via \Civi::resources()->addStyleFile)
+
+    $min = ((boolean) \Civi::settings()->get('stripe_jsdebug')) ? '' : '.min';
+    CRM_Core_Region::instance('billing-block')->add([
+      'styleUrl' => \Civi::resources()->getUrl(E::LONG_NAME, "css/elements{$min}.css"),
+      'weight' => -1,
+    ]);
+    \Civi::resources()->addScriptFile(E::LONG_NAME, "js/civicrm_stripe{$min}.js");
   }
 
   /**
@@ -422,22 +358,24 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
    * @throws \Civi\Payment\Exception\PaymentProcessorException
    */
   public function doPayment(&$params, $component = 'contribute') {
-    if (array_key_exists('credit_card_number', $params)) {
-      $cc = $params['credit_card_number'];
-      if (!empty($cc) && substr($cc, 0, 8) != '00000000') {
-        Civi::log()->debug(ts('ALERT! Unmasked credit card received in back end. Please report this error to the site administrator.'));
-      }
+    $params = $this->beginDoPayment($params);
+
+    // Get the passed in paymentIntent
+    if(!empty(CRM_Utils_Array::value('paymentIntentID', $params))) {
+      $paymentIntentID = CRM_Utils_Array::value('paymentIntentID', $params);
+    }
+    elseif (CRM_Core_Session::singleton()->get('stripePaymentIntent')) {
+      // @fixme Hack for contributionpages - see https://github.com/civicrm/civicrm-core/pull/15252
+      $paymentIntentID = CRM_Core_Session::singleton()->get('stripePaymentIntent');
+      $params['paymentIntentID'] = $paymentIntentID;
+    }
+    else {
+      CRM_Core_Error::statusBounce(E::ts('Unable to complete payment! Missing paymentIntent ID.'));
+      Civi::log()->debug('paymentIntentID not found. $params: ' . print_r($params, TRUE));
     }
 
-    $completedStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
     $pendingStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending');
 
-    // If we have a $0 amount, skip call to processor and set payment_status to Completed.
-    if (empty($params['amount'])) {
-      $params['payment_status_id'] = $completedStatusId;
-      return $params;
-    }
-
     $this->setAPIParams();
 
     // Get proper entry URL for returning on error.
@@ -448,162 +386,135 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
     }
     else {
       $qfKey = $params['qfKey'];
-      $parsed_url = parse_url($params['entryURL']);
-      $url_path = substr($parsed_url['path'], 1);
-      $params['stripe_error_url'] = CRM_Utils_System::url($url_path,
-      $parsed_url['query'] . "&_qf_Main_display=1&qfKey={$qfKey}", FALSE, NULL, FALSE);
+      $parsedUrl = parse_url($params['entryURL']);
+      $urlPath = substr($parsedUrl['path'], 1);
+      $query = $parsedUrl['query'];
+      if (strpos($query, '_qf_Main_display=1') === FALSE) {
+        $query .= '&_qf_Main_display=1';
+      }
+      if (strpos($query, 'qfKey=') === FALSE) {
+        $query .= "&qfKey={$qfKey}";
+      }
+      $params['stripe_error_url'] = CRM_Utils_System::url($urlPath, $query, FALSE, NULL, FALSE);
     }
     $amount = self::getAmount($params);
 
-    // Use Stripe.js instead of raw card details.
-    if (!empty($params['stripe_token'])) {
-      $card_token = $params['stripe_token'];
-    }
-    else if(!empty(CRM_Utils_Array::value('stripe_token', $_POST, NULL))) {
-      $card_token = CRM_Utils_Array::value('stripe_token', $_POST, NULL);
-    }
-    else {
-      CRM_Core_Error::statusBounce(ts('Unable to complete payment! Please this to the site administrator with a description of what you were trying to do.'));
-      Civi::log()->debug('Stripe.js token was not passed!  Report this message to the site administrator. $params: ' . print_r($params, TRUE));
-    }
-
     $contactId = $this->getContactId($params);
     $email = $this->getBillingEmail($params, $contactId);
 
     // See if we already have a stripe customer
     $customerParams = [
       'contact_id' => $contactId,
-      'card_token' => $card_token,
       'processor_id' => $this->_paymentProcessor['id'],
       'email' => $email,
       // Include this to allow redirect within session on payment failure
       'stripe_error_url' => $params['stripe_error_url'],
     ];
 
+    // Get the Stripe Customer:
+    //   1. Look for an existing customer.
+    //   2. If no customer (or a deleted customer found), create a new one.
+    //   3. If existing customer found, update the metadata that Stripe holds for this customer.
     $stripeCustomerId = CRM_Stripe_Customer::find($customerParams);
-
     // Customer not in civicrm database.  Create a new Customer in Stripe.
     if (!isset($stripeCustomerId)) {
-      $stripeCustomer = CRM_Stripe_Customer::create($customerParams);
+      $stripeCustomer = CRM_Stripe_Customer::create($customerParams, $this);
     }
     else {
       // Customer was found in civicrm database, fetch from Stripe.
-      $deleteCustomer = FALSE;
       try {
         $stripeCustomer = \Stripe\Customer::retrieve($stripeCustomerId);
-      }
-      catch (Exception $e) {
+      } catch (Exception $e) {
         $err = self::parseStripeException('retrieve_customer', $e, FALSE);
-        if (($err['type'] == 'invalid_request_error') && ($err['code'] == 'resource_missing')) {
-          $deleteCustomer = TRUE;
-        }
-        $errorMessage = self::handleErrorNotification($err, $params['stripe_error_url']);
-        throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to create Stripe Charge: ' . $errorMessage);
+        $errorMessage = $this->handleErrorNotification($err, $params['stripe_error_url']);
+        throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to retrieve Stripe Customer: ' . $errorMessage);
       }
 
-      if ($deleteCustomer || $stripeCustomer->isDeleted()) {
+      if ($stripeCustomer->isDeleted()) {
         // Customer doesn't exist, create a new one
         CRM_Stripe_Customer::delete($customerParams);
         try {
-          $stripeCustomer = CRM_Stripe_Customer::create($customerParams);
-        }
-        catch (Exception $e) {
+          $stripeCustomer = CRM_Stripe_Customer::create($customerParams, $this);
+        } catch (Exception $e) {
           // We still failed to create a customer
-          $errorMessage = self::handleErrorNotification($stripeCustomer, $params['stripe_error_url']);
+          $errorMessage = $this->handleErrorNotification($stripeCustomer, $params['stripe_error_url']);
           throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to create Stripe Customer: ' . $errorMessage);
         }
       }
-
-      $stripeCustomer->card = $card_token;
-      try {
-        $stripeCustomer->save();
-      }
-      catch (Exception $e) {
-        $err = self::parseStripeException('update_customer', $e, TRUE);
-        if (($err['type'] == 'invalid_request_error') && ($err['code'] == 'token_already_used')) {
-          // This error is ok, we've already used the token during create_customer
-        }
-        else {
-          $errorMessage = self::handleErrorNotification($err, $params['stripe_error_url']);
-          throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to update Stripe Customer: ' . $errorMessage);
-        }
+      else {
+        CRM_Stripe_Customer::updateMetadata($customerParams, $this, $stripeCustomer->id);
       }
     }
 
     // Prepare the charge array, minus Customer/Card details.
     if (empty($params['description'])) {
-      $params['description'] = ts('Backend Stripe contribution');
-    }
-
-    // Handle recurring payments in doRecurPayment().
-    if (CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID']) {
-      // We set payment status as pending because the IPN will set it as completed / failed
-      $params['payment_status_id'] = $pendingStatusId;
-      return $this->doRecurPayment($params, $amount, $stripeCustomer);
+      $params['description'] = E::ts('Contribution: %1', [1 => $this->getPaymentProcessorLabel()]);
     }
 
-    // Stripe charge.
-    $stripeChargeParams = [
-      'amount' => $amount,
-      'currency' => strtolower($params['currencyID']),
-      'description' => $params['description'] . ' # Invoice ID: ' . CRM_Utils_Array::value('invoiceID', $params),
+    $contactContribution = $this->getContactId($params) . '-' . ($this->getContributionId($params) ?: 'XX');
+    $intentParams = [
+      'customer' => $stripeCustomer->id,
+      'description' => "{$params['description']} {$contactContribution} #" . CRM_Utils_Array::value('invoiceID', $params),
+      'statement_descriptor_suffix' => "{$contactContribution} " . substr($params['description'],0,7),
+    ];
+    $intentParams['statement_descriptor'] = substr("{$contactContribution} " . $params['description'], 0, 22);
+    $intentMetadata = [
+      'amount_to_capture' => $this->getAmount($params),
     ];
 
-    // Use Stripe Customer if we have a valid one.  Otherwise just use the card.
-    if (!empty($stripeCustomer->id)) {
-      $stripeChargeParams['customer'] = $stripeCustomer->id;
-    }
-    else {
-      $stripeChargeParams['card'] = $card_token;
-    }
-
+    // This is where we actually charge the customer
     try {
-      $stripeCharge = \Stripe\Charge::create($stripeChargeParams);
+      \Stripe\PaymentIntent::update($paymentIntentID, $intentParams);
+      $intent = \Stripe\PaymentIntent::retrieve($paymentIntentID);
+      $intent->customer = $stripeCustomer->id;
+      switch ($intent->status) {
+        case 'requires_confirmation':
+          $intent->confirm();
+        case 'requires_capture':
+          $intent->capture($intentMetadata);
+          break;
+      }
     }
     catch (Exception $e) {
-      $err = self::parseStripeException('charge_create', $e, FALSE);
-
-      if ($e instanceof \Stripe\Error\Card) {
-        if ($this->getContributionId($params)) {
-          civicrm_api3('Note', 'create', [
-            'entity_id' => $this->getContributionId($params),
-            'contact_id' => $this->getContactId($params),
-            'subject' => $err['type'],
-            'note' => $err['code'],
-            'entity_table' => 'civicrm_contribution',
-          ]);
-        }
-      }
-      $errorMessage = self::handleErrorNotification($err, $params['stripe_error_url']);
-      throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to create Stripe Charge: ' . $errorMessage);
+      $this->handleError($e->getCode(), $e->getMessage(), $params['stripe_error_url']);
+    }
+
+    // Handle recurring payments in doRecurPayment().
+    if (CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID']) {
+      // This is where we save the customer card
+      // @todo For a recurring payment we have to save the card. For a single payment we'd like to develop the
+      //   save card functionality but should not save by default as the customer has not agreed.
+      $paymentMethod = \Stripe\PaymentMethod::retrieve($intent->payment_method);
+      $paymentMethod->attach(['customer' => $stripeCustomer->id]);
+
+      // We set payment status as pending because the IPN will set it as completed / failed
+      $params['payment_status_id'] = $pendingStatusId;
+      return $this->doRecurPayment($params, $amount, $stripeCustomer, $paymentMethod);
     }
 
     // Return fees & net amount for Civi reporting.
+    $stripeCharge = $intent->charges->data[0];
     try {
       $stripeBalanceTransaction = \Stripe\BalanceTransaction::retrieve($stripeCharge->balance_transaction);
     }
     catch (Exception $e) {
       $err = self::parseStripeException('retrieve_balance_transaction', $e, FALSE);
-      $errorMessage = self::handleErrorNotification($err, $params['stripe_error_url']);
+      $errorMessage = $this->handleErrorNotification($err, $params['stripe_error_url']);
       throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to retrieve Stripe Balance Transaction: ' . $errorMessage);
     }
 
     // Success!
+    // Set the desired contribution status which will be set later (do not set on the contribution here!)
+    $params['contribution_status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
     // 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...
-    $newParams['trxn_id'] = $stripeCharge->id;
-    $newParams['payment_status_id'] = $completedStatusId;
+    // For a single charge there is no stripe invoice.
+    $this->setPaymentProcessorOrderID($stripeCharge->id);
+    $this->setPaymentProcessorTrxnID($stripeCharge->id);
     $newParams['fee_amount'] = $stripeBalanceTransaction->fee / 100;
     $newParams['net_amount'] = $stripeBalanceTransaction->net / 100;
 
-    if ($this->getContributionId($params)) {
-      $newParams['id'] = $this->getContributionId($params);
-      civicrm_api3('Contribution', 'create', $newParams);
-      unset($newParams['id']);
-    }
-    $params = array_merge($params, $newParams);
-
-    return $params;
+    return $this->endDoPayment($params, $newParams);
   }
 
   /**
@@ -614,8 +525,9 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
    *   Assoc array of input parameters for this transaction.
    * @param int $amount
    *   Transaction amount in USD cents.
-   * @param object $stripeCustomer
+   * @param \Stripe\Customer $stripeCustomer
    *   Stripe customer object generated by Stripe API.
+   * @param \Stripe\PaymentMethod $stripePaymentMethod
    *
    * @return array
    *   The result in a nice formatted array (or an error object).
@@ -623,7 +535,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
    * @throws \CiviCRM_API3_Exception
    * @throws \CRM_Core_Exception
    */
-  public function doRecurPayment(&$params, $amount, $stripeCustomer) {
+  public function doRecurPayment($params, $amount, $stripeCustomer, $stripePaymentMethod) {
     $requiredParams = ['contributionRecurID', 'frequency_unit'];
     foreach ($requiredParams as $required) {
       if (!isset($params[$required])) {
@@ -635,8 +547,6 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
     // Make sure frequency_interval is set (default to 1 if not)
     empty($params['frequency_interval']) ? $params['frequency_interval'] = 1 : NULL;
 
-    $amount = $this->deprecatedHandleCiviDiscount($params, $amount, $stripeCustomer);
-
     // Create the stripe plan
     $planId = self::createPlan($params, $amount);
 
@@ -644,16 +554,18 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
     $subscriptionParams = [
       'prorate' => FALSE,
       'plan' => $planId,
+      'default_payment_method' => $stripePaymentMethod,
     ];
     // Create the stripe subscription for the customer
     $stripeSubscription = $stripeCustomer->subscriptions->create($subscriptionParams);
+    $this->setPaymentProcessorSubscriptionID($stripeSubscription->id);
 
     $recurParams = [
       'id' => $params['contributionRecurID'],
-      'trxn_id' => $stripeSubscription->id,
+      'trxn_id' => $this->getPaymentProcessorSubscriptionID(),
       // FIXME processor_id is deprecated as it is not guaranteed to be unique, but currently (CiviCRM 5.9)
       //  it is required by cancelSubscription (where it is called subscription_id)
-      'processor_id' => $stripeSubscription->id,
+      'processor_id' => $this->getPaymentProcessorSubscriptionID(),
       'auto_renew' => 1,
       'cycle_day' => date('d'),
       'next_sched_contribution_date' => $this->calculateNextScheduledDate($params),
@@ -665,6 +577,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
       }
       if ($params['installments']) {
         $recurParams['end_date'] = $this->calculateEndDate($params);
+        $recurParams['installments'] = $params['installments'];
       }
     }
 
@@ -672,9 +585,43 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
     CRM_Stripe_Hook::updateRecurringContribution($recurParams);
     // Update the recurring payment
     civicrm_api3('ContributionRecur', 'create', $recurParams);
-    // Update the contribution status
 
-    return $params;
+    // Set the orderID (trxn_id) to the invoice ID
+    // The IPN will change it to the charge_id
+    $this->setPaymentProcessorOrderID($stripeSubscription->latest_invoice);
+    return $this->endDoPayment($params);
+  }
+
+  /**
+   * Submit a refund payment
+   *
+   * @param array $params
+   *   Assoc array of input parameters for this transaction.
+   *
+   * @throws \Civi\Payment\Exception\PaymentProcessorException
+   */
+  public function doRefund(&$params) {
+    $requiredParams = ['charge_id', 'payment_processor_id'];
+    foreach ($requiredParams as $required) {
+      if (!isset($params[$required])) {
+        $message = 'Stripe doRefund: Missing mandatory parameter: ' . $required;
+        Civi::log()->error($message);
+        Throw new \Civi\Payment\Exception\PaymentProcessorException($message);
+      }
+    }
+    $refundParams = [
+      'charge' => $params['charge_id'],
+    ];
+    if (!empty($params['amount'])) {
+      $refundParams['amount'] = $this->getAmount($params);
+    }
+    try {
+      $refund = \Stripe\Refund::create($refundParams);
+    }
+    catch (Exception $e) {
+      $this->handleError($e->getCode(), $e->getMessage());
+      Throw new \Civi\Payment\Exception\PaymentProcessorException($e->getMessage());
+    }
   }
 
   /**
@@ -770,74 +717,6 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
     return $endDate->format('Ymd');
   }
 
-  /**
-   * @deprecated This belongs in a separate extension / hook as it's non-standard CiviCRM behaviour
-   *
-   * This adds some support for CiviDiscount on recurring contributions and changes the default behavior to discounting
-   *  only the first of a recurring contribution set instead of all. (Intro offer) The Stripe procedure for discounting the
-   *  first payment of subscription entails creating a negative invoice item or negative balance first,
-   *  then creating the subscription at 100% full price. The customers first Stripe invoice will reflect the
-   * discount. Subsequent invoices will be at the full undiscounted amount.
-   * NB: Civi currently won't send a $0 charge to a payproc extension, but it should in this case. If the discount is
-   *  the cost of initial payment, we still send the whole discount (or giftcard) as a negative balance.
-   * Consider not selling giftards greater than your least expensive auto-renew membership until we can override this.
-   *
-   * @param $params
-   * @param $amount
-   * @param $stripeCustomer
-   *
-   * @return float|int
-   * @throws \CiviCRM_API3_Exception
-   */
-  public function deprecatedHandleCiviDiscount(&$params, $amount, $stripeCustomer) {
-    if (!empty($params['discountcode'])) {
-      $discount_code = $params['discountcode'];
-      $discount_object = civicrm_api3('DiscountCode', 'get', array(
-        'sequential' => 1,
-        'return' => "amount,amount_type",
-        'code' => $discount_code,
-      ));
-      // amount_types: 1 = percentage, 2 = fixed, 3 = giftcard
-      if ((!empty($discount_object['values'][0]['amount'])) && (!empty($discount_object['values'][0]['amount_type']))) {
-        $discount_type = $discount_object['values'][0]['amount_type'];
-        if ( $discount_type == 1 ) {
-          // Discount is a percentage. Avoid ugly math and just get the full price using price_ param.
-          foreach($params as $key=>$value){
-            if("price_" == substr($key,0,6)){
-              $price_param = $key;
-              $price_field_id = substr($key,strrpos($key,'_') + 1);
-            }
-          }
-          if (!empty($params[$price_param])) {
-            $priceFieldValue = civicrm_api3('PriceFieldValue', 'get', array(
-              'sequential' => 1,
-              'return' => "amount",
-              'id' => $params[$price_param],
-              'price_field_id' => $price_field_id,
-            ));
-          }
-          if (!empty($priceFieldValue['values'][0]['amount'])) {
-            $priceset_amount = $priceFieldValue['values'][0]['amount'];
-            $full_price = $priceset_amount * 100;
-            $discount_in_cents = $full_price - $amount;
-            // Set amount to full price.
-            $amount = $full_price;
-          }
-        } else if ( $discount_type >= 2 ) {
-          // discount is fixed or a giftcard. (may be > amount).
-          $discount_amount = $discount_object['values'][0]['amount'];
-          $discount_in_cents = $discount_amount * 100;
-          // Set amount to full price.
-          $amount = $amount + $discount_in_cents;
-        }
-      }
-      // Apply the disount through a negative balance.
-      $stripeCustomer->account_balance = -$discount_in_cents;
-      $stripeCustomer->save();
-    }
-    return $amount;
-  }
-
   /**
    * Default payment instrument validation.
    *
@@ -863,15 +742,15 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
 
     $contributionRecurId = $this->getRecurringContributionId($params);
     try {
-      $contributionRecur = civicrm_api3('ContributionRecur', 'getsingle', array(
+      $contributionRecur = civicrm_api3('ContributionRecur', 'getsingle', [
         'id' => $contributionRecurId,
-      ));
+      ]);
     }
     catch (Exception $e) {
       return FALSE;
     }
     if (empty($contributionRecur['trxn_id'])) {
-      CRM_Core_Session::setStatus(ts('The recurring contribution cannot be cancelled (No reference (trxn_id) found).'), 'Smart Debit', 'error');
+      CRM_Core_Session::setStatus(E::ts('The recurring contribution cannot be cancelled (No reference (trxn_id) found).'), 'Smart Debit', 'error');
       return FALSE;
     }
 
diff --git a/CRM/Core/Payment/StripeIPN.php b/CRM/Core/Payment/StripeIPN.php
index 763dc7c8c5c978c8cb6bf8dbd04e3d905e56c5d3..5d096c55015e53c4f21bd0aaa49ecad7d125d858 100644
--- a/CRM/Core/Payment/StripeIPN.php
+++ b/CRM/Core/Payment/StripeIPN.php
@@ -1,12 +1,19 @@
 <?php
-/*
- * @file
- * Handle Stripe Webhooks for recurring payments.
+/**
+ * https://civicrm.org/licensing
  */
 
+/**
+ * Class CRM_Core_Payment_StripeIPN
+ */
 class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN {
 
-  use CRM_Core_Payment_StripeIPNTrait;
+  use CRM_Core_Payment_MJWIPNTrait;
+
+  /**
+   * @var \CRM_Core_Payment_Stripe Payment processor
+   */
+  protected $_paymentProcessor;
 
   /**
    * Transaction ID is the contribution in the redirect flow and a random number in the on-site->POST flow
@@ -16,7 +23,7 @@ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN {
   protected $transaction_id;
 
   // By default, always retrieve the event from stripe to ensure we are
-  // not being fed garbage. However, allow an override so when we are 
+  // not being fed garbage. However, allow an override so when we are
   // testing, we can properly test a failed recurring contribution.
   protected $verify_event = TRUE;
 
@@ -39,7 +46,7 @@ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN {
   protected $frequency_unit = NULL;
   protected $plan_name = NULL;
   protected $plan_start = NULL;
-       
+
   // Derived properties.
   protected $contribution_recur_id = NULL;
   protected $event_id = NULL;
@@ -47,16 +54,14 @@ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN {
   protected $receive_date = NULL;
   protected $amount = NULL;
   protected $fee = NULL;
-  protected $net_amount = NULL;
-  protected $previous_contribution = [];
+  protected $contribution = NULL;
+  protected $previous_contribution = NULL;
 
   /**
    * CRM_Core_Payment_StripeIPN constructor.
    *
-   * @param $ipnData
+   * @param \stdClass $ipnData
    * @param bool $verify
-   *
-   * @throws \CRM_Core_Exception
    */
   public function __construct($ipnData, $verify = TRUE) {
     $this->verify_event = $verify;
@@ -64,53 +69,12 @@ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN {
     parent::__construct();
   }
 
-  /**
-   * Set the value of is_email_receipt to use when a new contribution is received for a recurring contribution
-   * This is used for the API Stripe.Ipn function.  If not set, we respect the value set on the ContributionRecur entity.
-   *
-   * @param int $sendReceipt The value of is_email_receipt
-   */
-  public function setSendEmailReceipt($sendReceipt) {
-    switch ($sendReceipt) {
-      case 0:
-        $this->is_email_receipt = 0;
-        break;
-
-      case 1:
-        $this->is_email_receipt = 1;
-        break;
-
-      default:
-        $this->is_email_receipt = 0;
-    }
-  }
-
-  /**
-   * Get the value of is_email_receipt to use when a new contribution is received for a recurring contribution
-   * This is used for the API Stripe.Ipn function.  If not set, we respect the value set on the ContributionRecur entity.
-   *
-   * @return int
-   * @throws \CiviCRM_API3_Exception
-   */
-  public function getSendEmailReceipt() {
-    if (isset($this->is_email_receipt)) {
-      return (int) $this->is_email_receipt;
-    }
-    if (!empty($this->contribution_recur_id)) {
-      $this->is_email_receipt = civicrm_api3('ContributionRecur', 'getvalue', [
-        'return' => "is_email_receipt",
-        'id' => $this->contribution_recur_id,
-      ]);
-    }
-    return (int) $this->is_email_receipt;
-  }
-
   /**
    * Store input array on the class.
    * We override base because our input parameter is an object
    *
    * @param array $parameters
-  */
+   */
   public function setInputParameters($parameters) {
     if (!is_object($parameters)) {
       $this->exception('Invalid input parameters');
@@ -119,14 +83,13 @@ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN {
     // Determine the proper Stripe Processor ID so we can get the secret key
     // and initialize Stripe.
     $this->getPaymentProcessor();
+    $this->_paymentProcessor->setAPIParams();
 
     // Now re-retrieve the data from Stripe to ensure it's legit.
-    \Stripe\Stripe::setApiKey($this->_paymentProcessor['user_name']);
-
     // Special case if this is the test webhook
     if (substr($parameters->id, -15, 15) === '_00000000000000') {
       http_response_code(200);
-      $test = (boolean) $this->_paymentProcessor['is_test'] ? '(Test processor)' : '(Live processor)';
+      $test = (boolean) $this->_paymentProcessor->getPaymentProcessor()['is_test'] ? '(Test processor)' : '(Live processor)';
       echo "Test webhook from Stripe ({$parameters->id}) received successfully by CiviCRM {$test}.";
       exit();
     }
@@ -155,168 +118,150 @@ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN {
 
     $value = CRM_Utils_Type::validate($value, $type, FALSE);
     if ($abort && $value === NULL) {
-      echo "Failure: Missing Parameter<p>" . CRM_Utils_Type::escape($name, 'String');
-      $this->exception("Could not find an entry for $name");
+      echo "Failure: Missing or invalid parameter<p>" . CRM_Utils_Type::escape($name, 'String');
+      $this->exception("Missing or invalid parameter {$name}");
     }
     return $value;
   }
 
   /**
+   * @return bool
    * @throws \CRM_Core_Exception
    * @throws \CiviCRM_API3_Exception
+   * @throws \Stripe\Error\Api
    */
   public function main() {
     // Collect and determine all data about this event.
     $this->event_type = CRM_Stripe_Api::getParam('event_type', $this->_inputParameters);
-
     $pendingStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending');
 
     // NOTE: If you add an event here make sure you add it to the webhook or it will never be received!
     switch($this->event_type) {
-      // Successful recurring payment.
       case 'invoice.payment_succeeded':
+        // Successful recurring payment. Either we are completing an existing contribution or it's the next one in a subscription
         $this->setInfo();
-        if ($this->previous_contribution['contribution_status_id'] == $pendingStatusId) {
-          $this->completeContribution();
+        if ($this->contribution['contribution_status_id'] == $pendingStatusId) {
+          $params = [
+            'id' => $this->contribution['id'],
+            'trxn_date' => $this->receive_date,
+            'contribution_trxn_id' => $this->invoice_id,
+            'payment_trxn_id' => $this->charge_id,
+            'total_amount' => $this->amount,
+            'fee_amount' => $this->fee,
+          ];
+          $this->updateContributionCompleted($params);
+          // Don't touch the contributionRecur as it's updated automatically by Contribution.completetransaction
         }
-        elseif ($this->previous_contribution['trxn_id'] != $this->charge_id) {
-          // The first contribution was completed, so create a new one.
-          // api contribution repeattransaction repeats the appropriate contribution if it is given
-          // simply the recurring contribution id. It also updates the membership for us.
-          civicrm_api3('Contribution', 'repeattransaction', array(
+        elseif ($this->contribution['trxn_id'] != $this->invoice_id) {
+          // Stripe has generated a new invoice (next payment in a subscription) so we
+          //   create a new contribution in CiviCRM
+          $params = [
             'contribution_recur_id' => $this->contribution_recur_id,
             'contribution_status_id' => 'Completed',
             'receive_date' => $this->receive_date,
-            'trxn_id' => $this->charge_id,
+            'contribution_trxn_id' => $this->invoice_id,
+            'payment_trxn_id' => $this->charge_id,
             'total_amount' => $this->amount,
             'fee_amount' => $this->fee,
-            'is_email_receipt' => $this->getSendEmailReceipt(),
-          ));
+            'previous_contribution' => $this->previous_contribution,
+          ];
+          $this->repeatContribution($params);
+          // Don't touch the contributionRecur as it's updated automatically by Contribution.repeattransaction
         }
-
-        // Successful charge & more to come. 
-        civicrm_api3('ContributionRecur', 'create', array(
-          'id' => $this->contribution_recur_id,
-          'failure_count' => 0,
-          'contribution_status_id' => 'In Progress'
-        ));
+        $this->handleInstallmentsForSubscription();
         return TRUE;
 
-      // Failed recurring payment.
       case 'invoice.payment_failed':
+        // Failed recurring payment. Either we are failing an existing contribution or it's the next one in a subscription
         $this->setInfo();
-        $failDate = date('YmdHis');
 
-        if ($this->previous_contribution['contribution_status_id'] == $pendingStatusId) {
+        if ($this->contribution['contribution_status_id'] == $pendingStatusId) {
           // If this contribution is Pending, set it to Failed.
-          civicrm_api3('Contribution', 'create', array(
-            'id' => $this->previous_contribution['id'],
-            'contribution_status_id' => "Failed",
-            'receive_date' => $failDate,
-            'is_email_receipt' => 0,
-          ));
+          $params = [
+            'id' => $this->contribution['id'],
+            'receive_date' => $this->receive_date,
+            'cancel_reason' => $this->retrieve('failure_message', 'String'),
+            'payment_trxn_id' => $this->charge_id,
+          ];
+          $this->updateContributionFailed($params);
         }
-        else {
-          $contributionParams = [
+        elseif ($this->contribution['trxn_id'] != $this->invoice_id) {
+          $params = [
             'contribution_recur_id' => $this->contribution_recur_id,
             'contribution_status_id' => 'Failed',
-            'receive_date' => $failDate,
+            'receive_date' => $this->receive_date,
+            'contribution_trxn_id' => $this->invoice_id,
+            'payment_trxn_id' => $this->charge_id,
             'total_amount' => $this->amount,
-            'is_email_receipt' => 0,
+            'fee_amount' => $this->fee,
+            'previous_contribution' => $this->previous_contribution,
           ];
-          civicrm_api3('Contribution', 'repeattransaction', $contributionParams);
+          $this->repeatContribution($params);
+          // Don't touch the contributionRecur as it's updated automatically by Contribution.completetransaction
         }
-
-        $failureCount = civicrm_api3('ContributionRecur', 'getvalue', array(
-         'id' => $this->contribution_recur_id,
-         'return' => 'failure_count',
-        ));
-        $failureCount++;
-
-        // Change the status of the Recurring and update failed attempts.
-        civicrm_api3('ContributionRecur', 'create', array(
-          'id' => $this->contribution_recur_id,
-          'contribution_status_id' => "Failed",
-          'failure_count' => $failureCount,
-          'modified_date' => $failDate,
-        ));
         return TRUE;
 
-      // Subscription is cancelled
       case 'customer.subscription.deleted':
+        // Subscription is cancelled
         $this->setInfo();
         // Cancel the recurring contribution
-        civicrm_api3('ContributionRecur', 'cancel', array(
-          'id' => $this->contribution_recur_id,
-        ));
+        $this->updateRecurCancelled(['id' => $this->contribution_recur_id, 'cancel_date' => $this->retrieve('cancel_date', 'String', FALSE)]);
         return TRUE;
 
       // One-time donation and per invoice payment.
       case 'charge.failed':
-        $chargeId = $this->retrieve('charge_id', 'String');
-        $failureCode = $this->retrieve('failure_code', 'String');
-        $failureMessage = $this->retrieve('failure_message', 'String');
-        $contribution = civicrm_api3('Contribution', 'getsingle', ['trxn_id' => $chargeId]);
-        $failedStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Failed');
-        if ($contribution['contribution_status_id'] != $failedStatusId) {
-          $note = $failureCode . ' : ' . $failureMessage;
-          civicrm_api3('Contribution', 'create', ['id' => $contribution['id'], 'contribution_status_id' => $failedStatusId, 'note' => $note]);
-        }
+        $this->setInfo();
+        $params = [
+          'id' => $this->contribution['id'],
+          'receive_date' => $this->receive_date,
+          'cancel_reason' => $this->retrieve('failure_message', 'String'),
+          'payment_trxn_id' => $this->charge_id,
+        ];
+        $this->updateContributionFailed($params);
         return TRUE;
 
       case 'charge.refunded':
-        $chargeId = $this->retrieve('charge_id', 'String');
-        $refunded = $this->retrieve('refunded', 'Boolean');
-        $refundAmount = $this->retrieve('amount_refunded', 'Integer');
-        $contribution = civicrm_api3('Contribution', 'getsingle', ['trxn_id' => $chargeId]);
-        if ($refunded) {
-          $refundedStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Refunded');
-          if ($contribution['contribution_status_id'] != $refundedStatusId) {
-            civicrm_api3('Contribution', 'create', [
-              'id' => $contribution['id'],
-              'contribution_status_id' => $refundedStatusId
-            ]);
-          }
-          elseif ($refundAmount > 0) {
-            $partiallyRefundedStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Partially Refunded');
-            if ($contribution['contribution_status_id'] != $partiallyRefundedStatusId) {
-              civicrm_api3('Contribution', 'create', [
-                'id' => $contribution['id'],
-                'contribution_status_id' => $refundedStatusId
-              ]);
-            }
-          }
-        }
+        $this->setInfo();
+        $refunds = \Stripe\Refund::all(['charge' => $this->charge_id, 'limit' => 1]);
+        $params = [
+          'id' => $this->contribution['id'],
+          'total_amount' => $this->retrieve('amount_refunded', 'Float'),
+          'cancel_reason' => $refunds->data[0]->reason,
+          'cancel_date' => date('YmdHis', $refunds->data[0]->created),
+        ];
+        $this->updateContributionRefund($params);
         return TRUE;
 
       case 'charge.succeeded':
         $this->setInfo();
-        if ($this->previous_contribution['contribution_status_id'] == $pendingStatusId) {
-          $this->completeContribution();
+        if ($this->contribution['contribution_status_id'] == $pendingStatusId) {
+          $params = [
+            'id' => $this->contribution['id'],
+            'trxn_date' => $this->receive_date,
+            'contribution_trxn_id' => $this->charge_id,
+            'payment_trxn_id' => $this->charge_id,
+            'total_amount' => $this->amount,
+            'fee_amount' => $this->fee,
+          ];
+          $this->updateContributionCompleted($params);
         }
         return TRUE;
 
       case 'customer.subscription.updated':
-       $this->setInfo();
-       if (empty($this->previous_plan_id)) {
-         // Not a plan change...don't care.
-         return TRUE;
-       }
-
-       civicrm_api3('ContributionRecur', 'create', [
-         'id' => $this->contribution_recur_id,
+        $this->setInfo();
+        if (empty($this->previous_plan_id)) {
+          // Not a plan change...don't care.
+          return TRUE;
+        }
+
+        civicrm_api3('ContributionRecur', 'create', [
+          'id' => $this->contribution_recur_id,
           'amount' => $this->plan_amount,
           'auto_renew' => 1,
           'created_date' => $this->plan_start,
           'frequency_unit' => $this->frequency_unit,
           'frequency_interval' => $this->frequency_interval,
-       ]);
-
-       civicrm_api3('Contribution', 'create', [
-          'id' => $this->previous_contribution['id'],
-          'total_amount' => $this->plan_amount,
-          'contribution_recur_id' => $this->contribution_recur_id,
-       ]);
+        ]);
         return TRUE;
     }
     // Unhandled event type.
@@ -324,42 +269,17 @@ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN {
   }
 
   /**
-   * Complete a pending contribution and update associated entities (recur/membership)
-   *
-   * @throws \CiviCRM_API3_Exception
-   */
-  public function completeContribution() {
-    // Update the contribution to include the fee.
-    civicrm_api3('Contribution', 'create', array(
-      'id' => $this->previous_contribution['id'],
-      'total_amount' => $this->amount,
-      'fee_amount' => $this->fee,
-      'net_amount' => $this->net_amount,
-    ));
-    // The last one was not completed, so complete it.
-    civicrm_api3('Contribution', 'completetransaction', array(
-      'id' => $this->previous_contribution['id'],
-      'trxn_date' => $this->receive_date,
-      'trxn_id' => $this->charge_id,
-      'total_amount' => $this->amount,
-      'net_amount' => $this->net_amount,
-      'fee_amount' => $this->fee,
-      'payment_processor_id' => $this->_paymentProcessor['id'],
-      'is_email_receipt' => $this->getSendEmailReceipt(),
-    ));
-  }
-
-    /**
    * Gather and set info as class properties.
    *
    * Given the data passed to us via the Stripe Event, try to determine
-   * as much as we can about this event and set that information as 
+   * as much as we can about this event and set that information as
    * properties to be used later.
    *
    * @throws \CRM_Core_Exception
    */
   public function setInfo() {
     $abort = FALSE;
+    $stripeObjectName = get_class($this->_inputParameters->data->object);
     $this->customer_id = CRM_Stripe_Api::getObjectParam('customer_id', $this->_inputParameters->data->object);
     if (empty($this->customer_id)) {
       $this->exception('Missing customer_id!');
@@ -377,34 +297,30 @@ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN {
     $this->plan_name = $this->retrieve('plan_name', 'String', $abort);
     $this->plan_start = $this->retrieve('plan_start', 'String', $abort);
 
+    if (($stripeObjectName !== 'Stripe\Charge') && ($this->charge_id !== NULL)) {
+      $charge = \Stripe\Charge::retrieve($this->charge_id);
+      $balanceTransactionID = CRM_Stripe_Api::getObjectParam('balance_transaction', $charge);
+    }
+    else {
+      $charge = $this->_inputParameters->data->object;
+      $balanceTransactionID = CRM_Stripe_Api::getObjectParam('balance_transaction', $this->_inputParameters->data->object);
+    }
     // Gather info about the amount and fee.
     // Get the Stripe charge object if one exists. Null charge still needs processing.
-    if ($this->charge_id !== null) {
+    // If the transaction is declined, there won't be a balance_transaction_id.
+    $this->amount = 0;
+    $this->fee = 0;
+    if ($balanceTransactionID) {
       try {
-        $charge = \Stripe\Charge::retrieve($this->charge_id);
-        $balance_transaction_id = $charge->balance_transaction;
-        // If the transaction is declined, there won't be a balance_transaction_id.
-        if ($balance_transaction_id) {
-          $balance_transaction = \Stripe\BalanceTransaction::retrieve($balance_transaction_id);
-          $this->amount = $charge->amount / 100;
-          $this->fee = $balance_transaction->fee / 100;
-        }
-        else {
-          $this->amount = 0;
-          $this->fee = 0;
-        }
+        $balanceTransaction = \Stripe\BalanceTransaction::retrieve($balanceTransactionID);
+        $this->amount = $charge->amount / 100;
+        $this->fee = $balanceTransaction->fee / 100;
       }
       catch(Exception $e) {
-        $this->exception('Cannot get contribution amounts');
+        $this->exception('Error retrieving balance transaction. ' . $e->getMessage());
       }
-    } else {
-      // The customer had a credit on their subscription from a downgrade or gift card.
-      $this->amount = 0;
-      $this->fee = 0;
     }
 
-    $this->net_amount = $this->amount - $this->fee;
-
     // Additional processing of values is only relevant if there is a subscription id.
     if ($this->subscription_id) {
       // Get the recurring contribution record associated with the Stripe subscription.
@@ -416,28 +332,81 @@ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN {
         $this->exception('Cannot find recurring contribution for subscription ID: ' . $this->subscription_id . '. ' . $e->getMessage());
       }
     }
-    // If a recurring contribution has been found, get the most recent contribution belonging to it.
-    if ($this->contribution_recur_id) {
+
+    if ($this->charge_id) {
+      try {
+        $this->contribution = civicrm_api3('Contribution', 'getsingle', [
+          'trxn_id' => $this->charge_id,
+          'contribution_test' => $this->_paymentProcessor->getIsTestMode(),
+        ]);
+      }
+      catch (Exception $e) {
+        // Contribution not found - that's ok
+      }
+    }
+    if (!$this->contribution && $this->invoice_id) {
+      try {
+        $this->contribution = civicrm_api3('Contribution', 'getsingle', [
+          'trxn_id' => $this->invoice_id,
+          'contribution_test' => $this->_paymentProcessor->getIsTestMode(),
+        ]);
+      }
+      catch (Exception $e) {
+        // Contribution not found - that's ok
+      }
+    }
+    if (!$this->contribution && $this->contribution_recur_id) {
+      // If a recurring contribution has been found, get the most recent contribution belonging to it.
       try {
         // Same approach as api repeattransaction.
-        $contribution = civicrm_api3('contribution', 'getsingle', array(
-          'return' => array('id', 'contribution_status_id', 'total_amount', 'trxn_id'),
+        $contribution = civicrm_api3('contribution', 'getsingle', [
+          'return' => ['id', 'contribution_status_id', 'total_amount', 'trxn_id'],
           'contribution_recur_id' => $this->contribution_recur_id,
-          'contribution_test' => isset($this->_paymentProcessor['is_test']) && $this->_paymentProcessor['is_test'] ? 1 : 0,
-          'options' => array('limit' => 1, 'sort' => 'id DESC'),
-        ));
+          'contribution_test' => $this->_paymentProcessor->getIsTestMode(),
+          'options' => ['limit' => 1, 'sort' => 'id DESC'],
+        ]);
         $this->previous_contribution = $contribution;
       }
       catch (Exception $e) {
         $this->exception('Cannot find any contributions with recurring contribution ID: ' . $this->contribution_recur_id . '. ' . $e->getMessage());
       }
     }
+    if (!$this->contribution) {
+      $this->exception('No matching contributions for event ' . CRM_Stripe_Api::getParam('id', $this->_inputParameters));
+    }
   }
 
-  public function exception($message) {
-    $errorMessage = 'StripeIPN Exception: Event: ' . $this->event_type . ' Error: ' . $message;
-    Civi::log()->debug($errorMessage);
-    http_response_code(400);
-    exit(1);
+  /**
+   * This allows us to end a subscription once:
+   *   a) We've reached the end date / number of installments
+   *   b) The recurring contribution is marked as completed
+   *
+   * @throws \CiviCRM_API3_Exception
+   */
+  private function handleInstallmentsForSubscription() {
+    if ((!$this->contribution_recur_id) || (!$this->subscription_id)) {
+      return;
+    }
+
+    $contributionRecur = civicrm_api3('ContributionRecur', 'getsingle', [
+      'id' => $this->contribution_recur_id,
+    ]);
+
+    if (empty($contributionRecur['installments']) && empty($contributionRecur['end_date'])) {
+      return;
+    }
+
+    $stripeSubscription = \Stripe\Subscription::retrieve($this->subscription_id);
+    // If we've passed the end date cancel the subscription
+    if (($stripeSubscription->current_period_end >= strtotime($contributionRecur['end_date']))
+      || ($contributionRecur['contribution_status_id']
+        == CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_ContributionRecur', 'contribution_status_id', 'Completed'))) {
+      \Stripe\Subscription::update($this->subscription_id, ['cancel_at_period_end' => TRUE]);
+      $this->updateRecurCompleted(['id' => $this->contribution_recur_id]);
+    }
+    // There is no easy way of retrieving a count of all invoices for a subscription so we ignore the "installments"
+    //   parameter for now and rely on checking end_date (which was calculated based on number of installments...)
+    // $stripeInvoices = \Stripe\Invoice::all(['subscription' => $this->subscription_id, 'limit' => 100]);
   }
+
 }
diff --git a/CRM/Core/Payment/StripeIPNTrait.php b/CRM/Core/Payment/StripeIPNTrait.php
deleted file mode 100644
index 86cef1b1c0016376a94c5910b51775ccc23b8841..0000000000000000000000000000000000000000
--- a/CRM/Core/Payment/StripeIPNTrait.php
+++ /dev/null
@@ -1,108 +0,0 @@
-<?php
-/**
- * Shared payment IPN functions that should one day be migrated to CiviCRM core
- * Version: 20190304
- */
-
-trait CRM_Core_Payment_StripeIPNTrait {
-
-  /**
-   * @var array Payment processor
-   */
-  private $_paymentProcessor;
-
-  /**
-   * Get the payment processor
-   *   The $_GET['processor_id'] value is set by CRM_Core_Payment::handlePaymentMethod.
-   */
-  protected function getPaymentProcessor() {
-    $paymentProcessorId = (int) CRM_Utils_Array::value('processor_id', $_GET);
-    if (empty($paymentProcessorId)) {
-      $this->exception('Failed to get payment processor id');
-    }
-
-    try {
-      $this->_paymentProcessor = \Civi\Payment\System::singleton()->getById($paymentProcessorId)->getPaymentProcessor();
-    }
-    catch(Exception $e) {
-      $this->exception('Failed to get payment processor');
-    }
-  }
-
-  /**
-   * Mark a contribution as cancelled and update related entities
-   *
-   * @param array $params [ 'id' -> contribution_id, 'payment_processor_id' -> payment_processor_id]
-   *
-   * @return bool
-   * @throws \CiviCRM_API3_Exception
-   */
-  protected function canceltransaction($params) {
-    return $this->incompletetransaction($params, 'cancel');
-  }
-
-  /**
-   * Mark a contribution as failed and update related entities
-   *
-   * @param array $params [ 'id' -> contribution_id, 'payment_processor_id' -> payment_processor_id]
-   *
-   * @return bool
-   * @throws \CiviCRM_API3_Exception
-   */
-  protected function failtransaction($params) {
-    return $this->incompletetransaction($params, 'fail');
-  }
-
-  /**
-   * Handler for failtransaction and canceltransaction - do not call directly
-   *
-   * @param array $params
-   * @param string $mode
-   *
-   * @return bool
-   * @throws \CiviCRM_API3_Exception
-   */
-  protected function incompletetransaction($params, $mode) {
-    $requiredParams = ['id', 'payment_processor_id'];
-    foreach ($requiredParams as $required) {
-      if (!isset($params[$required])) {
-        $this->exception('canceltransaction: Missing mandatory parameter: ' . $required);
-      }
-    }
-
-    if (isset($params['payment_processor_id'])) {
-      $input['payment_processor_id'] = $params['payment_processor_id'];
-    }
-    $contribution = new CRM_Contribute_BAO_Contribution();
-    $contribution->id = $params['id'];
-    if (!$contribution->find(TRUE)) {
-      throw new CiviCRM_API3_Exception('A valid contribution ID is required', 'invalid_data');
-    }
-
-    if (!$contribution->loadRelatedObjects($input, $ids, TRUE)) {
-      throw new CiviCRM_API3_Exception('failed to load related objects');
-    }
-
-    $input['trxn_id'] = !empty($params['trxn_id']) ? $params['trxn_id'] : $contribution->trxn_id;
-    if (!empty($params['fee_amount'])) {
-      $input['fee_amount'] = $params['fee_amount'];
-    }
-
-    $objects['contribution'] = &$contribution;
-    $objects = array_merge($objects, $contribution->_relatedObjects);
-
-    $transaction = new CRM_Core_Transaction();
-    switch ($mode) {
-      case 'cancel':
-        return $this->cancelled($objects, $transaction);
-
-      case 'fail':
-        return $this->failed($objects, $transaction);
-
-      default:
-        throw new CiviCRM_API3_Exception('Unknown incomplete transaction type: ' . $mode);
-    }
-
-  }
-
-}
diff --git a/CRM/Core/Payment/StripeTrait.php b/CRM/Core/Payment/StripeTrait.php
deleted file mode 100644
index a5221c43a0240923450900ba96f30ead8d0b2a7e..0000000000000000000000000000000000000000
--- a/CRM/Core/Payment/StripeTrait.php
+++ /dev/null
@@ -1,170 +0,0 @@
-<?php
-/**
- * Shared payment functions that should one day be migrated to CiviCRM core
- */
-
-trait CRM_Core_Payment_StripeTrait {
-  /**********************
-   * Version 20190313
-   *********************/
-
-  /**
-   * Get the billing email address
-   *
-   * @param array $params
-   * @param int $contactId
-   *
-   * @return string|NULL
-   */
-  protected function getBillingEmail($params, $contactId) {
-    $billingLocationId = CRM_Core_BAO_LocationType::getBilling();
-
-    $emailAddress = CRM_Utils_Array::value("email-{$billingLocationId}", $params,
-      CRM_Utils_Array::value('email-Primary', $params,
-        CRM_Utils_Array::value('email', $params, NULL)));
-
-    if (empty($emailAddress) && !empty($contactId)) {
-      // Try and retrieve an email address from Contact ID
-      try {
-        $emailAddress = civicrm_api3('Email', 'getvalue', array(
-          'contact_id' => $contactId,
-          'return' => ['email'],
-        ));
-      }
-      catch (CiviCRM_API3_Exception $e) {
-        return NULL;
-      }
-    }
-    return $emailAddress;
-  }
-
-  /**
-   * Get the contact id
-   *
-   * @param array $params
-   *
-   * @return int ContactID
-   */
-  protected function getContactId($params) {
-    // contactID is set by: membership payment workflow
-    $contactId = CRM_Utils_Array::value('contactID', $params,
-      CRM_Utils_Array::value('contact_id', $params,
-        CRM_Utils_Array::value('cms_contactID', $params,
-          CRM_Utils_Array::value('cid', $params, NULL
-          ))));
-    if (!empty($contactId)) {
-      return $contactId;
-    }
-    // FIXME: Ref: https://lab.civicrm.org/extensions/stripe/issues/16
-    // The problem is that when registering for a paid event, civicrm does not pass in the
-    // contact id to the payment processor (civicrm version 5.3). So, I had to patch your
-    // getContactId to check the session for a contact id. It's a hack and probably should be fixed in core.
-    // The code below is exactly what CiviEvent does, but does not pass it through to the next function.
-    $session = CRM_Core_Session::singleton();
-    return $session->get('transaction.userID', NULL);
-  }
-
-  /**
-   * Get the contribution ID
-   *
-   * @param $params
-   *
-   * @return mixed
-   */
-  protected function getContributionId($params) {
-    /*
-     * contributionID is set in the contribution workflow
-     * We do NOT have a contribution ID for event and membership payments as they are created after payment!
-     * See: https://github.com/civicrm/civicrm-core/pull/13763 (for events)
-     */
-    return CRM_Utils_Array::value('contributionID', $params);
-  }
-
-  /**
-   * Get the recurring contribution ID from parameters passed in to cancelSubscription
-   * Historical the data passed to cancelSubscription is pretty poor and doesn't include much!
-   *
-   * @param array $params
-   *
-   * @return int|null
-   */
-  protected function getRecurringContributionId($params) {
-    // Not yet passed, but could be added via core PR
-    $contributionRecurId = CRM_Utils_Array::value('contribution_recur_id', $params);
-    if (!empty($contributionRecurId)) {
-      return $contributionRecurId;
-    }
-
-    // Not yet passed, but could be added via core PR
-    $contributionId = CRM_Utils_Array::value('contribution_id', $params);
-    try {
-      return civicrm_api3('Contribution', 'getvalue', ['id' => $contributionId, 'return' => 'contribution_recur_id']);
-    }
-    catch (Exception $e) {
-      $subscriptionId = CRM_Utils_Array::value('subscriptionId', $params);
-      if (!empty($subscriptionId)) {
-        try {
-          return civicrm_api3('ContributionRecur', 'getvalue', ['processor_id' => $subscriptionId, 'return' => 'id']);
-        }
-        catch (Exception $e) {
-          return NULL;
-        }
-      }
-      return NULL;
-    }
-  }
-
-  /**
-   *
-   * @param array $params ['name' => payment instrument name]
-   *
-   * @return int|null
-   * @throws \CiviCRM_API3_Exception
-   */
-  public static function createPaymentInstrument($params) {
-    $mandatoryParams = ['name'];
-    foreach ($mandatoryParams as $value) {
-      if (empty($params[$value])) {
-        Civi::log()->error('createPaymentInstrument: Missing mandatory parameter: ' . $value);
-        return NULL;
-      }
-    }
-
-    // Create a Payment Instrument
-    // See if we already have this type
-    $paymentInstrument = civicrm_api3('OptionValue', 'get', array(
-      'option_group_id' => "payment_instrument",
-      'name' => $params['name'],
-    ));
-    if (empty($paymentInstrument['count'])) {
-      // Otherwise create it
-      try {
-        $financialAccount = civicrm_api3('FinancialAccount', 'getsingle', [
-          'financial_account_type_id' => "Asset",
-          'name' => "Payment Processor Account",
-        ]);
-      }
-      catch (Exception $e) {
-        $financialAccount = civicrm_api3('FinancialAccount', 'getsingle', [
-          'financial_account_type_id' => "Asset",
-          'name' => "Payment Processor Account",
-          'options' => ['limit' => 1, 'sort' => "id ASC"],
-        ]);
-      }
-
-      $paymentParams = [
-        'option_group_id' => "payment_instrument",
-        'name' => $params['name'],
-        'description' => $params['name'],
-        'financial_account_id' => $financialAccount['id'],
-      ];
-      $paymentInstrument = civicrm_api3('OptionValue', 'create', $paymentParams);
-      $paymentInstrumentId = $paymentInstrument['values'][$paymentInstrument['id']]['value'];
-    }
-    else {
-      $paymentInstrumentId = $paymentInstrument['id'];
-    }
-    return $paymentInstrumentId;
-  }
-
-}
diff --git a/CRM/Stripe/AJAX.php b/CRM/Stripe/AJAX.php
new file mode 100644
index 0000000000000000000000000000000000000000..032e7cd58c8c961ff4d96fbbe0414531e2d04c91
--- /dev/null
+++ b/CRM/Stripe/AJAX.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * https://civicrm.org/licensing
+ */
+
+/**
+ * Class CRM_Stripe_AJAX
+ */
+class CRM_Stripe_AJAX {
+
+  /**
+   * Generate the paymentIntent for civicrm_stripe.js
+   *
+   * @throws \CRM_Core_Exception
+   * @throws \CiviCRM_API3_Exception
+   */
+  public static function confirmPayment() {
+    $paymentMethodID = CRM_Utils_Request::retrieveValue('payment_method_id', 'String');
+    $paymentIntentID = CRM_Utils_Request::retrieveValue('payment_intent_id', 'String');
+    $amount = CRM_Utils_Request::retrieveValue('amount', 'Money', NULL, TRUE);
+    $currency = CRM_Utils_Request::retrieveValue('currency', 'String', CRM_Core_Config::singleton()->defaultCurrency);
+    $processorID = CRM_Utils_Request::retrieveValue('id', 'Integer', NULL, TRUE);
+    $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $processorID]));
+    $processor->setAPIParams();
+
+    if (!$paymentIntentID) {
+      try {
+        $intent = \Stripe\PaymentIntent::create([
+          'payment_method' => $paymentMethodID,
+          'amount' => $processor->getAmount(['amount' => $amount]),
+          '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' => TRUE,
+        ]);
+      }
+      catch (Exception $e) {
+        CRM_Utils_JSON::output(['error' => ['message' => $e->getMessage()]]);
+      }
+    }
+
+    self::generatePaymentResponse($intent);
+  }
+
+  /**
+   * Generate the json response for civicrm_stripe.js
+   *
+   * @param \Stripe\PaymentIntent $intent
+   */
+  private static function generatePaymentResponse($intent) {
+    if ($intent->status == 'requires_action' &&
+      $intent->next_action->type == 'use_stripe_sdk') {
+      // Tell the client to handle the action
+      CRM_Utils_JSON::output([
+        'requires_action' => true,
+        'payment_intent_client_secret' => $intent->client_secret,
+      ]);
+    } else if (($intent->status == 'requires_capture') || ($intent->status == 'requires_confirmation')) {
+      // The payment intent has been confirmed, we just need to capture the payment
+      // Handle post-payment fulfillment
+      CRM_Utils_JSON::output([
+        'success' => true,
+        'paymentIntent' => ['id' => $intent->id],
+      ]);
+    } else {
+      // Invalid status
+      CRM_Utils_JSON::output(['error' => ['message' => 'Invalid PaymentIntent status']]);
+    }
+  }
+
+}
diff --git a/CRM/Stripe/Api.php b/CRM/Stripe/Api.php
index 733601512ab3595f9afac25137c86d403be2deab..ac1aa5f54ce0694bf9174509ba2fcde3a7743fa7 100644
--- a/CRM/Stripe/Api.php
+++ b/CRM/Stripe/Api.php
@@ -20,11 +20,17 @@ class CRM_Stripe_Api {
             return (bool) $stripeObject->refunded;
 
           case 'amount_refunded':
-            return (int) $stripeObject->amount_refunded / 100;
-            
+            return (float) $stripeObject->amount_refunded / 100;
+
           case 'customer_id':
             return (string) $stripeObject->customer;
 
+          case 'balance_transaction':
+            return (string) $stripeObject->balance_transaction;
+
+          case 'receive_date':
+            return self::formatDate($stripeObject->created);
+
         }
         break;
 
@@ -37,7 +43,7 @@ class CRM_Stripe_Api {
             return (string) $stripeObject->id;
 
           case 'receive_date':
-            return $stripeObject->date ? date("Y-m-d H:i:s", $stripeObject->date) : NULL;
+            return self::formatDate($stripeObject->created);
 
           case 'subscription_id':
             return (string) $stripeObject->subscription;
@@ -64,10 +70,13 @@ class CRM_Stripe_Api {
 
           case 'description':
             return (string) $stripeObject->description;
-        
+
           case 'customer_id':
             return (string) $stripeObject->customer;
 
+          case 'failure_message':
+            $stripeCharge = \Stripe\Charge::retrieve($stripeObject->charge);
+            return (string) $stripeCharge->failure_message;
 
         }
         break;
@@ -93,7 +102,10 @@ class CRM_Stripe_Api {
             return (string) $stripeObject->plan->name;
 
           case 'plan_start':
-            return $stripeObject->start ? date("Y-m-d H:i:s", $stripeObject->start) : NULL;
+            return self::formatDate($stripeObject->start_date);
+
+          case 'cancel_date':
+            return self::formatDate($stripeObject->canceled_at);
 
           case 'cycle_day':
             return date("d", $stripeObject->billing_cycle_anchor);
@@ -119,6 +131,16 @@ class CRM_Stripe_Api {
     return NULL;
   }
 
+  /**
+   * Return a formatted date from a stripe timestamp or NULL if not set
+   * @param int $stripeTimestamp
+   *
+   * @return string|null
+   */
+  private static function formatDate($stripeTimestamp) {
+    return $stripeTimestamp ? date('YmdHis', $stripeTimestamp) : NULL;
+  }
+
   public static function getParam($name, $stripeObject) {
     // Common parameters
     switch ($name) {
@@ -128,6 +150,9 @@ class CRM_Stripe_Api {
       case 'event_type':
         return (string) $stripeObject->type;
 
+      case 'id':
+        return (string) $stripeObject->id;
+
       case 'previous_plan_id':
         if (preg_match('/\.updated$/', $stripeObject->type)) {
           return (string) $stripeObject->data->previous_attributes->plan->id;
diff --git a/CRM/Stripe/Check.php b/CRM/Stripe/Check.php
new file mode 100644
index 0000000000000000000000000000000000000000..c453cfef130307e86d16d948151212b8916329d6
--- /dev/null
+++ b/CRM/Stripe/Check.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * https://civicrm.org/licensing
+ */
+
+use CRM_Stripe_ExtensionUtil as E;
+
+/**
+ * Class CRM_Stripe_Check
+ */
+class CRM_Stripe_Check {
+
+  const MIN_VERSION_MJWSHARED = 0.3;
+
+  public static function checkRequirements(&$messages) {
+    $extensions = civicrm_api3('Extension', 'get', [
+      'full_name' => "mjwshared",
+    ]);
+
+    if (empty($extensions['id']) || ($extensions['values'][$extensions['id']]['status'] !== 'installed')) {
+      $messages[] = new CRM_Utils_Check_Message(
+        'stripe_requirements',
+        E::ts('The Stripe extension requires the mjwshared extension which is not installed (https://lab.civicrm.org/extensions/mjwshared).'),
+        E::ts('Stripe: Missing Requirements'),
+        \Psr\Log\LogLevel::ERROR,
+        'fa-money'
+      );
+    }
+
+    if (version_compare($extensions['values'][$extensions['id']]['version'], self::MIN_VERSION_MJWSHARED) === -1) {
+      $messages[] = new CRM_Utils_Check_Message(
+        'stripe_requirements',
+        E::ts('The Stripe extension requires the mjwshared extension version %1 or greater but your system has version %2.',
+          [
+            1 => self::MIN_VERSION_MJWSHARED,
+            2 => $extensions['values'][$extensions['id']]['version']
+          ]),
+        E::ts('Stripe: Missing Requirements'),
+        \Psr\Log\LogLevel::ERROR,
+        'fa-money'
+      );
+    }
+  }
+
+}
diff --git a/CRM/Stripe/Customer.php b/CRM/Stripe/Customer.php
index 50b787f8ea257d49c1a34d5f90e8680628ddfed8..c974c25c52d6ee0c71ce099292e78565111ed210 100644
--- a/CRM/Stripe/Customer.php
+++ b/CRM/Stripe/Customer.php
@@ -24,7 +24,7 @@ class CRM_Stripe_Customer {
       1 => [$params['contact_id'], 'String'],
       2 => [$params['processor_id'], 'Positive'],
     ];
-    
+
 
     return CRM_Core_DAO::singleValueQuery("SELECT id
       FROM civicrm_stripe_customers
@@ -108,39 +108,29 @@ class CRM_Stripe_Customer {
   }
 
   /**
-   * @param $params
+   * @param array $params
+   * @param \CRM_Core_Payment_Stripe $stripe
    *
    * @return \Stripe\ApiResource
    * @throws \CiviCRM_API3_Exception
    * @throws \Civi\Payment\Exception\PaymentProcessorException
    */
-  public static function create($params) {
-    $requiredParams = ['contact_id', 'card_token', 'processor_id'];
-    // $optionalParams = ['email'];
+  public static function create($params, $stripe) {
+    $requiredParams = ['contact_id', 'processor_id'];
     foreach ($requiredParams as $required) {
       if (empty($params[$required])) {
         throw new \Civi\Payment\Exception\PaymentProcessorException('Stripe Customer (create): Missing required parameter: ' . $required);
       }
     }
 
-    $contactDisplayName = civicrm_api3('Contact', 'getvalue', [
-      'return' => 'display_name',
-      'id' => $params['contact_id'],
-    ]);
-
-    $stripeCustomerParams = [
-      'description' => $contactDisplayName . ' (CiviCRM)',
-      'card' => $params['card_token'],
-      'email' => CRM_Utils_Array::value('email', $params),
-      'metadata' => ['civicrm_contact_id' => $params['contact_id']],
-    ];
+    $stripeCustomerParams = self::getStripeCustomerMetadata($params);
 
     try {
       $stripeCustomer = \Stripe\Customer::create($stripeCustomerParams);
     }
     catch (Exception $e) {
       $err = CRM_Core_Payment_Stripe::parseStripeException('create_customer', $e, FALSE);
-      $errorMessage = CRM_Core_Payment_Stripe::handleErrorNotification($err, $params['stripe_error_url']);
+      $errorMessage = $stripe->handleErrorNotification($err, $params['stripe_error_url']);
       throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to create Stripe Customer: ' . $errorMessage);
     }
 
@@ -155,6 +145,61 @@ class CRM_Stripe_Customer {
     return $stripeCustomer;
   }
 
+  /**
+   * @param array $params
+   * @param \CRM_Core_Payment_Stripe $stripe
+   * @param string $stripeCustomerID
+   *
+   * @return \Stripe\Customer
+   * @throws \CiviCRM_API3_Exception
+   * @throws \Civi\Payment\Exception\PaymentProcessorException
+   */
+  public static function updateMetadata($params, $stripe, $stripeCustomerID) {
+    $requiredParams = ['contact_id', 'processor_id'];
+    foreach ($requiredParams as $required) {
+      if (empty($params[$required])) {
+        throw new \Civi\Payment\Exception\PaymentProcessorException('Stripe Customer (updateMetadata): Missing required parameter: ' . $required);
+      }
+    }
+
+    $stripeCustomerParams = self::getStripeCustomerMetadata($params);
+
+    try {
+      $stripeCustomer = \Stripe\Customer::update($stripeCustomerID, $stripeCustomerParams);
+    }
+    catch (Exception $e) {
+      $err = CRM_Core_Payment_Stripe::parseStripeException('create_customer', $e, FALSE);
+      $errorMessage = $stripe->handleErrorNotification($err, $params['stripe_error_url']);
+      throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to update Stripe Customer: ' . $errorMessage);
+    }
+    return $stripeCustomer;
+  }
+
+  /**
+   * @param array $params
+   *   Required: contact_id; Optional: email
+   *
+   * @return array
+   * @throws \CiviCRM_API3_Exception
+   */
+  private static function getStripeCustomerMetadata($params) {
+    $contactDisplayName = civicrm_api3('Contact', 'getvalue', [
+      'return' => 'display_name',
+      'id' => $params['contact_id'],
+    ]);
+
+    $stripeCustomerParams = [
+      'name' => $contactDisplayName,
+      'description' => 'CiviCRM: ' . civicrm_api3('Domain', 'getvalue', ['current_domain' => 1, 'return' => 'name']),
+      'email' => CRM_Utils_Array::value('email', $params),
+      'metadata' => [
+        'CiviCRM Contact ID' => $params['contact_id'],
+        'CiviCRM URL' => CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$params['contact_id']}", TRUE),
+      ],
+    ];
+    return $stripeCustomerParams;
+  }
+
   /**
    * Delete a Stripe customer from the CiviCRM database
    *
diff --git a/CRM/Stripe/Upgrader.php b/CRM/Stripe/Upgrader.php
index 70bfa5c295e4ddc66506b53262cc7ecc132f96df..30aacea59a5a12d2733e86d960f96051b9788cfd 100644
--- a/CRM/Stripe/Upgrader.php
+++ b/CRM/Stripe/Upgrader.php
@@ -1,8 +1,8 @@
 <?php
 /**
  * Collection of upgrade steps.
- * DO NOT USE a naming scheme other than upgrade_N, where N is an integer.  
- * Naming scheme upgrade_X_Y_Z is offically wrong!  
+ * DO NOT USE a naming scheme other than upgrade_N, where N is an integer.
+ * Naming scheme upgrade_X_Y_Z is offically wrong!
  * https://chat.civicrm.org/civicrm/pl/usx3pfjzjbrhzpewuggu1e6ftw
  */
 class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base {
@@ -56,7 +56,7 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base {
       CRM_Core_DAO::executeQuery('ALTER TABLE civicrm_stripe_plans ADD COLUMN `processor_id` int(10) DEFAULT NULL COMMENT "ID from civicrm_payment_processor"');
       CRM_Core_DAO::executeQuery('ALTER TABLE civicrm_stripe_subscriptions ADD COLUMN `processor_id` int(10) DEFAULT NULL COMMENT "ID from civicrm_payment_processor"');
     }
-     return TRUE;
+    return TRUE;
   }
 
 
@@ -70,14 +70,14 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base {
     $config = CRM_Core_Config::singleton();
     $dbName = DB::connect($config->dsn)->_db;
 
-    $null_count =  CRM_Core_DAO::executeQuery('SELECT COUNT(*) FROM civicrm_stripe_customers where processor_id IS NULL') + 
+    $null_count =  CRM_Core_DAO::executeQuery('SELECT COUNT(*) FROM civicrm_stripe_customers where processor_id IS NULL') +
       CRM_Core_DAO::executeQuery('SELECT COUNT(*) FROM civicrm_stripe_plans where processor_id IS NULL') +
       CRM_Core_DAO::executeQuery('SELECT COUNT(*) FROM civicrm_stripe_subscriptions where processor_id IS NULL');
     if ( $null_count == 0 ) {
       $this->ctx->log->info('Skipped civicrm_stripe update 5002.  No nulls found in column processor_id in our tables.');
       return TRUE;
-    } 
-    else { 
+    }
+    else {
       try {
         // Set processor ID if there's only one.
         $processorCount = civicrm_api3('PaymentProcessorType', 'get', array(
@@ -109,8 +109,8 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base {
     return TRUE;
   }
 
- 
- /**
+
+  /**
    * Add subscription_id column to civicrm_stripe_subscriptions table.
    *
    * @return TRUE on success
@@ -131,11 +131,11 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base {
       CRM_Core_DAO::executeQuery('ALTER TABLE civicrm_stripe_subscriptions ADD COLUMN `subscription_id` varchar(255) DEFAULT NULL COMMENT "Subscription ID from Stripe" FIRST');
       CRM_Core_DAO::executeQuery('ALTER TABLE `civicrm_stripe_subscriptions` ADD UNIQUE KEY(`subscription_id`)');
 
-        }
-      return TRUE;
     }
-   
- /**
+    return TRUE;
+  }
+
+  /**
    * Populates the subscription_id column in table civicrm_stripe_subscriptions.
    *
    * @return TRUE on success
@@ -145,51 +145,43 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base {
     $config = CRM_Core_Config::singleton();
     $dbName = DB::connect($config->dsn)->_db;
 
-    $null_count =  CRM_Core_DAO::executeQuery('SELECT COUNT(*) FROM civicrm_stripe_subscriptions where subscription_id IS NULL'); 
+    $null_count =  CRM_Core_DAO::executeQuery('SELECT COUNT(*) FROM civicrm_stripe_subscriptions where subscription_id IS NULL');
     if ( $null_count == 0 ) {
       $this->ctx->log->info('Skipped civicrm_stripe update 5004.  No nulls found in column subscription_id in our civicrm_stripe_subscriptions table.');
-    } 
-    else { 
-    $customer_infos = CRM_Core_DAO::executeQuery("SELECT customer_id,processor_id
+    }
+    else {
+      $customer_infos = CRM_Core_DAO::executeQuery("SELECT customer_id,processor_id
       FROM `civicrm_stripe_subscriptions`;");
       while ( $customer_infos->fetch() ) {
         $processor_id = $customer_infos->processor_id;
         $customer_id = $customer_infos->customer_id;
-          try {
-            $stripe_key = civicrm_api3('PaymentProcessor', 'getvalue', array(
-             'return' => 'user_name',
-             'id' => $processor_id,
-             ));
-          }
-          catch (Exception $e) { 
-            Civi::log()->debug('Update 5004 failed. Has Stripe been removed as a payment processor?', $out = false);
-            return;
-          }
-          try {
-            \Stripe\Stripe::setApiKey($stripe_key);
-            $subscription = \Stripe\Subscription::all(array(
-             'customer'=> $customer_id,
-             'limit'=>1,
-            ));
-          } 
-          catch (Exception $e) {
-            // Don't quit here.  A missing customer in Stipe is OK.  They don't exist, so they can't have a subscription.
-            Civi::log()->debug('Cannot find Stripe API key: ' . $e->getMessage());
-          }
-          if (!empty($subscription['data'][0]['id'])) {
-            $query_params = array(
-              1 => array($subscription['data'][0]['id'], 'String'),
-              2 => array($customer_id, 'String'),
-            );
-            CRM_Core_DAO::executeQuery('UPDATE civicrm_stripe_subscriptions SET subscription_id = %1 where customer_id = %2;', $query_params);
-            unset($subscription);
-          }
+        try {
+          $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $processor_id]));
+          $processor->setAPIParams();
+
+          $subscription = \Stripe\Subscription::all(array(
+            'customer'=> $customer_id,
+            'limit'=>1,
+          ));
+        }
+        catch (Exception $e) {
+          // Don't quit here.  A missing customer in Stipe is OK.  They don't exist, so they can't have a subscription.
+          Civi::log()->debug('Cannot find Stripe API key: ' . $e->getMessage());
+        }
+        if (!empty($subscription['data'][0]['id'])) {
+          $query_params = array(
+            1 => array($subscription['data'][0]['id'], 'String'),
+            2 => array($customer_id, 'String'),
+          );
+          CRM_Core_DAO::executeQuery('UPDATE civicrm_stripe_subscriptions SET subscription_id = %1 where customer_id = %2;', $query_params);
+          unset($subscription);
+        }
       }
     }
-       return TRUE;
+    return TRUE;
   }
 
- /**
+  /**
    * Add contribution_recur_id column to civicrm_stripe_subscriptions table.
    *
    * @return TRUE on success
@@ -213,10 +205,10 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base {
       CRM_Core_DAO::executeQuery('ALTER TABLE `civicrm_stripe_subscriptions` ADD INDEX(`contribution_recur_id`);');
       CRM_Core_DAO::executeQuery('ALTER TABLE `civicrm_stripe_subscriptions` ADD CONSTRAINT `FK_civicrm_stripe_contribution_recur_id` FOREIGN KEY (`contribution_recur_id`) REFERENCES `civicrm_contribution_recur`(`id`) ON DELETE SET NULL ON UPDATE RESTRICT;');
     }
-      return TRUE;
+    return TRUE;
   }
 
- /**
+  /**
    *  Method 1 for populating the contribution_recur_id column in the civicrm_stripe_subscriptions table.
    *  ( A simple approach if that works if there have never been any susbcription edits in the Stripe UI. )
 
@@ -230,93 +222,93 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base {
 
     $subscriptions  = CRM_Core_DAO::executeQuery("SELECT invoice_id,is_live
       FROM `civicrm_stripe_subscriptions`;");
-      while ( $subscriptions->fetch() ) {
-        $test_mode = (int)!$subscriptions->is_live;
-        try {
-          // Fetch the recurring contribution Id. 
-          $recur_id = civicrm_api3('Contribution', 'getvalue', array(
-            'sequential' => 1,
-            'return' => "contribution_recur_id",
-            'invoice_id' => $subscriptions->invoice_id,
-            'contribution_test' => $test_mode,
-          ));
-        } 
-        catch (CiviCRM_API3_Exception $e) {
-          // Don't quit here. If we can't find the recurring ID for a single customer, make a note in the error log and carry on.
-          Civi::log()->debug('Recurring contribution search: ' . $e->getMessage());
-        }
-        if (!empty($recur_id)) {
-          $p = array(
-            1 => array($recur_id, 'Integer'),
-            2 => array($subscriptions->invoice_id, 'String'),
-          );
-          CRM_Core_DAO::executeQuery('UPDATE civicrm_stripe_subscriptions SET contribution_recur_id = %1 WHERE invoice_id = %2;', $p);
-        }
+    while ( $subscriptions->fetch() ) {
+      $test_mode = (int)!$subscriptions->is_live;
+      try {
+        // Fetch the recurring contribution Id.
+        $recur_id = civicrm_api3('Contribution', 'getvalue', array(
+          'sequential' => 1,
+          'return' => "contribution_recur_id",
+          'invoice_id' => $subscriptions->invoice_id,
+          'contribution_test' => $test_mode,
+        ));
+      }
+      catch (CiviCRM_API3_Exception $e) {
+        // Don't quit here. If we can't find the recurring ID for a single customer, make a note in the error log and carry on.
+        Civi::log()->debug('Recurring contribution search: ' . $e->getMessage());
       }
-        return TRUE;
+      if (!empty($recur_id)) {
+        $p = array(
+          1 => array($recur_id, 'Integer'),
+          2 => array($subscriptions->invoice_id, 'String'),
+        );
+        CRM_Core_DAO::executeQuery('UPDATE civicrm_stripe_subscriptions SET contribution_recur_id = %1 WHERE invoice_id = %2;', $p);
+      }
+    }
+    return TRUE;
   }
 
 
- /**
+  /**
    *  Method 2 for populating the contribution_recur_id column in the  civicrm_stripe_subscriptions table. Uncomment this and comment 5006.
    *  ( A more convoluted approach that works if there HAVE been susbcription edits in the Stripe UI. )
    * @return TRUE on success.  Please let users uncomment this as needed and increment past 5007 for the next upgrade.
    * @throws Exception
    */
-/*
-  public function upgrade_5007() {
-    $config = CRM_Core_Config::singleton();
-    $dbName = DB::connect($config->dsn)->_db;
-
-    $subscriptions  = CRM_Core_DAO::executeQuery("SELECT customer_id,is_live,processor_id
-      FROM `civicrm_stripe_subscriptions`;");
-      while ( $subscriptions->fetch() ) {
-        $test_mode = (int)!$subscriptions->is_live;
-        $p = array(
-          1 => array($subscriptions->customer_id, 'String'),
-          2 => array($subscriptions->is_live, 'Integer'),
-        );
-        $customer = CRM_Core_DAO::executeQuery("SELECT email
-          FROM `civicrm_stripe_customers` WHERE id = %1 AND is_live = %2;", $p);
-        $customer->fetch();
-        //  Try the billing email first, since that's what we send to Stripe.
-        try {
-          $contact = civicrm_api3('Email', 'get', array(
-           'sequential' => 1,
-           'return' => "contact_id",
-           'is_billing' => 1,
-           'email' => $customer->email, 
-           'api.ContributionRecur.get' => array('return' => "id", 'contact_id' => "\$value.contact_id", 'contribution_status_id' => "In Progress"),
-          ));
-         } 
-        catch (CiviCRM_API3_Exception $e) { 
-        // Uh oh, that didn't work.  Try to retrieve the recurring id using the primary email.
-          $contact = civicrm_api3('Contact', 'get', array(
-           'sequential' => 1,
-           'return' => "id",
-           'email' => $customer->email,
-           'api.ContributionRecur.get' => array('sequential' => 1, 'return' => "id", 'contact_id' => "\$values.id", 'contribution_status_id' => "In Progress"),
-           ));
-        }
+  /*
+    public function upgrade_5007() {
+      $config = CRM_Core_Config::singleton();
+      $dbName = DB::connect($config->dsn)->_db;
+
+      $subscriptions  = CRM_Core_DAO::executeQuery("SELECT customer_id,is_live,processor_id
+        FROM `civicrm_stripe_subscriptions`;");
+        while ( $subscriptions->fetch() ) {
+          $test_mode = (int)!$subscriptions->is_live;
+          $p = array(
+            1 => array($subscriptions->customer_id, 'String'),
+            2 => array($subscriptions->is_live, 'Integer'),
+          );
+          $customer = CRM_Core_DAO::executeQuery("SELECT email
+            FROM `civicrm_stripe_customers` WHERE id = %1 AND is_live = %2;", $p);
+          $customer->fetch();
+          //  Try the billing email first, since that's what we send to Stripe.
+          try {
+            $contact = civicrm_api3('Email', 'get', array(
+             'sequential' => 1,
+             'return' => "contact_id",
+             'is_billing' => 1,
+             'email' => $customer->email,
+             'api.ContributionRecur.get' => array('return' => "id", 'contact_id' => "\$value.contact_id", 'contribution_status_id' => "In Progress"),
+            ));
+           }
+          catch (CiviCRM_API3_Exception $e) {
+          // Uh oh, that didn't work.  Try to retrieve the recurring id using the primary email.
+            $contact = civicrm_api3('Contact', 'get', array(
+             'sequential' => 1,
+             'return' => "id",
+             'email' => $customer->email,
+             'api.ContributionRecur.get' => array('sequential' => 1, 'return' => "id", 'contact_id' => "\$values.id", 'contribution_status_id' => "In Progress"),
+             ));
+          }
 
-        if (!empty($contact['values'][0]['api.ContributionRecur.get']['values'][0]['id'])) {
-         $recur_id = $contact['values'][0]['api.ContributionRecur.get']['values'][0]['id']; 
-             $p = array(
-              1 => array($recur_id, 'Integer'),
-              2 => array($subscriptions->customer_id, 'String'),
-            );
-            CRM_Core_DAO::executeQuery('UPDATE civicrm_stripe_subscriptions SET contribution_recur_id = %1 WHERE customer_id = %2;', $p);
-         } else {
-            // Crap.
-            $this->ctx->log->info('Update 5007 failed.  Consider adding recurring IDs manuallly to civicrm_stripe_subscriptions. ');
-            return;
+          if (!empty($contact['values'][0]['api.ContributionRecur.get']['values'][0]['id'])) {
+           $recur_id = $contact['values'][0]['api.ContributionRecur.get']['values'][0]['id'];
+               $p = array(
+                1 => array($recur_id, 'Integer'),
+                2 => array($subscriptions->customer_id, 'String'),
+              );
+              CRM_Core_DAO::executeQuery('UPDATE civicrm_stripe_subscriptions SET contribution_recur_id = %1 WHERE customer_id = %2;', $p);
+           } else {
+              // Crap.
+              $this->ctx->log->info('Update 5007 failed.  Consider adding recurring IDs manuallly to civicrm_stripe_subscriptions. ');
+              return;
+          }
         }
-      }
-       return TRUE;
-  }
-*/
+         return TRUE;
+    }
+  */
 
- /**
+  /**
    * Add change default NOT NULL to NULL in vestigial invoice_id column in civicrm_stripe_subscriptions table if needed. (issue #192)
    *
    * @return TRUE on success
@@ -338,7 +330,7 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base {
         MODIFY COLUMN `invoice_id` varchar(255) NULL default ""
         COMMENT "Safe to remove this column if the update retrieving subscription IDs completed satisfactorily."');
     }
-      return TRUE;
+    return TRUE;
   }
 
   /**
@@ -407,4 +399,25 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base {
     return TRUE;
   }
 
+  public function upgrade_5023() {
+    $this->ctx->log->info('Applying Stripe update 5023.  Swap over public/secret key settings');
+    $stripeProcessors = civicrm_api3('PaymentProcessor', 'get', [
+      'payment_processor_type_id' => "Stripe",
+    ]);
+    foreach ($stripeProcessors['values'] as $processor) {
+      if ((substr($processor['user_name'], 0, 3) === 'sk_')
+        && (substr($processor['password'], 0, 3) === 'pk_')) {
+        // Need to switch over parameters
+        $createParams = [
+          'id' => $processor['id'],
+          'user_name' => $processor['password'],
+          'password' => $processor['user_name'],
+        ];
+        civicrm_api3('PaymentProcessor', 'create', $createParams);
+      }
+    }
+    CRM_Utils_System::flushCache();
+    return TRUE;
+  }
+
 }
diff --git a/CRM/Stripe/Webhook.php b/CRM/Stripe/Webhook.php
index fe0e3733fb749239f7773c63a3600520388bb63b..07d4c3f74a8afaff8806b03420fd5ce601a1b627 100644
--- a/CRM/Stripe/Webhook.php
+++ b/CRM/Stripe/Webhook.php
@@ -20,13 +20,16 @@ class CRM_Stripe_Webhook {
     $result = civicrm_api3('PaymentProcessor', 'get', [
       'class_name' => 'Payment_Stripe',
       'is_active' => 1,
+      'domain_id' => CRM_Core_Config::domainID(),
     ]);
 
     foreach ($result['values'] as $paymentProcessor) {
       $messageTexts = [];
       $webhook_path = self::getWebhookPath($paymentProcessor['id']);
 
-      \Stripe\Stripe::setApiKey(CRM_Core_Payment_Stripe::getSecretKey($paymentProcessor));
+      $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $paymentProcessor['id']]));
+      $processor->setAPIParams();
+
       try {
         $webhooks = \Stripe\WebhookEndpoint::all(["limit" => 100]);
       }
@@ -35,10 +38,7 @@ class CRM_Stripe_Webhook {
         $messages[] = new CRM_Utils_Check_Message(
           'stripe_webhook',
           $error,
-          E::ts('Stripe Payment Processor: %1 (%2)', [
-            1 => $paymentProcessor['name'],
-            2 => $paymentProcessor['id'],
-          ]),
+          self::getTitle($paymentProcessor),
           \Psr\Log\LogLevel::ERROR,
           'fa-money'
         );
@@ -91,9 +91,12 @@ class CRM_Stripe_Webhook {
           }
         }
         else {
-          $messageTexts[] = E::ts('Stripe Webhook missing! Please visit <a href="%1">Fix Stripe Webhook</a> to fix.', [
-            1 => CRM_Utils_System::url('civicrm/stripe/fix-webhook'),
-          ]);
+          $messageTexts[] = E::ts('Stripe Webhook missing! Please visit <a href="%1">Fix Stripe Webhook</a> to fix.<br />Expected webhook path is: <a href="%2" target="_blank">%2</a>',
+            [
+              1 => CRM_Utils_System::url('civicrm/stripe/fix-webhook'),
+              2 => $webhook_path,
+            ]
+          );
         }
       }
 
@@ -101,10 +104,7 @@ class CRM_Stripe_Webhook {
         $messages[] = new CRM_Utils_Check_Message(
           'stripe_webhook',
           $messageText,
-          E::ts('Stripe Payment Processor Webhook: %1 (%2)', [
-            1 => $paymentProcessor['name'],
-            2 => $paymentProcessor['id'],
-          ]),
+          self::getTitle($paymentProcessor),
           \Psr\Log\LogLevel::WARNING,
           'fa-money'
         );
@@ -112,13 +112,30 @@ class CRM_Stripe_Webhook {
     }
   }
 
+  /**
+   * Get the error message title for the system check
+   * @param array $paymentProcessor
+   *
+   * @return string
+   */
+  private static function getTitle($paymentProcessor) {
+    if (!empty($paymentProcessor['is_test'])) {
+      $paymentProcessor['name'] .= ' (test)';
+    }
+    return E::ts('Stripe Payment Processor: %1 (%2)', [
+      1 => $paymentProcessor['name'],
+      2 => $paymentProcessor['id'],
+    ]);
+  }
+
   /**
    * Create a new webhook for payment processor
    *
    * @param int $paymentProcessorId
    */
   public static function createWebhook($paymentProcessorId) {
-    \Stripe\Stripe::setApiKey(CRM_Core_Payment_Stripe::getSecretKeyById($paymentProcessorId));
+    $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $paymentProcessorId]));
+    $processor->setAPIParams();
 
     $params = [
       'enabled_events' => self::getDefaultEnabledEvents(),
diff --git a/api/v3/Stripe/Ipn.php b/api/v3/Stripe/Ipn.php
index 07cde844b0b9b456b0803c04c4f750a21fd315c3..46c7be7203edb257d69d60c0cad07d343cb97ded 100644
--- a/api/v3/Stripe/Ipn.php
+++ b/api/v3/Stripe/Ipn.php
@@ -1,16 +1,16 @@
 <?php
 
 /**
- * This api allows you to replay Stripe events. 
+ * This api allows you to replay Stripe events.
  *
  * You can either pass the id of an entry in the System Log (which can
  * be populated with the Stripe.PopulateLog call) or you can pass a
  * event id from Stripe directly.
  *
  * When processing an event, the event will always be re-fetched from the
- * Stripe server first, so this will not work while offline or with 
+ * Stripe server first, so this will not work while offline or with
  * events that were not generated by the Stripe server.
- */ 
+ */
 
 /**
  * Stripe.Ipn API specification
@@ -57,12 +57,9 @@ function civicrm_api3_stripe_Ipn($params) {
       throw new API_Exception('Please pass the payment processor id (ppid) if using evtid.', 3236);
     }
     $ppid = $params['ppid'];
-    $results = civicrm_api3('PaymentProcessor', 'getsingle', array('id' => $ppid));
-    // YES! I know, password and user are backwards. wtf??
-    $sk = $results['user_name'];
+    $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $ppid]));
+    $processor->setAPIParams();
 
-    require_once ("vendor/stripe/stripe-php/init.php");
-    \Stripe\Stripe::setApiKey($sk);
     $object = \Stripe\Event::retrieve($params['evtid']);
   }
   // Avoid a SQL error if this one has been processed already.
@@ -74,7 +71,7 @@ function civicrm_api3_stripe_Ipn($params) {
     return civicrm_api3_create_error("Ipn already processed.");
   }
   if (class_exists('CRM_Core_Payment_StripeIPN')) {
-    // The $_GET['processor_id'] value is normally set by 
+    // The $_GET['processor_id'] value is normally set by
     // CRM_Core_Payment::handlePaymentMethod
     $_GET['processor_id'] = $ppid;
     $ipnClass = new CRM_Core_Payment_StripeIPN($object);
diff --git a/api/v3/Stripe/Listevents.php b/api/v3/Stripe/Listevents.php
index e0d5535ae3df613968189897499aa9de4a24187d..b1b02ebfd8720b0695652a3d4839a8fe4b834198 100644
--- a/api/v3/Stripe/Listevents.php
+++ b/api/v3/Stripe/Listevents.php
@@ -9,7 +9,7 @@
 
 /**
  * Stripe.ListEvents API specification
- * 
+ *
  *
  * @param array $spec description of fields supported by this API call
  * @return void
@@ -17,12 +17,13 @@
  */
 function _civicrm_api3_stripe_ListEvents_spec(&$spec) {
   $spec['ppid']['title'] = ts("Use the given Payment Processor ID");
-  $spec['ppid']['type'] = CRM_Utils_Type::T_INT; 
+  $spec['ppid']['type'] = CRM_Utils_Type::T_INT;
+  $spec['ppid']['api.required'] = TRUE;
   $spec['type']['title'] = ts("Limit to the given Stripe events type, defaults to invoice.payment_succeeded.");
   $spec['type']['api.default'] = 'invoice.payment_succeeded';
   $spec['limit']['title'] = ts("Limit number of results returned (100 is max)");
   $spec['starting_after']['title'] = ts("Only return results after this event id.");
-  $spec['output']['api.default'] = 'brief'; 
+  $spec['output']['api.default'] = 'brief';
   $spec['output']['title'] = ts("How to format the output, brief or raw. Defaults to brief.");
 }
 
@@ -122,18 +123,17 @@ function civicrm_api3_stripe_VerifyEventType($eventType) {
  * Process parameters to determine ppid and sk.
  *
  * @param array $params
+ *
+ * @return array
+ * @throws \API_Exception
  */
 function civicrm_api3_stripe_ProcessParams($params) {
-  $ppid = NULL;
   $type = NULL;
   $created = NULL;
   $limit = NULL;
   $starting_after = NULL;
   $sk = NULL;
 
-  if (array_key_exists('ppid', $params) ) {
-    $ppid = $params['ppid'];
-  }
   if (array_key_exists('created', $params) ) {
     $created = $params['created'];
   }
@@ -144,29 +144,6 @@ function civicrm_api3_stripe_ProcessParams($params) {
     $starting_after = $params['starting_after'];
   }
 
-  // Select the right payment processor to use.
-  if ($ppid) {
-    $query_params = array('id' => $ppid);
-  }
-  else {
-    // By default, select the live stripe processor (we expect there to be
-    // only one).
-    $query_params = array('class_name' => 'Payment_Stripe', 'is_test' => 0);
-  }
-  try {
-    $results = civicrm_api3('PaymentProcessor', 'getsingle', $query_params);
-    // YES! I know, password and user are backwards. wtf??
-    $sk = $results['user_name'];
-  }
-  catch (CiviCRM_API3_Exception $e) {
-    if(preg_match('/Expected one PaymentProcessor but/', $e->getMessage())) {
-      throw new API_Exception("Expected one live Stripe payment processor, but found none or more than one. Please specify ppid=.", 1234);
-    }
-    else {
-      throw new API_Exception("Error getting the Stripe Payment Processor to use", 1235);
-    }
-  }
-
 	// Check to see if we should filter by type.
   if (array_key_exists('type', $params) ) {
     // Validate - since we will be appending this to an URL.
@@ -185,7 +162,7 @@ function civicrm_api3_stripe_ProcessParams($params) {
       throw new API_Exception("Created can only be passed in programatically as an array", 1237);
     }
   }
-  return array('sk' => $sk, 'type' => $type, 'created' => $created, 'limit' => $limit, 'starting_after' => $starting_after);
+  return ['type' => $type, 'created' => $created, 'limit' => $limit, 'starting_after' => $starting_after];
 }
 
 /**
@@ -199,7 +176,6 @@ function civicrm_api3_stripe_ProcessParams($params) {
  */
 function civicrm_api3_stripe_Listevents($params) {
   $parsed = civicrm_api3_stripe_ProcessParams($params);
-  $sk = $parsed['sk'];
   $type = $parsed['type'];
   $created = $parsed['created'];
   $limit = $parsed['limit'];
@@ -218,9 +194,10 @@ function civicrm_api3_stripe_Listevents($params) {
   if ($starting_after) {
     $args['starting_after'] = $starting_after;
   }
-  
-  require_once ("vendor/stripe/stripe-php/init.php");
-  \Stripe\Stripe::setApiKey($sk);
+
+  $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $params['ppid']]));
+  $processor->setAPIParams();
+
   $data_list = \Stripe\Event::all($args);
   if (array_key_exists('error', $data_list)) {
     $err = $data_list['error'];
diff --git a/api/v3/StripeCustomer.php b/api/v3/StripeCustomer.php
index 319bccdee359b56dc91b1a6cf180256cd1d1d026..ee254ee5460b50abb09c138c07a05f1c9ec87792 100644
--- a/api/v3/StripeCustomer.php
+++ b/api/v3/StripeCustomer.php
@@ -258,8 +258,7 @@ function civicrm_api3_stripe_customer_updatestripemetadata($params) {
       throw new CiviCRM_API3_Exception('Could not find contact ID for stripe customer: ' . $customerId);
     }
 
-    $paymentProcessor = \Civi\Payment\System::singleton()
-      ->getById($customerParams['processor_id']);
+    $paymentProcessor = \Civi\Payment\System::singleton()->getById($customerParams['processor_id']);
     $paymentProcessor->setAPIParams();
 
     // Get the stripe customer from stripe
@@ -267,7 +266,7 @@ function civicrm_api3_stripe_customer_updatestripemetadata($params) {
       $stripeCustomer = \Stripe\Customer::retrieve($customerId);
     } catch (Exception $e) {
       $err = CRM_Core_Payment_Stripe::parseStripeException('retrieve_customer', $e, FALSE);
-      $errorMessage = CRM_Core_Payment_Stripe::handleErrorNotification($err, NULL);
+      $errorMessage = $paymentProcessor->handleErrorNotification($err, NULL);
       throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to retrieve Stripe Customer: ' . $errorMessage);
     }
 
diff --git a/api/v3/StripeSubscription.php b/api/v3/StripeSubscription.php
index 3eeee290233570f1131dcbaaff8f0e4adffb2729..d152780a74030faaa2d3a0bce8a490292e061c2c 100644
--- a/api/v3/StripeSubscription.php
+++ b/api/v3/StripeSubscription.php
@@ -149,9 +149,10 @@ function civicrm_api3_stripe_subscription_import($params) {
 
   $paymentProcessor = \Civi\Payment\System::singleton()->getById($params['payment_processor_id'])->getPaymentProcessor();
 
-  // Now re-retrieve the data from Stripe to ensure it's legit.
-  \Stripe\Stripe::setApiKey($paymentProcessor['user_name']);
+  $processor = new CRM_Core_Payment_Stripe('', $paymentProcessor);
+  $processor->setAPIParams();
 
+  // Now re-retrieve the data from Stripe to ensure it's legit.
   $stripeSubscription = \Stripe\Subscription::retrieve($params['subscription_id']);
 
   // Create the stripe customer in CiviCRM
diff --git a/composer.lock b/composer.lock
index d488bb77a655253803f2ec3e9451bbdaeaa370a0..b305a1239ca6f8085c41fb0f451bf0b8f9867050 100644
--- a/composer.lock
+++ b/composer.lock
@@ -8,16 +8,16 @@
     "packages": [
         {
             "name": "stripe/stripe-php",
-            "version": "v6.40.0",
+            "version": "v6.43.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/stripe/stripe-php.git",
-                "reference": "9c22ffab790ef4dae0f371929de50e8b53c9ec8d"
+                "reference": "42fcdaf99c44bb26937223f8eae1f263491d5ab8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/stripe/stripe-php/zipball/9c22ffab790ef4dae0f371929de50e8b53c9ec8d",
-                "reference": "9c22ffab790ef4dae0f371929de50e8b53c9ec8d",
+                "url": "https://api.github.com/repos/stripe/stripe-php/zipball/42fcdaf99c44bb26937223f8eae1f263491d5ab8",
+                "reference": "42fcdaf99c44bb26937223f8eae1f263491d5ab8",
                 "shasum": ""
             },
             "require": {
@@ -60,7 +60,7 @@
                 "payment processing",
                 "stripe"
             ],
-            "time": "2019-06-27T23:24:51+00:00"
+            "time": "2019-08-29T16:56:12+00:00"
         }
     ],
     "packages-dev": [],
diff --git a/css/elements.css b/css/elements.css
new file mode 100644
index 0000000000000000000000000000000000000000..2718ae2d807bb3bf698e73e1d728b6a71d485816
--- /dev/null
+++ b/css/elements.css
@@ -0,0 +1,14 @@
+#card-element {
+  padding: 2%;
+  margin: 2% auto;
+  max-width: 800px;
+  background-color: ghostwhite;
+  -webkit-box-shadow: 10px 10px 7px -6px rgba(0,0,0,0.81);
+  -moz-box-shadow: 10px 10px 7px -6px rgba(0, 0, 0, 0.81);
+  box-shadow: 10px 10px 7px -6px rgba(0, 0, 0, 0.81);
+}
+
+#card-errors {
+  margin: 2%;
+  display: none;
+}
diff --git a/css/elements.min.css b/css/elements.min.css
new file mode 100644
index 0000000000000000000000000000000000000000..47a21a67c10ff4d286cd11b211b66bbf93b76e2e
--- /dev/null
+++ b/css/elements.min.css
@@ -0,0 +1 @@
+#card-element{padding:2%;margin:2% auto;max-width:800px;background-color:ghostwhite;-webkit-box-shadow:10px 10px 7px -6px rgba(0,0,0,0.81);-moz-box-shadow:10px 10px 7px -6px rgba(0,0,0,0.81);box-shadow:10px 10px 7px -6px rgba(0,0,0,0.81)}#card-errors{margin:2%;display:none}
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
index ee55a3a9621dd772cb0921a4f1eda99b7d504a98..9c447d99616df107efdf95ba7009452469974064 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -8,13 +8,21 @@ Latest releases can be found here: https://civicrm.org/extensions/stripe-payment
 View this extension in the [Extension Directory](https://civicrm.org/extensions/stripe-payment-processor).
 
 ## Compatibility / Requirements
-* CiviCRM 5.10+
-* PHP 7.0+
+* CiviCRM 5.13+
+* PHP 7.1+
 * Jquery 1.10 (Use jquery_update module on Drupal).
 * Drupal 7 / Joomla / Wordpress (latest supported release). *Not currently tested with other CMS but it may work.*
-* Stripe API version: 2019-02-19
+* Stripe API version: 2019-09-09
 * Drupal webform_civicrm 7.x-4.22+ (if using webform integration)
 
+If using test mode with drupal webform_civicrm you need this patch: https://github.com/colemanw/webform_civicrm/pull/266
+
+## Troubleshooting
+Under *Administer->CiviContribute->Stripe Settings* you can find a setting:
+* Enable Stripe Javascript debugging?
+
+This can be switched on to output debug info to the browser console and can be used to debug problems with submitting your payments.
+
 ## Credits
 Current Maintainer: Matthew Wire - https://www.mjwconsult.co.uk
 
diff --git a/docs/release/release_notes.md b/docs/release/release_notes.md
index 14dea693632db379375b525554a00f1b69629403..94554e240ec38f68364e22f5924ffee280bd08f6 100644
--- a/docs/release/release_notes.md
+++ b/docs/release/release_notes.md
@@ -1,3 +1,54 @@
+## Release 6.0.beta1
+
+*Thanks to Rich Lott (@artfulrobot) for contributing and testing release.*
+* We don't need to confirm the payment until we capture it
+* payment method id is not required when passing in an existing payment intent
+* Add minified versions of js/css
+* Remove onclick attribute from submit form so that CiviContribute forms do stripe processing before submission
+* Description and Customer fields in Stripe backend - fixes #78
+
+## Release 6.0.alpha3
+
+* Support recurring payments with paymentIntents/Elements. Cancel subscription with Stripe when we reach recurring end date
+* **Update required Stripe API version to 2019-09-09**
+* Handle confirmation pages properly for contribution pages (make sure we pass through paymentIntentID).
+* Handle card declined on client side.
+* Support creating recurring payment (subscription).
+* Handle IPN events for charges / invoices (support cancel/refund etc).
+* Add basic support for PaymentProcessor.refund API.
+* Remove membership_type_tag from plan name.
+
+## Release 6.0.alpha2
+
+* Support Drupal Webform CiviCRM.
+* Support Event Registration.
+* Support Confirm/Thankyou pages on contribution pages / events.
+* Support cards using 3dsecure and cards not using 3dsecure.
+
+### Not Supported (should be in final 6.0 release):
+* Recurring payments.
+* Backend payments.
+
+## Release 6.0.alpha1
+
+* ONLY contribution pages with no confirm pages are supported.
+
+## Release 6.0 (not yet released)
+
+**This is a major new release. You cannot rollback once you've upgraded.**
+
+**This extension REQUIRES the mjwshared extension available here: https://lab.civicrm.org/extensions/mjwshared**
+
+* Use Stripe Elements: https://stripe.com/payments/elements.
+* Use PaymentIntents and comply with the European SCA directive (https://stripe.com/docs/strong-customer-authentication).
+* Require Stripe API Version: 2019-09-09 and ensure that all codepaths specify the API version.
+* Switch publishable key/secret key in settings (upgrader does this automatically) so they are now "correct" per CiviCRM settings pages.
+* Support cards using 3dsecure and cards not using 3dsecure (workflows with Stripe are slightly different but both are now handled).
+
+### Not supported
+* CiviCRM Event Cart (requires additional funding, changes should probably be made in CiviCRM core to standardize that workflow rather than adding support via this extension).
+* Card payments via the admin backend (this was supported in 5.4.1 but has unresolved issues with Stripe Elements when used via popup forms).
+
 ## Release 5.4.1
 * Don't overwrite system messages when performing webhook checks.
 * Add form to handle creating/updating webhooks instead of automatically during system check (Thanks @artfulrobot)
diff --git a/info.xml b/info.xml
index ddbb27c1cd0e39dd70fd1a728dc1b93c691386b0..e48e8c149137e4b9b0f4c62ca1fcd76c2acd0fe4 100644
--- a/info.xml
+++ b/info.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0"?>
 <extension key="com.drastikbydesign.stripe" type="module">
   <file>stripe</file>
-  <name>Stripe</name>
+  <name>Stripe (SCA payments development version)</name>
   <description>Stripe Payment Processor</description>
   <urls>
     <url desc="Main Extension Page">https://lab.civicrm.org/extensions/stripe</url>
@@ -12,15 +12,18 @@
     <author>Matthew Wire (MJW Consulting)</author>
     <email>mjw@mjwconsult.co.uk</email>
   </maintainer>
-  <releaseDate>2019-07-21</releaseDate>
-  <version>5.4.1</version>
-  <develStage>stable</develStage>
+  <releaseDate>2019-09-12</releaseDate>
+  <version>6.0.beta1</version>
+  <develStage>beta</develStage>
   <compatibility>
     <ver>5.13</ver>
   </compatibility>
   <comments>Original Author: Joshua Walker (drastik) - Drastik by Design.
     Jamie Mcclelland (ProgressiveTech) did a lot of the 5.x compatibility work.
   </comments>
+  <requires>
+    <ext>mjwshared</ext>
+  </requires>
   <civix>
     <namespace>CRM/Stripe</namespace>
   </civix>
diff --git a/js/civicrm_stripe.js b/js/civicrm_stripe.js
index 54aa160d169235c45fa94027490ea1382ec76d7c..f29d7dd90a1118f967ac3acc9fb456a88b6d4d1a 100644
--- a/js/civicrm_stripe.js
+++ b/js/civicrm_stripe.js
@@ -1,62 +1,105 @@
 /**
- * @file
  * JS Integration between CiviCRM & Stripe.
  */
 CRM.$(function($) {
 
-  // Response from Stripe.createToken.
-  function stripeResponseHandler(status, response) {
-    $form = getBillingForm();
-    $submit = getBillingSubmit();
-
-    if (response.error) {
-      $('html, body').animate({scrollTop: 0}, 300);
-      // Show the errors on the form.
-      if ($(".messages.crm-error.stripe-message").length > 0) {
-        $(".messages.crm-error.stripe-message").slideUp();
-        $(".messages.crm-error.stripe-message:first").remove();
-      }
-      $form.prepend('<div class="messages alert alert-block alert-danger error crm-error stripe-message">'
-        + '<strong>Payment Error Response:</strong>'
-        + '<ul id="errorList">'
-        + '<li>Error: ' + response.error.message + '</li>'
-        + '</ul>'
-        + '</div>');
-
-      removeCCDetails($form, true);
-      $form.data('submitted', false);
-      $submit.prop('disabled', false);
-    }
-    else {
-      var token = response['id'];
-      // Update form with the token & submit.
-      removeCCDetails($form, false);
-      $form.find("input#stripe-token").val(token);
+  var stripe;
+  var card;
+  var form;
+  var submitButton;
+  var stripeLoading = false;
 
-      // Disable unload event handler
-      window.onbeforeunload = null;
+  function paymentIntentSuccessHandler(paymentIntent) {
+    debugging('paymentIntent confirmation success');
+
+    // Insert the token ID into the form so it gets submitted to the server
+    var hiddenInput = document.createElement('input');
+    hiddenInput.setAttribute('type', 'hidden');
+    hiddenInput.setAttribute('name', 'paymentIntentID');
+    hiddenInput.setAttribute('value', paymentIntent.id);
+    form.appendChild(hiddenInput);
+
+    // Submit the form
+    form.submit();
+  }
 
-      // Restore any onclickAction that was removed.
-      $submit.attr('onclick', onclickAction);
+  function displayError(result) {
+    // Display error.message in your UI.
+    debugging('error: ' + result.error.message);
+    // Inform the user if there was an error
+    var errorElement = document.getElementById('card-errors');
+    errorElement.style.display = 'block';
+    errorElement.textContent = result.error.message;
+    document.querySelector('#billing-payment-block').scrollIntoView();
+    window.scrollBy(0, -50);
+    submitButton.removeAttribute('disabled');
+  }
+
+  function handleCardPayment() {
+    debugging('handle card payment');
+    stripe.createPaymentMethod('card', card).then(function (result) {
+      if (result.error) {
+        // Show error in payment form
+        displayError(result);
+      }
+      else {
+        // Send paymentMethod.id to server
+        var url = CRM.url('civicrm/stripe/confirm-payment');
+        $.post(url, {
+          payment_method_id: result.paymentMethod.id,
+          amount: getTotalAmount(),
+          currency: CRM.vars.stripe.currency,
+          id: CRM.vars.stripe.id,
+        }).then(function (result) {
+          // Handle server response (see Step 3)
+          handleServerResponse(result);
+        });
+      }
+    });
+  }
 
-      // This triggers submit without generating a submit event (so we don't run submit handler again)
-      $form.get(0).submit();
+  function handleServerResponse(result) {
+    debugging('handleServerResponse');
+    if (result.error) {
+      // Show error from server on payment form
+      displayError(result);
+    } else if (result.requires_action) {
+      // Use Stripe.js to handle required card action
+      handleAction(result);
+    } else {
+      // All good, we can submit the form
+      paymentIntentSuccessHandler(result.paymentIntent);
     }
   }
 
+  function handleAction(response) {
+    stripe.handleCardAction(
+      response.payment_intent_client_secret
+    ).then(function(result) {
+      if (result.error) {
+        // Show error in payment form
+        displayError(result);
+      } else {
+        // The card action has been handled
+        // The PaymentIntent can be confirmed again on the server
+        paymentIntentSuccessHandler(result.paymentIntent);
+      }
+    });
+  }
+
   // Prepare the form.
   var onclickAction = null;
   $(document).ready(function() {
     // Disable the browser "Leave Page Alert" which is triggered because we mess with the form submit function.
     window.onbeforeunload = null;
+
     // Load Stripe onto the form.
-    loadStripeBillingBlock();
-    $submit = getBillingSubmit();
+    checkAndLoad();
 
     // Store and remove any onclick Action currently assigned to the form.
     // We will re-add it if the transaction goes through.
-    onclickAction = $submit.attr('onclick');
-    $submit.removeAttr('onclick');
+    //onclickAction = submitButton.getAttribute('onclick');
+    //submitButton.removeAttribute('onclick');
 
     // Quickform doesn't add hidden elements via standard method. On a form where payment processor may
     //  be loaded via initial form load AND ajax (eg. backend live contribution page with payproc dropdown)
@@ -82,7 +125,7 @@ CRM.$(function($) {
         // There is. Check if the selected payment processor is different
         // from the one we think we should be using.
         var ppid = $('#payment_processor_id').val();
-        if (ppid != $('#stripe-id').val()) {
+        if (ppid != CRM.vars.stripe.id) {
           debugging('payment processor changed to id: ' + ppid);
           // It is! See if the new payment processor is also a Stripe
           // Payment processor. First, find out what the stripe
@@ -106,143 +149,159 @@ CRM.$(function($) {
               if (pub_key) {
                 // It is a stripe payment processor, so update the key.
                 debugging("Setting new stripe key to: " + pub_key);
-                $('#stripe-pub-key').val(pub_key);
+                CRM.vars.stripe.publishableKey = pub_key;
               }
               else {
                 debugging("New payment processor is not Stripe, setting stripe-pub-key to null");
-                $('#stripe-pub-key').val(null);
+                CRM.vars.stripe.publishableKey = null;
               }
               // Now reload the billing block.
-              loadStripeBillingBlock();
+              checkAndLoad();
             });
           });
         }
       }
-      loadStripeBillingBlock();
+      checkAndLoad();
     }
   });
 
-  function loadStripeBillingBlock() {
-    // Setup Stripe.Js
-    var $stripePubKey = $('#stripe-pub-key');
+  function checkAndLoad() {
+    if (typeof CRM.vars.stripe === 'undefined') {
+      debugging('CRM.vars.stripe not defined!');
+      return;
+    }
 
-    if ($stripePubKey.length) {
-      if (!$().Stripe) {
-        $.getScript('https://js.stripe.com/v2/', function () {
-          Stripe.setPublishableKey($('#stripe-pub-key').val());
-        });
+    if (typeof Stripe === 'undefined') {
+      if (stripeLoading) {
+        return;
       }
+      stripeLoading = true;
+      debugging('Stripe.js is not loaded!');
+
+      $.getScript("https://js.stripe.com/v3", function () {
+        debugging("Script loaded and executed.");
+        stripeLoading = false;
+        loadStripeBillingBlock();
+      });
     }
+    else {
+      loadStripeBillingBlock();
+    }
+  }
+
+  function loadStripeBillingBlock() {
+    stripe = Stripe(CRM.vars.stripe.publishableKey);
+    var elements = stripe.elements();
+
+    var style = {
+      base: {
+        fontSize: '20px',
+      },
+    };
+
+    // Create an instance of the card Element.
+    card = elements.create('card', {style: style});
+    card.mount('#card-element');
+
+    // Hide the CiviCRM postcode field so it will still be submitted but will contain the value set in the stripe card-element.
+    document.getElementsByClassName('billing_postal_code-' + CRM.vars.stripe.billingAddressID + '-section')[0].setAttribute('hidden', true);
+    card.addEventListener('change', function(event) {
+      updateFormElementsFromCreditCardDetails(event);
+    });
 
-    // Get the form containing payment details
-    $form = getBillingForm();
-    if (!$form.length) {
+      // Get the form containing payment details
+    form = getBillingForm();
+    if (typeof form.length === 'undefined' || form.length === 0) {
       debugging('No billing form!');
       return;
     }
-    $submit = getBillingSubmit();
+    submitButton = getBillingSubmit();
 
     // If another submit button on the form is pressed (eg. apply discount)
     //  add a flag that we can set to stop payment submission
-    $form.data('submit-dont-process', '0');
+    form.dataset.submitdontprocess = false;
+
     // Find submit buttons which should not submit payment
-    $form.find('[type="submit"][formnovalidate="1"], ' +
+    var nonPaymentSubmitButtons = form.querySelectorAll('[type="submit"][formnovalidate="1"], ' +
       '[type="submit"][formnovalidate="formnovalidate"], ' +
       '[type="submit"].cancel, ' +
-      '[type="submit"].webform-previous').click( function() {
-      debugging('adding submit-dont-process');
-      $form.data('submit-dont-process', 1);
-    });
+      '[type="submit"].webform-previous'), i;
+    for (i = 0; i < nonPaymentSubmitButtons.length; ++i) {
+      nonPaymentSubmitButtons[i].addEventListener('click', function () {
+        debugging('adding submitdontprocess');
+        form.dataset.submitdontprocess = true;
+      });
+    }
 
-    $submit.click( function(event) {
+    submitButton.addEventListener('click', function(event) {
       // Take over the click function of the form.
-      debugging('clearing submit-dont-process');
-      $form.data('submit-dont-process', 0);
+      debugging('clearing submitdontprocess');
+      form.dataset.submitdontprocess = false;
 
       // Run through our own submit, that executes Stripe submission if
       // appropriate for this submit.
-      var ret = submit(event);
-      if (ret) {
-        // True means it's not our form. We are bailing and not trying to
-        // process Stripe.
-        // Restore any onclickAction that was removed.
-        $form = getBillingForm();
-        $submit = getBillingSubmit();
-        $submit.attr('onclick', onclickAction);
-        $form.get(0).submit();
-        return true;
-      }
-      // Otherwise, this is a stripe submission - don't handle normally.
-      // The code for completing the submission is all managed in the
-      // stripe handler (stripeResponseHandler) which gets execute after
-      // stripe finishes.
-      return false;
+      return submit(event);
     });
 
-    // Add a keypress handler to set flag if enter is pressed
-    $form.find('input#discountcode').keypress( function(e) {
-      if (e.which === 13) {
-        $form.data('submit-dont-process', 1);
-      }
-    });
+    // Remove the onclick attribute added by CiviCRM.
+    submitButton.removeAttribute('onclick');
 
-    var isWebform = getIsWebform($form);
+    addSupportForCiviDiscount();
 
     // For CiviCRM Webforms.
-    if (isWebform) {
+    if (getIsDrupalWebform()) {
       // We need the action field for back/submit to work and redirect properly after submission
-      if (!($('#action').length)) {
-        $form.append($('<input type="hidden" name="op" id="action" />'));
-      }
-      var $actions = $form.find('[type=submit]');
+
       $('[type=submit]').click(function() {
-        $('#action').val(this.value);
+        addDrupalWebformActionElement(this.value);
       });
       // If enter pressed, use our submit function
-      $form.keypress(function(event) {
-        if (event.which === 13) {
-          $('#action').val(this.value);
+      form.addEventListener('keydown', function (e) {
+        if (e.keyCode === 13) {
+          addDrupalWebformActionElement(this.value);
           submit(event);
         }
       });
+
       $('#billingcheckbox:input').hide();
       $('label[for="billingcheckbox"]').hide();
     }
-    else {
-      // As we use credit_card_number to pass token, make sure it is empty when shown
-      $form.find("input#credit_card_number").val('');
-      $form.find("input#cvv2").val('');
-    }
 
     function submit(event) {
       event.preventDefault();
       debugging('submit handler');
 
-      if ($form.data('submitted') === true) {
+      if (form.dataset.submitted === true) {
         debugging('form already submitted');
         return false;
       }
 
-      var isWebform = getIsWebform($form);
+      var stripeProcessorId;
+      var chosenProcessorId;
+
+      if (typeof CRM.vars.stripe !== 'undefined') {
+        stripeProcessorId = CRM.vars.stripe.id;
+      }
 
       // Handle multiple payment options and Stripe not being chosen.
-      if (isWebform) {
-        var stripeProcessorId;
-        var chosenProcessorId;
-        stripeProcessorId = $('#stripe-id').val();
+      // @fixme this needs refactoring as some is not relevant anymore (with stripe 6.0)
+      if (getIsDrupalWebform()) {
+        stripeProcessorId = CRM.vars.stripe.id;
         // this element may or may not exist on the webform, but we are dealing with a single (stripe) processor enabled.
         if (!$('input[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]').length) {
           chosenProcessorId = stripeProcessorId;
         } else {
-          chosenProcessorId = $form.find('input[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]:checked').val();
+          chosenProcessorId = form.querySelector('input[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]:checked').val();
         }
       }
       else {
         // Most forms have payment_processor-section but event registration has credit_card_info-section
-        if (($form.find(".crm-section.payment_processor-section").length > 0)
-            || ($form.find(".crm-section.credit_card_info-section").length > 0)) {
-          stripeProcessorId = $('#stripe-id').val();
-          chosenProcessorId = $form.find('input[name="payment_processor_id"]:checked').val();
+        if ((form.querySelector(".crm-section.payment_processor-section") !== null)
+          || (form.querySelector(".crm-section.credit_card_info-section") !== null)) {
+          stripeProcessorId = CRM.vars.stripe.id;
+          if (form.querySelector('input[name="payment_processor_id"]:checked') !== null) {
+            chosenProcessorId = form.querySelector('input[name="payment_processor_id"]:checked').value;
+          }
         }
       }
 
@@ -251,8 +310,8 @@ CRM.$(function($) {
       // - Is the Stripe processor ID defined?
       // - Is selected processor ID and stripe ID undefined? If we only have stripe ID, then there is only one (stripe) processor on the page
       if ((chosenProcessorId === 0)
-          || (stripeProcessorId == null)
-          || ((chosenProcessorId == null) && (stripeProcessorId == null))) {
+        || (stripeProcessorId == null)
+        || ((chosenProcessorId == null) && (stripeProcessorId == null))) {
         debugging('Not a Stripe transaction, or pay-later');
         return true;
       }
@@ -260,22 +319,18 @@ CRM.$(function($) {
         debugging('Stripe is the selected payprocessor');
       }
 
-      $form = getBillingForm();
-
       // Don't handle submits generated by non-stripe processors
-      if (!$('input#stripe-pub-key').length || !($('input#stripe-pub-key').val())) {
+      if (typeof CRM.vars.stripe.publishableKey === 'undefined') {
         debugging('submit missing stripe-pub-key element or value');
         return true;
       }
       // Don't handle submits generated by the CiviDiscount button.
-      if ($form.data('submit-dont-process')) {
+      if (form.dataset.submitdontprocess === true) {
         debugging('non-payment submit detected - not submitting payment');
         return true;
       }
 
-      $submit = getBillingSubmit();
-
-      if (isWebform) {
+      if (getIsDrupalWebform()) {
         // If we have selected Stripe but amount is 0 we don't submit via Stripe
         if ($('#billing-payment-block').is(':hidden')) {
           debugging('no payment processor on webform');
@@ -292,111 +347,124 @@ CRM.$(function($) {
         }
       }
 
-      // This is ONLY triggered in the following circumstances on a CiviCRM contribution page:
-      // - With a priceset that allows a 0 amount to be selected.
-      // - When Stripe is the ONLY payment processor configured on the page.
-      if (typeof calculateTotalFee == 'function') {
-        var totalFee = calculateTotalFee();
-        if (totalFee == '0') {
-          debugging("Total amount is 0");
-          return true;
-        }
-      }
-
-      // If there's no credit card field, no use in continuing (probably wrong
-      // context anyway)
-      if (!$form.find('#credit_card_number').length) {
-        debugging('No credit card field');
+      var totalFee = getTotalAmount();
+      if (totalFee == '0') {
+        debugging("Total amount is 0");
         return true;
       }
+
       // Lock to prevent multiple submissions
-      if ($form.data('submitted') === true) {
+      if (form.dataset.submitted === true) {
         // Previously submitted - don't submit again
         alert('Form already submitted. Please wait.');
         return false;
       } else {
         // Mark it so that the next submit can be ignored
-        // ADDED requirement that form be valid
-        if($form.valid()) {
-          $form.data('submitted', true);
-        }
+        form.dataset.submitted = true;
       }
 
       // Disable the submit button to prevent repeated clicks
-      $submit.prop('disabled', true);
-
-      var cc_month = $form.find('#credit_card_exp_date_M').val();
-      var cc_year = $form.find('#credit_card_exp_date_Y').val();
-
-      Stripe.card.createToken({
-        name: $form.find('#billing_first_name')
-          .val() + ' ' + $form.find('#billing_last_name').val(),
-        address_zip: $form.find('#billing_postal_code-5').val(),
-        number: $form.find('#credit_card_number').val(),
-        cvc: $form.find('#cvv2').val(),
-        exp_month: cc_month,
-        exp_year: cc_year
-      }, stripeResponseHandler);
-      debugging('Created Stripe token');
-      return false;
+      submitButton.setAttribute('disabled', true);
+
+      // Create a token when the form is submitted.
+      handleCardPayment();
+
+      return true;
     }
   }
 
-  function getIsWebform(form) {
-    // Pass in the billingForm object
-    // If the form has the webform-client-form (drupal 7) or webform-submission-form (drupal 8) class then it's a drupal webform!
-    return form.hasClass('webform-client-form') || form.hasClass('webform-submission-form');
+  function getIsDrupalWebform() {
+    // form class for drupal webform: webform-client-form (drupal 7); webform-submission-form (drupal 8)
+    if (form !== null) {
+      return form.classList.contains('webform-client-form') || form.classList.contains('webform-submission-form');
+    }
+    return false;
   }
 
   function getBillingForm() {
     // If we have a stripe billing form on the page
-    var $billingForm = $('input#stripe-pub-key').closest('form');
-    //if (!$billingForm.length && getIsWebform()) {
-      // If we are in a webform
-    //  $billingForm = $('.webform-client-form');
-    //}
-    if (!$billingForm.length) {
+    var billingFormID = $('div#card-element').closest('form').prop('id');
+    if (!billingFormID.length) {
       // If we have multiple payment processors to select and stripe is not currently loaded
-      $billingForm = $('input[name=hidden_processor]').closest('form');
+      billingFormID = $('input[name=hidden_processor]').closest('form').prop('id');
     }
-    return $billingForm;
+    // We have to use document.getElementById here so we have the right elementtype for appendChild()
+    return document.getElementById(billingFormID);
   }
 
   function getBillingSubmit() {
-    $form = getBillingForm();
-    var isWebform = getIsWebform($form);
-
-    if (isWebform) {
-      $submit = $form.find('[type="submit"].webform-submit');
-      if (!$submit.length) {
+    var submit = null;
+    if (getIsDrupalWebform()) {
+      submit = form.querySelector('[type="submit"].webform-submit');
+      if (!submit) {
         // drupal 8 webform
-        $submit = $form.find('[type="submit"].webform-button--submit');
+        submit = form.querySelector('[type="submit"].webform-button--submit');
       }
     }
     else {
-      $submit = $form.find('[type="submit"].validate');
+      submit = form.querySelector('[type="submit"].validate');
     }
-    return $submit;
+    return submit;
   }
 
-  function removeCCDetails($form, $truncate) {
-    // Remove the "name" attribute so params are not submitted
-    var ccNumElement = $form.find("input#credit_card_number");
-    var cvv2Element = $form.find("input#cvv2");
-    if ($truncate) {
-      ccNumElement.val('');
-      cvv2Element.val('');
+  function getTotalAmount() {
+    var totalFee = null;
+    if (typeof calculateTotalFee == 'function') {
+      // This is ONLY triggered in the following circumstances on a CiviCRM contribution page:
+      // - With a priceset that allows a 0 amount to be selected.
+      // - When Stripe is the ONLY payment processor configured on the page.
+      totalFee = calculateTotalFee();
     }
-    else {
-      var last4digits = ccNumElement.val().substr(12, 16);
-      ccNumElement.val('000000000000' + last4digits);
-      cvv2Element.val('000');
+    else if (getIsDrupalWebform()) {
+      // This is how webform civicrm calculates the amount in webform_civicrm_payment.js
+      $('.line-item:visible', '#wf-crm-billing-items').each(function() {
+        totalFee += parseFloat($(this).data('amount'));
+      });
+    }
+    return totalFee;
+  }
+
+  function updateFormElementsFromCreditCardDetails(event) {
+    if (!event.complete) {
+      return;
+    }
+    document.getElementById('billing_postal_code-' + CRM.vars.stripe.billingAddressID).value = event.value.postalCode;
+  }
+
+  function addSupportForCiviDiscount() {
+    // Add a keypress handler to set flag if enter is pressed
+    cividiscountElements = form.querySelectorAll('input#discountcode');
+    for (i = 0; i < cividiscountElements.length; ++i) {
+      cividiscountElements[i].addEventListener('keydown', function (e) {
+        if (e.keyCode === 13) {
+          e.preventDefault();
+          debugging('adding submitdontprocess');
+          form.dataset.submitdontprocess = true;
+        }
+      });
     }
   }
 
   function debugging (errorCode) {
     // Uncomment the following to debug unexpected returns.
-    //console.log(new Date().toISOString() + ' civicrm_stripe.js: ' + errorCode);
+    if ((typeof(CRM.vars.stripe) === 'undefined') || (Boolean(CRM.vars.stripe.jsDebug) === true)) {
+      console.log(new Date().toISOString() + ' civicrm_stripe.js: ' + errorCode);
+    }
+  }
+
+  function addDrupalWebformActionElement(submitAction) {
+    var hiddenInput = null;
+    if (document.getElementById('action') !== null) {
+      hiddenInput = document.getElementById('action');
+    }
+    else {
+      hiddenInput = document.createElement('input');
+    }
+    hiddenInput.setAttribute('type', 'hidden');
+    hiddenInput.setAttribute('name', 'op');
+    hiddenInput.setAttribute('id', 'action');
+    hiddenInput.setAttribute('value', submitAction);
+    form.appendChild(hiddenInput);
   }
 
 });
diff --git a/js/civicrm_stripe.min.js b/js/civicrm_stripe.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..daab8881a2c057f1deecc39ab2f58e17f2db8937
--- /dev/null
+++ b/js/civicrm_stripe.min.js
@@ -0,0 +1 @@
+CRM.$(function(d){var n;var b;var c;var a;var m=false;function s(y){f("paymentIntent confirmation success");var x=document.createElement("input");x.setAttribute("type","hidden");x.setAttribute("name","paymentIntentID");x.setAttribute("value",y.id);c.appendChild(x);c.submit()}function g(x){f("error: "+x.error.message);var y=document.getElementById("card-errors");y.style.display="block";y.textContent=x.error.message;document.querySelector("#billing-payment-block").scrollIntoView();window.scrollBy(0,-50);a.removeAttribute("disabled")}function v(){f("handle card payment");n.createPaymentMethod("card",b).then(function(x){if(x.error){g(x)}else{var y=CRM.url("civicrm/stripe/confirm-payment");d.post(y,{payment_method_id:x.paymentMethod.id,amount:o(),currency:CRM.vars.stripe.currency,id:CRM.vars.stripe.id}).then(function(z){t(z)})}})}function t(x){f("handleServerResponse");if(x.error){g(x)}else{if(x.requires_action){p(x)}else{s(x.paymentIntent)}}}function p(x){n.handleCardAction(x.payment_intent_client_secret).then(function(y){if(y.error){g(y)}else{s(y.paymentIntent)}})}var l=null;d(document).ready(function(){window.onbeforeunload=null;j();d("select#payment_processor_id").on("change",function(){d("input.payproc-metadata").remove()})});d(document).ajaxComplete(function(y,A,x){if((x.url.match("civicrm(/|%2F)payment(/|%2F)form")!=null)||(x.url.match("civicrm(/|%2F)contact(/|%2F)view(/|%2F)participant")!=null)){if(d("#payment_processor_id").length>0){var z=d("#payment_processor_id").val();if(z!=CRM.vars.stripe.id){f("payment processor changed to id: "+z);CRM.api3("PaymentProcessorType","getvalue",{sequential:1,"return":"id",name:"Stripe"}).done(function(B){var C=B.result;CRM.api3("PaymentProcessor","getvalue",{sequential:1,"return":"password",id:z,payment_processor_type_id:C}).done(function(D){var E=D.result;if(E){f("Setting new stripe key to: "+E);CRM.vars.stripe.publishableKey=E}else{f("New payment processor is not Stripe, setting stripe-pub-key to null");CRM.vars.stripe.publishableKey=null}j()})})}}j()}});function j(){if(typeof CRM.vars.stripe==="undefined"){f("CRM.vars.stripe not defined!");return}if(typeof Stripe==="undefined"){if(m){return}m=true;f("Stripe.js is not loaded!");d.getScript("https://js.stripe.com/v3",function(){f("Script loaded and executed.");m=false;e()})}else{e()}}function e(){n=Stripe(CRM.vars.stripe.publishableKey);var B=n.elements();var z={base:{fontSize:"20px"}};b=B.create("card",{style:z});b.mount("#card-element");document.getElementsByClassName("billing_postal_code-"+CRM.vars.stripe.billingAddressID+"-section")[0].setAttribute("hidden",true);b.addEventListener("change",function(C){r(C)});c=h();if(typeof c.length==="undefined"||c.length===0){f("No billing form!");return}a=w();c.dataset.submitdontprocess=false;var x=c.querySelectorAll('[type="submit"][formnovalidate="1"], [type="submit"][formnovalidate="formnovalidate"], [type="submit"].cancel, [type="submit"].webform-previous'),y;for(y=0;y<x.length;++y){x[y].addEventListener("click",function(){f("adding submitdontprocess");c.dataset.submitdontprocess=true})}a.addEventListener("click",function(C){f("clearing submitdontprocess");c.dataset.submitdontprocess=false;return A(C)});a.removeAttribute("onclick");k();if(u()){d("[type=submit]").click(function(){q(this.value)});c.addEventListener("keydown",function(C){if(C.keyCode===13){q(this.value);A(event)}});d("#billingcheckbox:input").hide();d('label[for="billingcheckbox"]').hide()}function A(E){E.preventDefault();f("submit handler");if(c.dataset.submitted===true){f("form already submitted");return false}var G;var D;if(typeof CRM.vars.stripe!=="undefined"){G=CRM.vars.stripe.id}if(u()){G=CRM.vars.stripe.id;if(!d('input[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]').length){D=G}else{D=c.querySelector('input[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]:checked').val()}}else{if((c.querySelector(".crm-section.payment_processor-section")!==null)||(c.querySelector(".crm-section.credit_card_info-section")!==null)){G=CRM.vars.stripe.id;if(c.querySelector('input[name="payment_processor_id"]:checked')!==null){D=c.querySelector('input[name="payment_processor_id"]:checked').value}}}if((D===0)||(G==null)||((D==null)&&(G==null))){f("Not a Stripe transaction, or pay-later");return true}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(u()){if(d("#billing-payment-block").is(":hidden")){f("no payment processor on webform");return true}var F=d('[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]');if(F.length){if(F.filter(":checked").val()==="0"||F.filter(":checked").val()===0){f("no payment processor selected");return true}}}var C=o();if(C=="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);v();return true}}function u(){if(c!==null){return c.classList.contains("webform-client-form")||c.classList.contains("webform-submission-form")}return false}function h(){var x=d("div#card-element").closest("form").prop("id");if(!x.length){x=d("input[name=hidden_processor]").closest("form").prop("id")}return document.getElementById(x)}function w(){var x=null;if(u()){x=c.querySelector('[type="submit"].webform-submit');if(!x){x=c.querySelector('[type="submit"].webform-button--submit')}}else{x=c.querySelector('[type="submit"].validate')}return x}function o(){var x=null;if(typeof calculateTotalFee=="function"){x=calculateTotalFee()}else{if(u()){d(".line-item:visible","#wf-crm-billing-items").each(function(){x+=parseFloat(d(this).data("amount"))})}}return x}function r(x){if(!x.complete){return}document.getElementById("billing_postal_code-"+CRM.vars.stripe.billingAddressID).value=x.value.postalCode}function k(){cividiscountElements=c.querySelectorAll("input#discountcode");for(i=0;i<cividiscountElements.length;++i){cividiscountElements[i].addEventListener("keydown",function(x){if(x.keyCode===13){x.preventDefault();f("adding submitdontprocess");c.dataset.submitdontprocess=true}})}}function f(x){if((typeof(CRM.vars.stripe)==="undefined")||(Boolean(CRM.vars.stripe.jsDebug)===true)){console.log(new Date().toISOString()+" civicrm_stripe.js: "+x)}}function q(y){var x=null;if(document.getElementById("action")!==null){x=document.getElementById("action")}else{x=document.createElement("input")}x.setAttribute("type","hidden");x.setAttribute("name","op");x.setAttribute("id","action");x.setAttribute("value",y);c.appendChild(x)}});
\ No newline at end of file
diff --git a/settings/stripe.setting.php b/settings/stripe.setting.php
new file mode 100644
index 0000000000000000000000000000000000000000..2eb58cd04ca8160ed148f0f9e5552e5d0c59ceec
--- /dev/null
+++ b/settings/stripe.setting.php
@@ -0,0 +1,23 @@
+<?php
+
+use CRM_Stripe_ExtensionUtil as E;
+
+return [
+  'stripe_jsdebug' => [
+    'name' => 'stripe_jsdebug',
+    'type' => 'Boolean',
+    'html_type' => 'checkbox',
+    'default' => 0,
+    'add' => '5.13',
+    'is_domain' => 1,
+    'is_contact' => 0,
+    'title' => E::ts('Enable Stripe Javascript debugging?'),
+    'description' => E::ts('Enables debug logging to browser console for stripe payment processors.'),
+    'html_attributes' => [],
+    'settings_pages' => [
+      'stripe' => [
+        'weight' => 10,
+      ]
+    ],
+  ]
+];
diff --git a/stripe.mgd.php b/stripe.mgd.php
index 2b5b3c2c936db6cb02d48bd34e889eab8bbdbb6f..486bf445bbdf5ec62f8a6140bccdd1caa2eac394 100644
--- a/stripe.mgd.php
+++ b/stripe.mgd.php
@@ -15,8 +15,8 @@ return [
       'title' => 'Stripe',
       'description' => 'Stripe Payment Processor',
       'class_name' => 'Payment_Stripe',
-      'user_name_label' => 'Secret Key',
-      'password_label' => 'Publishable key',
+      'user_name_label' => 'Publishable key',
+      'password_label' => 'Secret Key',
       'url_site_default' => 'https://api.stripe.com/v2',
       'url_recur_default' => 'https://api.stripe.com/v2',
       'url_site_test_default' => 'https://api.stripe.com/v2',
diff --git a/stripe.php b/stripe.php
index f9eefe6a7b6edc7870e7c3358a42a98c4a5c39ca..c3bb467ab1a60654c4524fb1fa222532bdb52394 100644
--- a/stripe.php
+++ b/stripe.php
@@ -74,43 +74,47 @@ function stripe_civicrm_managed(&$entities) {
 }
 
 /**
-   * Implementation of hook_civicrm_validateForm().
-   *
-   * Prevent server validation of cc fields
-   *
-   * @param $formName - the name of the form
-   * @param $fields - Array of name value pairs for all 'POST'ed form values
-   * @param $files - Array of file properties as sent by PHP POST protocol
-   * @param $form - reference to the form object
-   * @param $errors - Reference to the errors array.
-   *
-*/
+ * Implements hook_civicrm_alterSettingsFolders().
+ */
+function stripe_civicrm_alterSettingsFolders(&$metaDataFolders = NULL) {
+  _stripe_civix_civicrm_alterSettingsFolders($metaDataFolders);
+}
 
- function stripe_civicrm_validateForm($formName, &$fields, &$files, &$form, &$errors) {
-    if (empty($form->_paymentProcessor['payment_processor_type'])) {
-      return;
-    }
-    // If Stripe is active here.
-    if ($form->_paymentProcessor['class_name'] == 'Payment_Stripe') {
-      if (isset($form->_elementIndex['stripe_token'])) {
-        if ($form->elementExists('credit_card_number')) {
-          $cc_field = $form->getElement('credit_card_number');
-          $form->removeElement('credit_card_number', true);
-          $form->addElement($cc_field);
-        }
-        if ($form->elementExists('cvv2')) {
-          $cvv2_field = $form->getElement('cvv2');
-          $form->removeElement('cvv2', true);
-          $form->addElement($cvv2_field);
-        }
+/**
+ * Implementation of hook_civicrm_validateForm().
+ *
+ * Prevent server validation of cc fields
+ *
+ * @param $formName - the name of the form
+ * @param $fields - Array of name value pairs for all 'POST'ed form values
+ * @param $files - Array of file properties as sent by PHP POST protocol
+ * @param $form - reference to the form object
+ * @param $errors - Reference to the errors array.
+ *
+ */
+
+function stripe_civicrm_validateForm($formName, &$fields, &$files, &$form, &$errors) {
+  if (empty($form->_paymentProcessor['payment_processor_type'])) {
+    return;
+  }
+  // If Stripe is active here.
+  if ($form->_paymentProcessor['class_name'] == 'Payment_Stripe') {
+    if (isset($form->_elementIndex['stripe_token'])) {
+      if ($form->elementExists('credit_card_number')) {
+        $cc_field = $form->getElement('credit_card_number');
+        $form->removeElement('credit_card_number', true);
+        $form->addElement($cc_field);
+      }
+      if ($form->elementExists('cvv2')) {
+        $cvv2_field = $form->getElement('cvv2');
+        $form->removeElement('cvv2', true);
+        $form->addElement($cvv2_field);
       }
-    } else {
-      return;
     }
+  } else {
+    return;
   }
-
-// Flag so we don't add the stripe scripts more than once.
-static $_stripe_scripts_added;
+}
 
 /**
  * Implementation of hook_civicrm_alterContent
@@ -121,7 +125,6 @@ static $_stripe_scripts_added;
  * @return void
  */
 function stripe_civicrm_alterContent( &$content, $context, $tplName, &$object ) {
-  global $_stripe_scripts_added;
   /* Adding stripe js:
    * - Webforms don't get scripts added by hook_civicrm_buildForm so we have to user alterContent
    * - (Webforms still call buildForm and it looks like they are added but they are not,
@@ -132,12 +135,12 @@ function stripe_civicrm_alterContent( &$content, $context, $tplName, &$object )
    *
    */
   if (($context == 'form' && !empty($object->_paymentProcessor['class_name']))
-     || (($context == 'page') && !empty($object->_isPaymentProcessor))) {
-    if (!$_stripe_scripts_added || $object instanceof CRM_Financial_Form_Payment) {
-      $stripeJSURL = CRM_Core_Resources::singleton()
-        ->getUrl('com.drastikbydesign.stripe', 'js/civicrm_stripe.js');
+    || (($context == 'page') && !empty($object->_isPaymentProcessor))) {
+    if (!isset(\Civi::$statics[E::LONG_NAME]['stripeJSLoaded']) || $object instanceof CRM_Financial_Form_Payment) {
+      $min = ((boolean) \Civi::settings()->get('stripe_jsdebug')) ? '' : '.min';
+      $stripeJSURL = \Civi::resources()->getUrl(E::LONG_NAME, "js/civicrm_stripe{$min}.js");
       $content .= "<script src='{$stripeJSURL}'></script>";
-      $_stripe_scripts_added = TRUE;
+      \Civi::$statics[E::LONG_NAME]['stripeJSLoaded'] = TRUE;
     }
   }
 }
@@ -150,17 +153,57 @@ function stripe_civicrm_alterContent( &$content, $context, $tplName, &$object )
  * @param CRM_Core_Form $form
  */
 function stripe_civicrm_buildForm($formName, &$form) {
-  global $_stripe_scripts_added;
-  if (!isset($form->_paymentProcessor)) {
+  // Don't load stripe js on ajax forms
+  if (CRM_Utils_Request::retrieveValue('snippet', 'String') === 'json') {
+    return;
+  }
+
+  // Load stripe.js on all civi forms per stripe requirements
+  if (!isset(\Civi::$statics[E::LONG_NAME]['stripeJSLoaded'])) {
+    \Civi::resources()->addScriptUrl('https://js.stripe.com/v3');
+    \Civi::$statics[E::LONG_NAME]['stripeJSLoaded'] = TRUE;
+  }
+}
+
+/**
+ * Implements hook_civicrm_postProcess().
+ *
+ * @param string $formName
+ * @param \CRM_Core_Form $form
+ */
+function stripe_civicrm_postProcess($formName, &$form) {
+  // We're only interested in forms that have a paymentprocessor
+  if (empty($form->get('paymentProcessor')) || ($form->get('paymentProcessor')['class_name'] !== 'Payment_Stripe')) {
     return;
   }
-  $paymentProcessor = $form->_paymentProcessor;
-  if (!empty($paymentProcessor['class_name'])) {
-    if (!$_stripe_scripts_added) {
-      CRM_Core_Resources::singleton()
-        ->addScriptFile('com.drastikbydesign.stripe', 'js/civicrm_stripe.js');
+
+  // Retrieve the paymentIntentID that was posted along with the form and add it to the form params
+  //  This allows multi-page checkout to work (eg. register->confirm->thankyou)
+  $params = $form->get('params');
+  if (!$params) {
+    // @fixme Hack for contributionpages - see https://github.com/civicrm/civicrm-core/pull/15252
+    $params = $form->getVar('_params');
+    $hackForContributionPages = TRUE;
+  }
+  if (isset($params['amount'])) {
+    // Contribution pages have params directly in the main array
+    $paymentParams = &$params;
+  }
+  elseif (isset($params[0]['amount'])) {
+    // Event registration pages have params in a sub-array
+    $paymentParams = &$params[0];
+  }
+  else {
+    return;
+  }
+  $paymentIntentID = CRM_Utils_Request::retrieveValue('paymentIntentID', 'String');
+  if ($paymentIntentID) {
+    $paymentParams['paymentIntentID'] = $paymentIntentID;
+    $form->set('params', $params);
+    if (isset($hackForContributionPages)) {
+      // @fixme Hack for contributionpages - see https://github.com/civicrm/civicrm-core/pull/15252
+      CRM_Core_Session::singleton()->set('stripePaymentIntent', $paymentIntentID);
     }
-    $_stripe_scripts_added = TRUE;
   }
 }
 
@@ -169,4 +212,20 @@ function stripe_civicrm_buildForm($formName, &$form) {
  */
 function stripe_civicrm_check(&$messages) {
   CRM_Stripe_Webhook::check($messages);
+  CRM_Stripe_Check::checkRequirements($messages);
+}
+
+/**
+ * Implements hook_civicrm_navigationMenu().
+ */
+function stripe_civicrm_navigationMenu(&$menu) {
+  _stripe_civix_insert_navigation_menu($menu, 'Administer/CiviContribute', array(
+    'label' => E::ts('Stripe Settings'),
+    'name' => 'stripe_settings',
+    'url' => 'civicrm/admin/setting/stripe',
+    'permission' => 'administer CiviCRM',
+    'operator' => 'OR',
+    'separator' => 0,
+  ));
+  _stripe_civix_navigationMenu($menu);
 }
diff --git a/templates/CRM/Core/Payment/Stripe/Card.tpl b/templates/CRM/Core/Payment/Stripe/Card.tpl
new file mode 100644
index 0000000000000000000000000000000000000000..aa3e1bd86f6d5d013e7360e31053913fe0dcef8c
--- /dev/null
+++ b/templates/CRM/Core/Payment/Stripe/Card.tpl
@@ -0,0 +1,21 @@
+{* https://civicrm.org/licensing *}
+
+{* Manually create the CRM.vars.stripe here for drupal webform because \Civi::resources()->addVars() does not work in this context *}
+{literal}
+<script type="text/javascript">
+  CRM.$(function($) {
+    $(document).ready(function() {
+      if (typeof CRM.vars.stripe === 'undefined') {
+        var stripe = {{/literal}{foreach from=$stripeJSVars key=arrayKey item=arrayValue}{$arrayKey}:'{$arrayValue}',{/foreach}{literal}};
+        CRM.vars.stripe = stripe;
+      }
+    });
+  });
+</script>
+{/literal}
+
+{* Add the components required for a Stripe card element *}
+<label for="card-element"><legend>Credit or debit card</legend></label>
+<div id="card-element"></div>
+{* Area for Stripe to report errors *}
+<div id="card-errors" role="alert" class="alert alert-danger"></div>
diff --git a/tests/phpunit/CRM/Stripe/BaseTest.php b/tests/phpunit/CRM/Stripe/BaseTest.php
index c5b45208bb7cc79a17063a1a02c10bc6abb3e2d0..b23d69f90bc051c1f2c1900bb08aa8d679312848 100644
--- a/tests/phpunit/CRM/Stripe/BaseTest.php
+++ b/tests/phpunit/CRM/Stripe/BaseTest.php
@@ -39,7 +39,7 @@ class CRM_Stripe_BaseTest extends \PHPUnit_Framework_TestCase implements Headles
   // Secret/public keys are PTP test keys.
   protected $_sk = 'sk_test_TlGdeoi8e1EOPC3nvcJ4q5UZ';
   protected $_pk = 'pk_test_k2hELLGpBLsOJr6jZ2z9RaYh';
-  protected $_cc = NULL; 
+  protected $_cc = NULL;
 
   public function setUpHeadless() {
     // Civi\Test has many helpers, like install(), uninstall(), sql(), and sqlFile().
@@ -111,8 +111,8 @@ class CRM_Stripe_BaseTest extends \PHPUnit_Framework_TestCase implements Headles
     $processor = array_pop($result['values']);
     $this->_sk = $processor['user_name'];
     $this->_pk = $processor['password'];
-    $this->_paymentProcessor = $processor; 
-    $this->_paymentProcessorID = $result['id']; 
+    $this->_paymentProcessor = $processor;
+    $this->_paymentProcessorID = $result['id'];
   }
 
   /**
@@ -132,9 +132,9 @@ class CRM_Stripe_BaseTest extends \PHPUnit_Framework_TestCase implements Headles
       ), $params);
     $result = civicrm_api3('ContributionPage', 'create', $params);
     $this->assertEquals(0, $result['is_error']);
-    $this->_contributionPageID = $result['id']; 
+    $this->_contributionPageID = $result['id'];
   }
-  
+
   /**
    * Submit to stripe
    */
@@ -179,8 +179,9 @@ class CRM_Stripe_BaseTest extends \PHPUnit_Framework_TestCase implements Headles
   public function assertValidTrxn() {
     $this->assertNotEmpty($this->_trxn_id, "A trxn id was assigned");
 
-    \Stripe\Stripe::setApiKey($this->_sk);
-    $found = FALSE;
+    $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $this->_paymentProcessorID]));
+    $processor->setAPIParams();
+
     try {
       $results = \Stripe\Charge::retrieve(array( "id" => $this->_trxn_id));
       $found = TRUE;
@@ -188,10 +189,10 @@ class CRM_Stripe_BaseTest extends \PHPUnit_Framework_TestCase implements Headles
     catch (Stripe_Error $e) {
       $found = FALSE;
     }
-    
+
     $this->assertTrue($found, 'Assigned trxn_id is valid.');
 
-  }  
+  }
   /**
    * Create contribition
    */
@@ -213,7 +214,7 @@ class CRM_Stripe_BaseTest extends \PHPUnit_Framework_TestCase implements Headles
     ), $params));
     $this->assertEquals(0, $contribution['is_error']);
     $this->_contributionID = $contribution['id'];
-  } 
+  }
 
   public function createOrganization() {
     if (!empty($this->_orgID)) {
@@ -230,7 +231,7 @@ class CRM_Stripe_BaseTest extends \PHPUnit_Framework_TestCase implements Headles
     CRM_Member_PseudoConstant::flush('membershipType');
     CRM_Core_Config::clearDBCache();
     $this->createOrganization();
-    $params = array( 
+    $params = array(
       'name' => 'General',
       'duration_unit' => 'year',
       'duration_interval' => 1,
@@ -251,5 +252,5 @@ class CRM_Stripe_BaseTest extends \PHPUnit_Framework_TestCase implements Headles
     CRM_Utils_Cache::singleton()->flush();
   }
 
-  
+
 }
diff --git a/tests/phpunit/CRM/Stripe/IpnTest.php b/tests/phpunit/CRM/Stripe/IpnTest.php
index 64069ff64d30605725dae244843e4d8aa6a7732f..d616615290ddef0f4a30556f32ed7933f201db26 100644
--- a/tests/phpunit/CRM/Stripe/IpnTest.php
+++ b/tests/phpunit/CRM/Stripe/IpnTest.php
@@ -27,7 +27,7 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest {
   protected $_frequency_interval = 1;
   protected $_membershipID;
 
-  // This test is particularly dirty for some reason so we have to 
+  // This test is particularly dirty for some reason so we have to
   // force a reset.
   public function setUpHeadless() {
     $force = TRUE;
@@ -71,13 +71,15 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest {
     $this->doPayment($payment_extra_params);
 
     // Now check to see if an event was triggered and if so, process it.
-    $payment_object = $this->getEvent('invoice.payment_succeeded'); 
+    $payment_object = $this->getEvent('invoice.payment_succeeded');
     if ($payment_object) {
       $this->ipn($payment_object);
     }
 
-    // Now that we have a recurring contribution, let's update it. 
-    \Stripe\Stripe::setApiKey($this->_sk);
+    // Now that we have a recurring contribution, let's update it.
+    $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $this->_paymentProcessorID]));
+    $processor->setAPIParams();
+
     $sub = \Stripe\Subscription::retrieve($this->_subscriptionID);
 
     // Create a new plan if it doesn't yet exist.
@@ -110,7 +112,7 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest {
     $sub->save();
 
     // Now check to see if an event was triggered and if so, process it.
-    $payment_object = $this->getEvent('customer.subscription.updated'); 
+    $payment_object = $this->getEvent('customer.subscription.updated');
     if ($payment_object) {
       $this->ipn($payment_object);
     }
@@ -123,8 +125,8 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest {
       'return' => array('id'),
     );
     $result = civicrm_api3('ContributionRecur', 'getsingle', $params);
-    $newContributionRecurID = $result['id']; 
-    
+    $newContributionRecurID = $result['id'];
+
     // Now ensure that the membership record is updated to have this
     // new recurring contribution id.
     $membership_contribution_recur_id = civicrm_api3('Membership', 'getvalue', array(
@@ -135,7 +137,7 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest {
 
     // Delete the new plan so we can cleanly run the next time.
     $plan->delete();
-    
+
   }
 
   /**
@@ -155,7 +157,7 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest {
     $this->doPayment($payment_extra_params);
 
     // Now check to see if an event was triggered and if so, process it.
-    $payment_object = $this->getEvent('invoice.payment_succeeded'); 
+    $payment_object = $this->getEvent('invoice.payment_succeeded');
     if ($payment_object) {
       // Now manipulate the transaction so it appears to be a failed one.
       $payment_object->type = 'invoice.payment_failed';
@@ -192,7 +194,7 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest {
     $this->doPayment($payment_extra_params);
 
     // Now check to see if an event was triggered and if so, process it.
-    $payment_object = $this->getEvent('invoice.payment_succeeded'); 
+    $payment_object = $this->getEvent('invoice.payment_succeeded');
     if ($payment_object) {
       $this->ipn($payment_object);
     }
@@ -201,15 +203,17 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest {
     $this->assertEquals(1, $contribution_status_id, "Recurring payment was properly processed via a stripe event.");
 
     // Now, cancel the subscription and ensure it is properly cancelled.
-    \Stripe\Stripe::setApiKey($this->_sk);
+    $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $this->_paymentProcessorID]));
+    $processor->setAPIParams();
+
     $sub = \Stripe\Subscription::retrieve($this->_subscriptionID);
     $sub->cancel();
 
-    $sub_object = $this->getEvent('customer.subscription.deleted'); 
+    $sub_object = $this->getEvent('customer.subscription.deleted');
     if ($sub_object) {
       $this->ipn($sub_object);
     }
-    $this->assertContributionRecurIsCancelled();  
+    $this->assertContributionRecurIsCancelled();
   }
 
   public function assertContributionRecurIsCancelled() {
@@ -239,7 +243,7 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest {
     $params['output'] = 'raw';
 
     // Now try to retrieve this transaction.
-    $transactions = civicrm_api3('Stripe', 'listevents', $params );    
+    $transactions = civicrm_api3('Stripe', 'listevents', $params );
     foreach($transactions['values']['data'] as $transaction) {
       if ($transaction->data->object->$property == $this->_subscriptionID) {
         return $transaction;
@@ -254,7 +258,7 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest {
    *
    */
   public function ipn($data, $verify = TRUE) {
-    // The $_GET['processor_id'] value is normally set by 
+    // The $_GET['processor_id'] value is normally set by
     // CRM_Core_Payment::handlePaymentMethod
     $_GET['processor_id'] = $this->_paymentProcessorID;
     $ipnClass = new CRM_Core_Payment_StripeIPN($data, $verify);
@@ -272,7 +276,7 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest {
       'amount' => $this->_total,
       'sequential' => 1,
       'installments' => $this->_installments,
-      'frequency_unit' => $this->_frequency_unit, 
+      'frequency_unit' => $this->_frequency_unit,
       'frequency_interval' => $this->_frequency_interval,
       'invoice_id' => $this->_invoiceID,
       'contribution_status_id' => 2,
@@ -293,5 +297,5 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest {
     $this->assertEquals(0, $contributionRecur['is_error']);
     $this->_contributionRecurID = $contributionRecur['id'];
     $this->_contributionID = $contributionRecur['values']['0']['api.contribution.create']['id'];
-  } 
+  }
 }
diff --git a/utils/fix-issue-44.php b/utils/fix-issue-44.php
index 53c127f73822a5de7e443f38e9f7c87eb1dd5972..a2d6d768779975d2330c9eee473c8eef9c671e7b 100644
--- a/utils/fix-issue-44.php
+++ b/utils/fix-issue-44.php
@@ -51,7 +51,8 @@ while ($dao->fetch()) {
   if (!$paymentProcessor) {
     echo "Failed to find a stripe payment processor for recurring contrib $dao->contribution_recur_id\n";
   }
-  Stripe::setApiKey(CRM_Core_Payment_Stripe::getSecretKey($paymentProcessor));
+  $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $paymentProcessor['id']]));
+  $processor->setAPIParams();
 
   try {
     $results = Charge::retrieve(['id' => $dao->trxn_id]);
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 151112a5e753f9ac2985156e637b392a743bdbd1..eae21a68a560b15fed848122b36c89c063c27ee2 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -1,17 +1,17 @@
 [
     {
         "name": "stripe/stripe-php",
-        "version": "v6.40.0",
-        "version_normalized": "6.40.0.0",
+        "version": "v6.43.1",
+        "version_normalized": "6.43.1.0",
         "source": {
             "type": "git",
             "url": "https://github.com/stripe/stripe-php.git",
-            "reference": "9c22ffab790ef4dae0f371929de50e8b53c9ec8d"
+            "reference": "42fcdaf99c44bb26937223f8eae1f263491d5ab8"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/stripe/stripe-php/zipball/9c22ffab790ef4dae0f371929de50e8b53c9ec8d",
-            "reference": "9c22ffab790ef4dae0f371929de50e8b53c9ec8d",
+            "url": "https://api.github.com/repos/stripe/stripe-php/zipball/42fcdaf99c44bb26937223f8eae1f263491d5ab8",
+            "reference": "42fcdaf99c44bb26937223f8eae1f263491d5ab8",
             "shasum": ""
         },
         "require": {
@@ -26,7 +26,7 @@
             "squizlabs/php_codesniffer": "~2.0",
             "symfony/process": "~2.8"
         },
-        "time": "2019-06-27T23:24:51+00:00",
+        "time": "2019-08-29T16:56:12+00:00",
         "type": "library",
         "extra": {
             "branch-alias": {
diff --git a/vendor/stripe/stripe-php/CHANGELOG.md b/vendor/stripe/stripe-php/CHANGELOG.md
index b918de61fd4885a4f9202a16a24619567140db0a..5554f4ceb1fadc611e15b9c36a922817ba813fab 100644
--- a/vendor/stripe/stripe-php/CHANGELOG.md
+++ b/vendor/stripe/stripe-php/CHANGELOG.md
@@ -1,5 +1,22 @@
 # Changelog
 
+## 6.43.1 - 2019-08-29
+* [#722](https://github.com/stripe/stripe-php/pull/722) Make `LoggerInterface::error` compatible with its PSR-3 counterpart
+* [#714](https://github.com/stripe/stripe-php/pull/714) Add `pending_setup_intent` property in `Subscription`
+* [#713](https://github.com/stripe/stripe-php/pull/713) Add typehint to `ApiResponse`
+* [#712](https://github.com/stripe/stripe-php/pull/712) Fix comment
+* [#701](https://github.com/stripe/stripe-php/pull/701) Start testing PHP 7.3
+
+## 6.43.0 - 2019-08-09
+* [#694](https://github.com/stripe/stripe-php/pull/694) Add `SubscriptionItem::createUsageRecord` method
+
+## 6.42.0 - 2019-08-09
+* [#688](https://github.com/stripe/stripe-php/pull/688) Remove `SubscriptionScheduleRevision`
+  * Note that this is technically a breaking change, however we've chosen to release it as a minor version in light of the fact that this resource and its API methods were virtually unused.
+
+## 6.41.0 - 2019-07-31
+* [#683](https://github.com/stripe/stripe-php/pull/683) Move the List Balance History API to `/v1/balance_transactions`
+
 ## 6.40.0 - 2019-06-27
 * [#675](https://github.com/stripe/stripe-php/pull/675) Add support for `SetupIntent` resource and APIs
 
@@ -57,7 +74,7 @@
 * [#642](https://github.com/stripe/stripe-php/pull/642) Fix an issue where existing idempotency keys would be overwritten when using automatic retries
 
 ## 6.34.1 - 2019-04-25
-* [#640](https://github.com/stripe/stripe-php/pull/640) Add missing phpdocs 
+* [#640](https://github.com/stripe/stripe-php/pull/640) Add missing phpdocs
 
 ## 6.34.0 - 2019-04-24
 * [#626](https://github.com/stripe/stripe-php/pull/626) Add support for the `TaxRate` resource and APIs
@@ -145,7 +162,7 @@
 * [#564](https://github.com/stripe/stripe-php/pull/564) Add event name constants for subscription schedule aborted/expiring
 
 ## 6.23.0 - 2018-11-27
-* [#542](https://github.com/stripe/stripe-php/pull/542) Add support for `ValueList` and `ValueListItem` for Radar 
+* [#542](https://github.com/stripe/stripe-php/pull/542) Add support for `ValueList` and `ValueListItem` for Radar
 
 ## 6.22.1 - 2018-11-20
 * [#561](https://github.com/stripe/stripe-php/pull/561) Add cast and some docs to telemetry introduced in 6.22.0/#549
@@ -399,7 +416,7 @@ Pull requests included in this release:
 * Add support for payouts and recipient transfers
 
 ## 4.6.0 - 2017-04-06
-* Please see 4.7.0 instead (no-op release)	
+* Please see 4.7.0 instead (no-op release)
 
 ## 4.5.1 - 2017-03-22
 * Remove hard dependency on cURL
diff --git a/vendor/stripe/stripe-php/VERSION b/vendor/stripe/stripe-php/VERSION
index 353819c7ea3c30b1aba04dc9d639ec7100f93d8c..eb34ef6cbc3aff7ca93ca07ec2b61e5f0b7652cf 100644
--- a/vendor/stripe/stripe-php/VERSION
+++ b/vendor/stripe/stripe-php/VERSION
@@ -1 +1 @@
-6.40.0
+6.43.1
diff --git a/vendor/stripe/stripe-php/init.php b/vendor/stripe/stripe-php/init.php
index e893cfcda90b8a3cf7dcb4e3f9f65f48d8ee76fe..05243999637672b8af6e4732b2a890947439c15b 100644
--- a/vendor/stripe/stripe-php/init.php
+++ b/vendor/stripe/stripe-php/init.php
@@ -122,7 +122,6 @@ require(dirname(__FILE__) . '/lib/SourceTransaction.php');
 require(dirname(__FILE__) . '/lib/Subscription.php');
 require(dirname(__FILE__) . '/lib/SubscriptionItem.php');
 require(dirname(__FILE__) . '/lib/SubscriptionSchedule.php');
-require(dirname(__FILE__) . '/lib/SubscriptionScheduleRevision.php');
 require(dirname(__FILE__) . '/lib/TaxId.php');
 require(dirname(__FILE__) . '/lib/TaxRate.php');
 require(dirname(__FILE__) . '/lib/Terminal/ConnectionToken.php');
diff --git a/vendor/stripe/stripe-php/lib/Account.php b/vendor/stripe/stripe-php/lib/Account.php
index 1adc6b79d53ba128a3fcd1ac75891d07d9354643..4993ca3541436b10d428193a9b35ad6934a5c5e7 100644
--- a/vendor/stripe/stripe-php/lib/Account.php
+++ b/vendor/stripe/stripe-php/lib/Account.php
@@ -30,7 +30,6 @@ namespace Stripe;
  */
 class Account extends ApiResource
 {
-
     const OBJECT_NAME = "account";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/AccountLink.php b/vendor/stripe/stripe-php/lib/AccountLink.php
index f2975ae38343b80516880e29c1e743e487e89916..692447dbf69ec131187e3394cc87e2266f70f9f6 100644
--- a/vendor/stripe/stripe-php/lib/AccountLink.php
+++ b/vendor/stripe/stripe-php/lib/AccountLink.php
@@ -14,7 +14,6 @@ namespace Stripe;
  */
 class AccountLink extends ApiResource
 {
-
     const OBJECT_NAME = "account_link";
 
     use ApiOperations\Create;
diff --git a/vendor/stripe/stripe-php/lib/AlipayAccount.php b/vendor/stripe/stripe-php/lib/AlipayAccount.php
index 70a281bd768af5b55f7b05ec1c88acec20482e18..a79705fa4ebb459a9020a32676f070688e39262a 100644
--- a/vendor/stripe/stripe-php/lib/AlipayAccount.php
+++ b/vendor/stripe/stripe-php/lib/AlipayAccount.php
@@ -12,7 +12,6 @@ namespace Stripe;
  */
 class AlipayAccount extends ApiResource
 {
-
     const OBJECT_NAME = "alipay_account";
 
     use ApiOperations\Delete;
diff --git a/vendor/stripe/stripe-php/lib/ApiRequestor.php b/vendor/stripe/stripe-php/lib/ApiRequestor.php
index 7cf851877deb1073854abd79d963afbd87f4010f..1f75a007b327c0eece940222d0fada955f642312 100644
--- a/vendor/stripe/stripe-php/lib/ApiRequestor.php
+++ b/vendor/stripe/stripe-php/lib/ApiRequestor.php
@@ -205,7 +205,7 @@ class ApiRequestor
                     return new Error\Idempotency($msg, $rcode, $rbody, $resp, $rheaders);
                 }
 
-                // intentional fall-through
+                // no break
             case 404:
                 return new Error\InvalidRequest($msg, $param, $rcode, $rbody, $resp, $rheaders);
             case 401:
diff --git a/vendor/stripe/stripe-php/lib/ApiResponse.php b/vendor/stripe/stripe-php/lib/ApiResponse.php
index 31f54a50df00888d67ef4e4484ae656f8255ad97..acca75123e67ee1b94a0ea57d01bbcd2bf092f2c 100644
--- a/vendor/stripe/stripe-php/lib/ApiResponse.php
+++ b/vendor/stripe/stripe-php/lib/ApiResponse.php
@@ -2,6 +2,8 @@
 
 namespace Stripe;
 
+use Stripe\Util\CaseInsensitiveArray;
+
 /**
  * Class ApiResponse
  *
@@ -17,7 +19,7 @@ class ApiResponse
     /**
      * @param string $body
      * @param integer $code
-     * @param array|null $headers
+     * @param array|CaseInsensitiveArray|null $headers
      * @param array|null $json
      *
      * @return obj An APIResponse
diff --git a/vendor/stripe/stripe-php/lib/ApplePayDomain.php b/vendor/stripe/stripe-php/lib/ApplePayDomain.php
index ea84220a7a93d6ff82ab7b4f642e86e09a9688d1..57687c4f2e4bd0e76a850c577e61e0d2fd36071b 100644
--- a/vendor/stripe/stripe-php/lib/ApplePayDomain.php
+++ b/vendor/stripe/stripe-php/lib/ApplePayDomain.php
@@ -9,7 +9,6 @@ namespace Stripe;
  */
 class ApplePayDomain extends ApiResource
 {
-
     const OBJECT_NAME = "apple_pay_domain";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/ApplicationFee.php b/vendor/stripe/stripe-php/lib/ApplicationFee.php
index 0d9fde4c521d6328655b3be74b3b3050864f8868..4b83071ade40b4a14dda663eb9b5bae379fc162c 100644
--- a/vendor/stripe/stripe-php/lib/ApplicationFee.php
+++ b/vendor/stripe/stripe-php/lib/ApplicationFee.php
@@ -24,7 +24,6 @@ namespace Stripe;
  */
 class ApplicationFee extends ApiResource
 {
-
     const OBJECT_NAME = "application_fee";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/ApplicationFeeRefund.php b/vendor/stripe/stripe-php/lib/ApplicationFeeRefund.php
index 91d7e9d2bc665b3988173c9c60490401157bacf2..b242f2024b48b2a93ad224b987dc32017cf8236d 100644
--- a/vendor/stripe/stripe-php/lib/ApplicationFeeRefund.php
+++ b/vendor/stripe/stripe-php/lib/ApplicationFeeRefund.php
@@ -18,7 +18,6 @@ namespace Stripe;
  */
 class ApplicationFeeRefund extends ApiResource
 {
-
     const OBJECT_NAME = "fee_refund";
 
     use ApiOperations\Update {
diff --git a/vendor/stripe/stripe-php/lib/Balance.php b/vendor/stripe/stripe-php/lib/Balance.php
index 25f88ae74c7886e83bfbc1d6728faa5b32eb1078..b99871adb85a9bf8aa154b566e1e5f3c7d805416 100644
--- a/vendor/stripe/stripe-php/lib/Balance.php
+++ b/vendor/stripe/stripe-php/lib/Balance.php
@@ -15,7 +15,6 @@ namespace Stripe;
  */
 class Balance extends SingletonApiResource
 {
-
     const OBJECT_NAME = "balance";
 
     /**
diff --git a/vendor/stripe/stripe-php/lib/BalanceTransaction.php b/vendor/stripe/stripe-php/lib/BalanceTransaction.php
index c306ab067912ff223856d0df82334e423249f0f2..78805d4d448a3de900751d0c170aed26678595ef 100644
--- a/vendor/stripe/stripe-php/lib/BalanceTransaction.php
+++ b/vendor/stripe/stripe-php/lib/BalanceTransaction.php
@@ -24,7 +24,6 @@ namespace Stripe;
  */
 class BalanceTransaction extends ApiResource
 {
-
     const OBJECT_NAME = "balance_transaction";
 
     use ApiOperations\All;
@@ -63,13 +62,4 @@ class BalanceTransaction extends ApiResource
     const TYPE_TRANSFER_CANCEL               = 'transfer_cancel';
     const TYPE_TRANSFER_FAILURE              = 'transfer_failure';
     const TYPE_TRANSFER_REFUND               = 'transfer_refund';
-
-    /**
-     * @return string The class URL for this resource. It needs to be special
-     *    cased because it doesn't fit into the standard resource pattern.
-     */
-    public static function classUrl()
-    {
-        return "/v1/balance/history";
-    }
 }
diff --git a/vendor/stripe/stripe-php/lib/BankAccount.php b/vendor/stripe/stripe-php/lib/BankAccount.php
index 3fdc9188c648f6d8a046109e28d1bf28fa1a96e2..a77f3054ba5570fd14d4d43a3625bc75f016e322 100644
--- a/vendor/stripe/stripe-php/lib/BankAccount.php
+++ b/vendor/stripe/stripe-php/lib/BankAccount.php
@@ -25,7 +25,6 @@ namespace Stripe;
  */
 class BankAccount extends ApiResource
 {
-
     const OBJECT_NAME = "bank_account";
 
     use ApiOperations\Delete;
@@ -93,12 +92,12 @@ class BankAccount extends ApiResource
         throw new Error\InvalidRequest($msg, null);
     }
 
-   /**
-     * @param array|null $params
-     * @param array|string|null $options
-     *
-     * @return BankAccount The verified bank account.
-     */
+    /**
+      * @param array|null $params
+      * @param array|string|null $options
+      *
+      * @return BankAccount The verified bank account.
+      */
     public function verify($params = null, $options = null)
     {
         $url = $this->instanceUrl() . '/verify';
diff --git a/vendor/stripe/stripe-php/lib/BitcoinReceiver.php b/vendor/stripe/stripe-php/lib/BitcoinReceiver.php
index b4cc5291b5623ffa7c252d39f508a45ce91deb15..a9272dfa28880f6efa59c40be949526e55c97143 100644
--- a/vendor/stripe/stripe-php/lib/BitcoinReceiver.php
+++ b/vendor/stripe/stripe-php/lib/BitcoinReceiver.php
@@ -12,7 +12,6 @@ namespace Stripe;
  */
 class BitcoinReceiver extends ApiResource
 {
-
     const OBJECT_NAME = "bitcoin_receiver";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/BitcoinTransaction.php b/vendor/stripe/stripe-php/lib/BitcoinTransaction.php
index 8269fd216eb043e89ccbeafd14e7714272da303b..122be42314f95da3e1471a71ece0187dea9ae73e 100644
--- a/vendor/stripe/stripe-php/lib/BitcoinTransaction.php
+++ b/vendor/stripe/stripe-php/lib/BitcoinTransaction.php
@@ -9,6 +9,5 @@ namespace Stripe;
  */
 class BitcoinTransaction extends ApiResource
 {
-
     const OBJECT_NAME = "bitcoin_transaction";
 }
diff --git a/vendor/stripe/stripe-php/lib/Capability.php b/vendor/stripe/stripe-php/lib/Capability.php
index 78cb03936956dc8ef71089e2414fcf12033a3d17..aabf54b1f1b72363d50b966c06f191240a3f6116 100644
--- a/vendor/stripe/stripe-php/lib/Capability.php
+++ b/vendor/stripe/stripe-php/lib/Capability.php
@@ -17,7 +17,6 @@ namespace Stripe;
  */
 class Capability extends ApiResource
 {
-
     const OBJECT_NAME = "capability";
 
     use ApiOperations\Update;
diff --git a/vendor/stripe/stripe-php/lib/Card.php b/vendor/stripe/stripe-php/lib/Card.php
index 40de733e7807792a44787ad9244677edef8a45e7..51eb5add1c9fdd6cf17a9b0ffaf73b9ded14d6e4 100644
--- a/vendor/stripe/stripe-php/lib/Card.php
+++ b/vendor/stripe/stripe-php/lib/Card.php
@@ -38,7 +38,6 @@ namespace Stripe;
  */
 class Card extends ApiResource
 {
-
     const OBJECT_NAME = "card";
 
     use ApiOperations\Delete;
diff --git a/vendor/stripe/stripe-php/lib/Charge.php b/vendor/stripe/stripe-php/lib/Charge.php
index 43274c5559c2b5eca8aaefecaa94f4d04b47010e..0c3853c9678751b4d84bb274ab3dd927f139f9da 100644
--- a/vendor/stripe/stripe-php/lib/Charge.php
+++ b/vendor/stripe/stripe-php/lib/Charge.php
@@ -53,7 +53,6 @@ namespace Stripe;
  */
 class Charge extends ApiResource
 {
-
     const OBJECT_NAME = "charge";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/Checkout/Session.php b/vendor/stripe/stripe-php/lib/Checkout/Session.php
index 33fc6a08ab29baad0b0402c2c882d49fe9efa486..227dd7b68166ae4c57cc09d53bd7b9faec81bd92 100644
--- a/vendor/stripe/stripe-php/lib/Checkout/Session.php
+++ b/vendor/stripe/stripe-php/lib/Checkout/Session.php
@@ -23,7 +23,6 @@ namespace Stripe\Checkout;
  */
 class Session extends \Stripe\ApiResource
 {
-
     const OBJECT_NAME = "checkout.session";
 
     use \Stripe\ApiOperations\Create;
diff --git a/vendor/stripe/stripe-php/lib/Collection.php b/vendor/stripe/stripe-php/lib/Collection.php
index 986cd33090a17c4061258c1a26918152c1a36a5b..38d3835ff03ab2a7f072ffa5fb40f80cbf770f84 100644
--- a/vendor/stripe/stripe-php/lib/Collection.php
+++ b/vendor/stripe/stripe-php/lib/Collection.php
@@ -14,7 +14,6 @@ namespace Stripe;
  */
 class Collection extends StripeObject implements \IteratorAggregate
 {
-
     const OBJECT_NAME = "list";
 
     use ApiOperations\Request;
diff --git a/vendor/stripe/stripe-php/lib/CountrySpec.php b/vendor/stripe/stripe-php/lib/CountrySpec.php
index 668bfe62ed9f811638950628ac25f712ecf4920a..ef02c340b5e77cb9fdbb14c17e75bbcea27df2fc 100644
--- a/vendor/stripe/stripe-php/lib/CountrySpec.php
+++ b/vendor/stripe/stripe-php/lib/CountrySpec.php
@@ -18,7 +18,6 @@ namespace Stripe;
  */
 class CountrySpec extends ApiResource
 {
-
     const OBJECT_NAME = "country_spec";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/Coupon.php b/vendor/stripe/stripe-php/lib/Coupon.php
index 51d4fd8659346656cd9a35714b93771bccf51a5e..76d549ad90d8bac2e3e196c327d107d07ce00ab3 100644
--- a/vendor/stripe/stripe-php/lib/Coupon.php
+++ b/vendor/stripe/stripe-php/lib/Coupon.php
@@ -25,7 +25,6 @@ namespace Stripe;
  */
 class Coupon extends ApiResource
 {
-
     const OBJECT_NAME = "coupon";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/CreditNote.php b/vendor/stripe/stripe-php/lib/CreditNote.php
index 66351ffb8287758b2e9fbd1a2c2fcd15b6dc133f..c81d86ee66d1fef2aafb0bb1a418e046ed63beb9 100644
--- a/vendor/stripe/stripe-php/lib/CreditNote.php
+++ b/vendor/stripe/stripe-php/lib/CreditNote.php
@@ -27,7 +27,6 @@ namespace Stripe;
  */
 class CreditNote extends ApiResource
 {
-
     const OBJECT_NAME = "credit_note";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/Customer.php b/vendor/stripe/stripe-php/lib/Customer.php
index 6e78f981ee4124eb963e3ccbb33eabd8a4d256d3..a3f7f6311da0084a484fc01475782483ea47d22c 100644
--- a/vendor/stripe/stripe-php/lib/Customer.php
+++ b/vendor/stripe/stripe-php/lib/Customer.php
@@ -33,7 +33,6 @@ namespace Stripe;
  */
 class Customer extends ApiResource
 {
-
     const OBJECT_NAME = "customer";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/Discount.php b/vendor/stripe/stripe-php/lib/Discount.php
index a72d12bc6e3c128d539b391d743d4082d20f4e54..68bfc0fd74a6ffc7277708eed12b93090cf93845 100644
--- a/vendor/stripe/stripe-php/lib/Discount.php
+++ b/vendor/stripe/stripe-php/lib/Discount.php
@@ -16,6 +16,5 @@ namespace Stripe;
  */
 class Discount extends StripeObject
 {
-
     const OBJECT_NAME = "discount";
 }
diff --git a/vendor/stripe/stripe-php/lib/Dispute.php b/vendor/stripe/stripe-php/lib/Dispute.php
index 2d58daa380a436f20ff47afc33c7ed7f6659bbbe..8ffc2d4cc73b4e5f37ab669a26741f6d423f659a 100644
--- a/vendor/stripe/stripe-php/lib/Dispute.php
+++ b/vendor/stripe/stripe-php/lib/Dispute.php
@@ -24,7 +24,6 @@ namespace Stripe;
  */
 class Dispute extends ApiResource
 {
-
     const OBJECT_NAME = "dispute";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/EphemeralKey.php b/vendor/stripe/stripe-php/lib/EphemeralKey.php
index 5ed4646b4f73a8c32240358a54735a5ecaedb9b3..ea4cc756ccd05b8d101f069ea62dc98c8351d17c 100644
--- a/vendor/stripe/stripe-php/lib/EphemeralKey.php
+++ b/vendor/stripe/stripe-php/lib/EphemeralKey.php
@@ -17,7 +17,6 @@ namespace Stripe;
  */
 class EphemeralKey extends ApiResource
 {
-
     const OBJECT_NAME = "ephemeral_key";
 
     use ApiOperations\Create {
diff --git a/vendor/stripe/stripe-php/lib/Event.php b/vendor/stripe/stripe-php/lib/Event.php
index a986c35dfec2401fe7932b706d97bcb2bd69c1f1..f9a004aa6a9750957d0bb1cf1b6b33987d419da1 100644
--- a/vendor/stripe/stripe-php/lib/Event.php
+++ b/vendor/stripe/stripe-php/lib/Event.php
@@ -20,7 +20,6 @@ namespace Stripe;
  */
 class Event extends ApiResource
 {
-
     const OBJECT_NAME = "event";
 
     /**
diff --git a/vendor/stripe/stripe-php/lib/ExchangeRate.php b/vendor/stripe/stripe-php/lib/ExchangeRate.php
index 803a5f87700fb8382af9bfd02df62817ff10c823..6a7e7a36a3807f9c52eba2f01407d40f3545159c 100644
--- a/vendor/stripe/stripe-php/lib/ExchangeRate.php
+++ b/vendor/stripe/stripe-php/lib/ExchangeRate.php
@@ -9,7 +9,6 @@ namespace Stripe;
  */
 class ExchangeRate extends ApiResource
 {
-
     const OBJECT_NAME = "exchange_rate";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/FileLink.php b/vendor/stripe/stripe-php/lib/FileLink.php
index 2a012b361052fc03b75a4b76a3be107b6029faca..385971f7d82c7753fbcd52bb098508e369eebf1f 100644
--- a/vendor/stripe/stripe-php/lib/FileLink.php
+++ b/vendor/stripe/stripe-php/lib/FileLink.php
@@ -19,7 +19,6 @@ namespace Stripe;
  */
 class FileLink extends ApiResource
 {
-
     const OBJECT_NAME = "file_link";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/Invoice.php b/vendor/stripe/stripe-php/lib/Invoice.php
index 31a84585485033c6a418f71f853fca70c07c266b..a309dd9e477e54b4fccfcfb182a80165404c4af2 100644
--- a/vendor/stripe/stripe-php/lib/Invoice.php
+++ b/vendor/stripe/stripe-php/lib/Invoice.php
@@ -70,7 +70,6 @@ namespace Stripe;
  */
 class Invoice extends ApiResource
 {
-
     const OBJECT_NAME = "invoice";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/InvoiceItem.php b/vendor/stripe/stripe-php/lib/InvoiceItem.php
index 02ca0f2d7ee3689afbfff257f8385a091432448d..2f74dabec5ce4ffd12960596d5de76c984d8701b 100644
--- a/vendor/stripe/stripe-php/lib/InvoiceItem.php
+++ b/vendor/stripe/stripe-php/lib/InvoiceItem.php
@@ -29,7 +29,6 @@ namespace Stripe;
  */
 class InvoiceItem extends ApiResource
 {
-
     const OBJECT_NAME = "invoiceitem";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/IssuerFraudRecord.php b/vendor/stripe/stripe-php/lib/IssuerFraudRecord.php
index 8db4b31b0b4a067381ddc979bdf27c3c0d6155b3..805dc502885f1abbc12ea71a3a041e8ebcae64c8 100644
--- a/vendor/stripe/stripe-php/lib/IssuerFraudRecord.php
+++ b/vendor/stripe/stripe-php/lib/IssuerFraudRecord.php
@@ -17,7 +17,6 @@ namespace Stripe;
  */
 class IssuerFraudRecord extends ApiResource
 {
-
     const OBJECT_NAME = "issuer_fraud_record";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/LoginLink.php b/vendor/stripe/stripe-php/lib/LoginLink.php
index 3a443bf16817dacbd204260a513a4e6708de694e..9f677364b173c91543833d859ed7e10911cdabed 100644
--- a/vendor/stripe/stripe-php/lib/LoginLink.php
+++ b/vendor/stripe/stripe-php/lib/LoginLink.php
@@ -13,6 +13,5 @@ namespace Stripe;
  */
 class LoginLink extends ApiResource
 {
-
     const OBJECT_NAME = "login_link";
 }
diff --git a/vendor/stripe/stripe-php/lib/Order.php b/vendor/stripe/stripe-php/lib/Order.php
index 0688c6ef937d8c7492de09ca6d5a493605780f6c..1f2ce2563acb995f40ae015ff9ce1e71d46756fd 100644
--- a/vendor/stripe/stripe-php/lib/Order.php
+++ b/vendor/stripe/stripe-php/lib/Order.php
@@ -33,7 +33,6 @@ namespace Stripe;
  */
 class Order extends ApiResource
 {
-
     const OBJECT_NAME = "order";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/OrderItem.php b/vendor/stripe/stripe-php/lib/OrderItem.php
index 26d49b4e75114fe1cbe64d83f8b7ab8e6e4a3007..6ef1242fc34aceb3eeecf416a39d513586b09d2b 100644
--- a/vendor/stripe/stripe-php/lib/OrderItem.php
+++ b/vendor/stripe/stripe-php/lib/OrderItem.php
@@ -17,6 +17,5 @@ namespace Stripe;
  */
 class OrderItem extends StripeObject
 {
-
     const OBJECT_NAME = "order_item";
 }
diff --git a/vendor/stripe/stripe-php/lib/OrderReturn.php b/vendor/stripe/stripe-php/lib/OrderReturn.php
index f3705e86166db6f7779158b957736a55555afe54..3b1ca8efa1281a2ec96d58b884d7814e73ad5a41 100644
--- a/vendor/stripe/stripe-php/lib/OrderReturn.php
+++ b/vendor/stripe/stripe-php/lib/OrderReturn.php
@@ -19,7 +19,6 @@ namespace Stripe;
  */
 class OrderReturn extends ApiResource
 {
-
     const OBJECT_NAME = "order_return";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/PaymentIntent.php b/vendor/stripe/stripe-php/lib/PaymentIntent.php
index 47cbf8fc6cb70ba398eb8a2012ea3b01f4ea3cfe..b70b1577899ec6ba7b88f4054ff138f2cb969547 100644
--- a/vendor/stripe/stripe-php/lib/PaymentIntent.php
+++ b/vendor/stripe/stripe-php/lib/PaymentIntent.php
@@ -42,7 +42,6 @@ namespace Stripe;
  */
 class PaymentIntent extends ApiResource
 {
-
     const OBJECT_NAME = "payment_intent";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/PaymentMethod.php b/vendor/stripe/stripe-php/lib/PaymentMethod.php
index 1a0bc371d5d9fe3febc1dae77e971b9b80f2de82..c0557e8c39470a86fad8bd307310dca6f8dc0757 100644
--- a/vendor/stripe/stripe-php/lib/PaymentMethod.php
+++ b/vendor/stripe/stripe-php/lib/PaymentMethod.php
@@ -22,7 +22,6 @@ namespace Stripe;
  */
 class PaymentMethod extends ApiResource
 {
-
     const OBJECT_NAME = "payment_method";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/Payout.php b/vendor/stripe/stripe-php/lib/Payout.php
index 365d7e47ee99d417b4edd6ef3c6c783a3bd7df53..131bd4ae06d4b1e8ca55f0a56cdb4bb4edbdc621 100644
--- a/vendor/stripe/stripe-php/lib/Payout.php
+++ b/vendor/stripe/stripe-php/lib/Payout.php
@@ -30,7 +30,6 @@ namespace Stripe;
  */
 class Payout extends ApiResource
 {
-
     const OBJECT_NAME = "payout";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/Person.php b/vendor/stripe/stripe-php/lib/Person.php
index f79b80da060f26288af180f4b928364c617ba547..5540f7c5f2840081fdfe79eff80d41015b90b7fc 100644
--- a/vendor/stripe/stripe-php/lib/Person.php
+++ b/vendor/stripe/stripe-php/lib/Person.php
@@ -35,7 +35,6 @@ namespace Stripe;
  */
 class Person extends ApiResource
 {
-
     const OBJECT_NAME = "person";
 
     use ApiOperations\Delete;
diff --git a/vendor/stripe/stripe-php/lib/Plan.php b/vendor/stripe/stripe-php/lib/Plan.php
index 54a2b58eb35fafcbb9180fb7ec41a3acde65ecb6..1a7ae87dda8b7373ec089c8caab41b83f12fa1c3 100644
--- a/vendor/stripe/stripe-php/lib/Plan.php
+++ b/vendor/stripe/stripe-php/lib/Plan.php
@@ -29,7 +29,6 @@ namespace Stripe;
  */
 class Plan extends ApiResource
 {
-
     const OBJECT_NAME = "plan";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/Product.php b/vendor/stripe/stripe-php/lib/Product.php
index cb27ef42d8ccfc580bf0a7254175fc33d2badd07..6c60d20c4a7fe0c4f00bf8fd9811f660e708d38f 100644
--- a/vendor/stripe/stripe-php/lib/Product.php
+++ b/vendor/stripe/stripe-php/lib/Product.php
@@ -29,7 +29,6 @@ namespace Stripe;
  */
 class Product extends ApiResource
 {
-
     const OBJECT_NAME = "product";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/Recipient.php b/vendor/stripe/stripe-php/lib/Recipient.php
index 2d5eda5cf7f77ff788ad8decfe4a1d35531fc9a0..35a695f5084bd859089af3acf0a43136ecf8d72b 100644
--- a/vendor/stripe/stripe-php/lib/Recipient.php
+++ b/vendor/stripe/stripe-php/lib/Recipient.php
@@ -24,7 +24,6 @@ namespace Stripe;
  */
 class Recipient extends ApiResource
 {
-
     const OBJECT_NAME = "recipient";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/RecipientTransfer.php b/vendor/stripe/stripe-php/lib/RecipientTransfer.php
index 17de5a52e51541e9a81ec14992d7306957aa6eb5..7a2ec1f3fb70d4d7046476136e61da4035c5d760 100644
--- a/vendor/stripe/stripe-php/lib/RecipientTransfer.php
+++ b/vendor/stripe/stripe-php/lib/RecipientTransfer.php
@@ -34,6 +34,5 @@ namespace Stripe;
  */
 class RecipientTransfer extends ApiResource
 {
-
     const OBJECT_NAME = "recipient_transfer";
 }
diff --git a/vendor/stripe/stripe-php/lib/Refund.php b/vendor/stripe/stripe-php/lib/Refund.php
index 6f3fdfaf44550e9e66da6953acbac39575cb15b2..f8b4529b72e732d8b11c118bbbb8789da42d1f1c 100644
--- a/vendor/stripe/stripe-php/lib/Refund.php
+++ b/vendor/stripe/stripe-php/lib/Refund.php
@@ -26,7 +26,6 @@ namespace Stripe;
  */
 class Refund extends ApiResource
 {
-
     const OBJECT_NAME = "refund";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/SKU.php b/vendor/stripe/stripe-php/lib/SKU.php
index 5b50df8a8479122a224ffab09f50e73ce111d3fb..cb3aa6f73f1e5b3334b17714d3de969c7764762b 100644
--- a/vendor/stripe/stripe-php/lib/SKU.php
+++ b/vendor/stripe/stripe-php/lib/SKU.php
@@ -24,7 +24,6 @@ namespace Stripe;
  */
 class SKU extends ApiResource
 {
-
     const OBJECT_NAME = "sku";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/SetupIntent.php b/vendor/stripe/stripe-php/lib/SetupIntent.php
index f476fb35fa9a4e6f5a6da90d8562f418a7ee1cc0..176a411b67fc06e5d152841238c5807fe223a1af 100644
--- a/vendor/stripe/stripe-php/lib/SetupIntent.php
+++ b/vendor/stripe/stripe-php/lib/SetupIntent.php
@@ -25,7 +25,6 @@ namespace Stripe;
  */
 class SetupIntent extends ApiResource
 {
-
     const OBJECT_NAME = "setup_intent";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/Source.php b/vendor/stripe/stripe-php/lib/Source.php
index 1e2c8c7356591d2ce6145c604ad0850f670d0d2d..ec5cc42127e78df9a423ee84f2cd3c82655fd58f 100644
--- a/vendor/stripe/stripe-php/lib/Source.php
+++ b/vendor/stripe/stripe-php/lib/Source.php
@@ -43,7 +43,6 @@ namespace Stripe;
  */
 class Source extends ApiResource
 {
-
     const OBJECT_NAME = "source";
 
     use ApiOperations\Create;
diff --git a/vendor/stripe/stripe-php/lib/SourceTransaction.php b/vendor/stripe/stripe-php/lib/SourceTransaction.php
index 018a896240e8c3f26c9291d82545ea1d039ace4c..d004032c569a375805e2cadf6f08a5d11ec49aee 100644
--- a/vendor/stripe/stripe-php/lib/SourceTransaction.php
+++ b/vendor/stripe/stripe-php/lib/SourceTransaction.php
@@ -18,6 +18,5 @@ namespace Stripe;
  */
 class SourceTransaction extends ApiResource
 {
-
     const OBJECT_NAME = "source_transaction";
 }
diff --git a/vendor/stripe/stripe-php/lib/Stripe.php b/vendor/stripe/stripe-php/lib/Stripe.php
index 5b67435c007bbafc77a24faf5d64908eb177e966..0b533e0eb5cbdc7d104d310d41136b4c1da7aa28 100644
--- a/vendor/stripe/stripe-php/lib/Stripe.php
+++ b/vendor/stripe/stripe-php/lib/Stripe.php
@@ -46,7 +46,7 @@ class Stripe
     // @var int Maximum number of request retries
     public static $maxNetworkRetries = 0;
 
-    // @var boolean Whether client telemetry is enabled. Defaults to false.
+    // @var boolean Whether client telemetry is enabled. Defaults to true.
     public static $enableTelemetry = true;
 
     // @var float Maximum delay between retries, in seconds
@@ -55,7 +55,7 @@ class Stripe
     // @var float Initial delay between retries, in seconds
     private static $initialNetworkRetryDelay = 0.5;
 
-    const VERSION = '6.40.0';
+    const VERSION = '6.43.1';
 
     /**
      * @return string The API key used for requests.
diff --git a/vendor/stripe/stripe-php/lib/StripeObject.php b/vendor/stripe/stripe-php/lib/StripeObject.php
index 432e95b7f0b703b22ba3b294b1f2b7ea7cefd7db..62e02c0e0cc03f2cd1b0df46c5cb0a7b9d827879 100644
--- a/vendor/stripe/stripe-php/lib/StripeObject.php
+++ b/vendor/stripe/stripe-php/lib/StripeObject.php
@@ -153,7 +153,7 @@ class StripeObject implements \ArrayAccess, \Countable, \JsonSerializable
         $nullval = null;
         if (!empty($this->_values) && array_key_exists($k, $this->_values)) {
             return $this->_values[$k];
-        } else if (!empty($this->_transientValues) && $this->_transientValues->includes($k)) {
+        } elseif (!empty($this->_transientValues) && $this->_transientValues->includes($k)) {
             $class = get_class($this);
             $attrs = join(', ', array_keys($this->_values));
             $message = "Stripe Notice: Undefined property of $class instance: $k. "
diff --git a/vendor/stripe/stripe-php/lib/Subscription.php b/vendor/stripe/stripe-php/lib/Subscription.php
index f5a46171d81e9da96b1fd9bfcb89d5b9a1fed66d..a1087a56a4ca6d7ce8b0f41ba58007ec9036cbe4 100644
--- a/vendor/stripe/stripe-php/lib/Subscription.php
+++ b/vendor/stripe/stripe-php/lib/Subscription.php
@@ -28,6 +28,7 @@ namespace Stripe;
  * @property string $latest_invoice
  * @property boolean $livemode
  * @property StripeObject $metadata
+ * @property string $pending_setup_intent
  * @property Plan $plan
  * @property int $quantity
  * @property SubscriptionSchedule $schedule
@@ -42,7 +43,6 @@ namespace Stripe;
  */
 class Subscription extends ApiResource
 {
-
     const OBJECT_NAME = "subscription";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/SubscriptionItem.php b/vendor/stripe/stripe-php/lib/SubscriptionItem.php
index 5baa540fe9835d9fd6e214f9b098fa61e24969af..d0d2dea8fcf82aa7d07c8e1bb6a2f0a6eb8e7f79 100644
--- a/vendor/stripe/stripe-php/lib/SubscriptionItem.php
+++ b/vendor/stripe/stripe-php/lib/SubscriptionItem.php
@@ -19,20 +19,34 @@ namespace Stripe;
  */
 class SubscriptionItem extends ApiResource
 {
-
     const OBJECT_NAME = "subscription_item";
 
+    const PATH_USAGE_RECORDS = '/usage_records';
+
     use ApiOperations\All;
     use ApiOperations\Create;
     use ApiOperations\Delete;
+    use ApiOperations\NestedResource;
     use ApiOperations\Retrieve;
     use ApiOperations\Update;
 
+    /**
+     * @param string|null $id The ID of the subscription item on which to create the usage record.
+     * @param array|null $params
+     * @param array|string|null $opts
+     *
+     * @return ApiResource
+     */
+    public static function createUsageRecord($id, $params = null, $opts = null)
+    {
+        return self::_createNestedResource($id, static::PATH_USAGE_RECORDS, $params, $opts);
+    }
+
     /**
      * @param array|null $params
      * @param array|string|null $options
      *
-     * @return Collection The list of source transactions.
+     * @return Collection The list of usage record summaries.
      */
     public function usageRecordSummaries($params = null, $options = null)
     {
diff --git a/vendor/stripe/stripe-php/lib/SubscriptionSchedule.php b/vendor/stripe/stripe-php/lib/SubscriptionSchedule.php
index ac3686f1db81be189b4bb01a05692a9c5883f2a1..e41f279a036448f446c62541dce20e492fbc49ee 100644
--- a/vendor/stripe/stripe-php/lib/SubscriptionSchedule.php
+++ b/vendor/stripe/stripe-php/lib/SubscriptionSchedule.php
@@ -30,7 +30,6 @@ namespace Stripe;
  */
 class SubscriptionSchedule extends ApiResource
 {
-
     const OBJECT_NAME = "subscription_schedule";
 
     use ApiOperations\All;
@@ -39,8 +38,6 @@ class SubscriptionSchedule extends ApiResource
     use ApiOperations\Update;
     use ApiOperations\NestedResource;
 
-    const PATH_REVISIONS = '/revisions';
-
     /**
      * @param array|null $params
      * @param array|string|null $opts
@@ -68,44 +65,4 @@ class SubscriptionSchedule extends ApiResource
         $this->refreshFrom($response, $opts);
         return $this;
     }
-
-    /**
-     * @param array|null $params
-     * @param array|string|null $options
-     *
-     * @return Collection The list of subscription schedule revisions.
-     */
-    public function revisions($params = null, $options = null)
-    {
-        $url = $this->instanceUrl() . '/revisions';
-        list($response, $opts) = $this->_request('get', $url, $params, $options);
-        $obj = Util\Util::convertToStripeObject($response, $opts);
-        $obj->setLastResponse($response);
-        return $obj;
-    }
-
-    /**
-     * @param array|null $id The ID of the subscription schedule to which the person belongs.
-     * @param array|null $personId The ID of the person to retrieve.
-     * @param array|null $params
-     * @param array|string|null $opts
-     *
-     * @return Revision
-     */
-    public static function retrieveRevision($id, $personId, $params = null, $opts = null)
-    {
-        return self::_retrieveNestedResource($id, static::PATH_REVISIONS, $personId, $params, $opts);
-    }
-
-    /**
-     * @param array|null $id The ID of the subscription schedule on which to retrieve the persons.
-     * @param array|null $params
-     * @param array|string|null $opts
-     *
-     * @return Collection The list of revisions.
-     */
-    public static function allRevisions($id, $params = null, $opts = null)
-    {
-        return self::_allNestedResources($id, static::PATH_REVISIONS, $params, $opts);
-    }
 }
diff --git a/vendor/stripe/stripe-php/lib/SubscriptionScheduleRevision.php b/vendor/stripe/stripe-php/lib/SubscriptionScheduleRevision.php
deleted file mode 100644
index 12177236530b3f37f786241087707cb5347c725f..0000000000000000000000000000000000000000
--- a/vendor/stripe/stripe-php/lib/SubscriptionScheduleRevision.php
+++ /dev/null
@@ -1,77 +0,0 @@
-<?php
-
-namespace Stripe;
-
-/**
- * Class SubscriptionScheduleRevision
- *
- * @property string $id
- * @property string $object
- * @property int $created
- * @property mixed $invoice_settings
- * @property boolean $livemode
- * @property mixed $phases
- * @property string $previous_revision
- * @property string $renewal_behavior
- * @property mixed $renewal_interval
- * @property string $schedule
- *
- * @package Stripe
- */
-class SubscriptionScheduleRevision extends ApiResource
-{
-
-    const OBJECT_NAME = "subscription_schedule_revision";
-
-    use ApiOperations\All;
-    use ApiOperations\Retrieve;
-
-    /**
-     * @return string The API URL for this Subscription Schedule Revision.
-     */
-    public function instanceUrl()
-    {
-        $id = $this['id'];
-        $schedule = $this['schedule'];
-        if (!$id) {
-            throw new Error\InvalidRequest(
-                "Could not determine which URL to request: " .
-                "class instance has invalid ID: $id",
-                null
-            );
-        }
-        $id = Util\Util::utf8($id);
-        $schedule = Util\Util::utf8($schedule);
-
-        $base = SubscriptionSchedule::classUrl();
-        $scheduleExtn = urlencode($schedule);
-        $extn = urlencode($id);
-        return "$base/$scheduleExtn/revisions/$extn";
-    }
-
-    /**
-     * @param array|string $_id
-     * @param array|string|null $_opts
-     *
-     * @throws \Stripe\Error\InvalidRequest
-     */
-    public static function retrieve($_id, $_opts = null)
-    {
-        $msg = "Subscription Schedule Revisions cannot be accessed without a Subscription Schedule ID. " .
-               "Retrieve one using \$schedule->retrieveRevision('revision_id') instead.";
-        throw new Error\InvalidRequest($msg, null);
-    }
-
-    /**
-     * @param array|string $_id
-     * @param array|string|null $_opts
-     *
-     * @throws \Stripe\Error\InvalidRequest
-     */
-    public static function all($params = null, $opts = null)
-    {
-        $msg = "Subscription Schedule Revisions cannot be listed without a Subscription Schedule ID. " .
-               "List those using \$schedule->allRevisions('revision_id') instead.";
-        throw new Error\InvalidRequest($msg, null);
-    }
-}
diff --git a/vendor/stripe/stripe-php/lib/TaxId.php b/vendor/stripe/stripe-php/lib/TaxId.php
index 0f72a2ac95f337d2130417f75427e8bcae6f1bb2..8d607132e6e977f6766d6baed8d9f087fe0635f0 100644
--- a/vendor/stripe/stripe-php/lib/TaxId.php
+++ b/vendor/stripe/stripe-php/lib/TaxId.php
@@ -19,7 +19,6 @@ namespace Stripe;
  */
 class TaxId extends ApiResource
 {
-
     const OBJECT_NAME = "tax_id";
 
     use ApiOperations\Delete;
diff --git a/vendor/stripe/stripe-php/lib/TaxRate.php b/vendor/stripe/stripe-php/lib/TaxRate.php
index 25884dd840f0fc857db469df9f938a51789aab6e..9f416507cf4547d5f32a3d1ade8317d086306b46 100644
--- a/vendor/stripe/stripe-php/lib/TaxRate.php
+++ b/vendor/stripe/stripe-php/lib/TaxRate.php
@@ -21,7 +21,6 @@ namespace Stripe;
  */
 class TaxRate extends ApiResource
 {
-
     const OBJECT_NAME = "tax_rate";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/ThreeDSecure.php b/vendor/stripe/stripe-php/lib/ThreeDSecure.php
index 5e67e351ba73d364015e30eae181587b58007654..3869eba85b3687dc2c8770c1aaba6d6f4dad0da9 100644
--- a/vendor/stripe/stripe-php/lib/ThreeDSecure.php
+++ b/vendor/stripe/stripe-php/lib/ThreeDSecure.php
@@ -4,7 +4,6 @@ namespace Stripe;
 
 class ThreeDSecure extends ApiResource
 {
-
     const OBJECT_NAME = "three_d_secure";
 
     use ApiOperations\Create;
diff --git a/vendor/stripe/stripe-php/lib/Token.php b/vendor/stripe/stripe-php/lib/Token.php
index 989219fc7895d0fa68448429f0fd89b5970927a8..629d51f6867eccb62d839c2d68d20a83ed71d7ec 100644
--- a/vendor/stripe/stripe-php/lib/Token.php
+++ b/vendor/stripe/stripe-php/lib/Token.php
@@ -19,7 +19,6 @@ namespace Stripe;
  */
 class Token extends ApiResource
 {
-
     const OBJECT_NAME = "token";
 
     use ApiOperations\Create;
diff --git a/vendor/stripe/stripe-php/lib/Topup.php b/vendor/stripe/stripe-php/lib/Topup.php
index 9ad96670a5565861bd534ae9c1570e1bc1505403..37625a329ff6971d425090a09d4e0d5a7a1c9fbd 100644
--- a/vendor/stripe/stripe-php/lib/Topup.php
+++ b/vendor/stripe/stripe-php/lib/Topup.php
@@ -26,7 +26,6 @@ namespace Stripe;
  */
 class Topup extends ApiResource
 {
-
     const OBJECT_NAME = "topup";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/Transfer.php b/vendor/stripe/stripe-php/lib/Transfer.php
index dff2faa33296bdf2a4b4dbf1b2df8832665ffe64..bf9fa25ce37839fdb38f5ee3f1b38144da991e60 100644
--- a/vendor/stripe/stripe-php/lib/Transfer.php
+++ b/vendor/stripe/stripe-php/lib/Transfer.php
@@ -27,7 +27,6 @@ namespace Stripe;
  */
 class Transfer extends ApiResource
 {
-
     const OBJECT_NAME = "transfer";
 
     use ApiOperations\All;
diff --git a/vendor/stripe/stripe-php/lib/TransferReversal.php b/vendor/stripe/stripe-php/lib/TransferReversal.php
index c945e5a93603b976491e0a3cd93e64c2182edec9..a4f94f2fb43b66270b5982fdcda9bacea2f83df1 100644
--- a/vendor/stripe/stripe-php/lib/TransferReversal.php
+++ b/vendor/stripe/stripe-php/lib/TransferReversal.php
@@ -20,7 +20,6 @@ namespace Stripe;
  */
 class TransferReversal extends ApiResource
 {
-
     const OBJECT_NAME = "transfer_reversal";
 
     use ApiOperations\Update {
diff --git a/vendor/stripe/stripe-php/lib/UsageRecord.php b/vendor/stripe/stripe-php/lib/UsageRecord.php
index a9e3a25e3e91aaf05b0fe119e4a4048893c0e85a..1239222d49d8f8e0f79cf29928f700b7cba6bc06 100644
--- a/vendor/stripe/stripe-php/lib/UsageRecord.php
+++ b/vendor/stripe/stripe-php/lib/UsageRecord.php
@@ -16,7 +16,6 @@ namespace Stripe;
  */
 class UsageRecord extends ApiResource
 {
-
     const OBJECT_NAME = "usage_record";
 
     /**
diff --git a/vendor/stripe/stripe-php/lib/UsageRecordSummary.php b/vendor/stripe/stripe-php/lib/UsageRecordSummary.php
index b8f4aebe974fa8ef922a62bd3d1c6b4cf39df856..9d3e7a396edf9a2477814bfebe0dbcd38c66a0dd 100644
--- a/vendor/stripe/stripe-php/lib/UsageRecordSummary.php
+++ b/vendor/stripe/stripe-php/lib/UsageRecordSummary.php
@@ -17,6 +17,5 @@ namespace Stripe;
  */
 class UsageRecordSummary extends ApiResource
 {
-
     const OBJECT_NAME = "usage_record_summary";
 }
diff --git a/vendor/stripe/stripe-php/lib/Util/LoggerInterface.php b/vendor/stripe/stripe-php/lib/Util/LoggerInterface.php
index bbdfc929982b474dd7f3786045a46f9c52fd29ec..cf04e1d2baddfabf99f35984b2aa33a94a570a28 100644
--- a/vendor/stripe/stripe-php/lib/Util/LoggerInterface.php
+++ b/vendor/stripe/stripe-php/lib/Util/LoggerInterface.php
@@ -30,7 +30,7 @@ interface LoggerInterface
      *
      * @param string $message
      * @param array $context
-     * @return null
+     * @return void
      */
     public function error($message, array $context = []);
 }
diff --git a/vendor/stripe/stripe-php/lib/Util/Util.php b/vendor/stripe/stripe-php/lib/Util/Util.php
index f9f15440023b688fb878fae8ad69f6fae6e59a2a..dd8677aa0995fcfe92e2a2cbea770c25b172db80 100644
--- a/vendor/stripe/stripe-php/lib/Util/Util.php
+++ b/vendor/stripe/stripe-php/lib/Util/Util.php
@@ -135,7 +135,6 @@ abstract class Util
             \Stripe\Subscription::OBJECT_NAME => 'Stripe\\Subscription',
             \Stripe\SubscriptionItem::OBJECT_NAME => 'Stripe\\SubscriptionItem',
             \Stripe\SubscriptionSchedule::OBJECT_NAME => 'Stripe\\SubscriptionSchedule',
-            \Stripe\SubscriptionScheduleRevision::OBJECT_NAME => 'Stripe\\SubscriptionScheduleRevision',
             \Stripe\TaxId::OBJECT_NAME => 'Stripe\\TaxId',
             \Stripe\TaxRate::OBJECT_NAME => 'Stripe\\TaxRate',
             \Stripe\ThreeDSecure::OBJECT_NAME => 'Stripe\\ThreeDSecure',
diff --git a/vendor/stripe/stripe-php/lib/WebhookEndpoint.php b/vendor/stripe/stripe-php/lib/WebhookEndpoint.php
index 27ecacf690c358961bb9ce99026f01b5e376da57..636c7e1927db59325338a9d457bcfc4aec182743 100644
--- a/vendor/stripe/stripe-php/lib/WebhookEndpoint.php
+++ b/vendor/stripe/stripe-php/lib/WebhookEndpoint.php
@@ -18,7 +18,6 @@ namespace Stripe;
  */
 class WebhookEndpoint extends ApiResource
 {
-
     const OBJECT_NAME = "webhook_endpoint";
 
     use ApiOperations\All;
diff --git a/xml/Menu/stripe.xml b/xml/Menu/stripe.xml
index 23cac580e0dab8ddf17d5a6cd0a4a33a2a56f96c..21a2c2ccc2a22e49dcda3916c9e6d930517ee3c8 100644
--- a/xml/Menu/stripe.xml
+++ b/xml/Menu/stripe.xml
@@ -6,4 +6,16 @@
     <title>Check and Fix Stripe Webhooks</title>
     <access_arguments>access CiviCRM</access_arguments>
   </item>
+  <item>
+    <path>civicrm/stripe/confirm-payment</path>
+    <page_callback>CRM_Stripe_AJAX::confirmPayment</page_callback>
+    <title>Client Secret</title>
+    <access_callback>1</access_callback>
+  </item>
+  <item>
+    <path>civicrm/admin/setting/stripe</path>
+    <title>Stripe Settings</title>
+    <page_callback>CRM_Admin_Form_Generic</page_callback>
+    <access_arguments>administer CiviCRM</access_arguments>
+  </item>
 </menu>