diff --git a/CRM/Core/Payment/Stripe.php b/CRM/Core/Payment/Stripe.php
index 44802948f56fa4b5dfdcdcbfbda9f27155191f67..00eb54495b963cc59a4cff3dcfb64df705bb838f 100644
--- a/CRM/Core/Payment/Stripe.php
+++ b/CRM/Core/Payment/Stripe.php
@@ -879,10 +879,12 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
    * @param array $errors
    */
   public function validatePaymentInstrument($values, &$errors) {
-    CRM_Core_Form::validateMandatoryFields($this->getMandatoryFields(), $values, $errors);
+    // Use $_POST here and not $values - for webform fields are not set in $values, but are in $_POST
+    CRM_Core_Form::validateMandatoryFields($this->getMandatoryFields(), $_POST, $errors);
     if ($this->_paymentProcessor['payment_type'] == 1) {
       // Don't validate credit card details as they are not passed (and stripe does this for us)
       //CRM_Core_Payment_Form::validateCreditCard($values, $errors, $this->_paymentProcessor['id']);
     }
   }
+
 }
diff --git a/README.md b/README.md
index 3ddf345e9ae1cac5c686e506089151d215903d13..863fdd49e04dde4f467b716d4016b905f96fec82 100644
--- a/README.md
+++ b/README.md
@@ -70,7 +70,9 @@ rgburton, Swingline0, BorislavZlatanov, agh1, & jmcclelland
 TESTING
 --------
 1. Test webform submission with payment and user-select, single processor.
-2. Test online contribution page with single processor, multi-processor (stripe default, stripe non-default).
-3. Test offline contribution page with single processor, multi-processor (stripe default, stripe non-default).
-4. Test event registration.
-5. Test event registration (cart checkout).
+1. Test online contribution page with single processor, multi-processor (stripe default, stripe non-default).
+1. Test offline contribution page with single processor, multi-processor (stripe default, stripe non-default).
+1. Test event registration.
+1. Test event registration (cart checkout).
+1. Test offline event registration.
+1. Test offline membership.
diff --git a/js/civicrm_stripe.js b/js/civicrm_stripe.js
index 06694b2b8906048f5c2f95bc543a2b7dcde92923..6f289e4cc8eb98bbe9fd8a8172d4e179de83081d 100644
--- a/js/civicrm_stripe.js
+++ b/js/civicrm_stripe.js
@@ -4,8 +4,6 @@
  */
 (function($, CRM) {
 
-  var buttonText;
-
   // Response from Stripe.createToken.
   function stripeResponseHandler(status, response) {
     $form = getBillingForm();
@@ -18,7 +16,7 @@
         $(".messages.crm-error.stripe-message").slideUp();
         $(".messages.crm-error.stripe-message:first").remove();
       }
-      $form.prepend('<div class="messages crm-error stripe-message">'
+      $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>'
@@ -26,7 +24,7 @@
         + '</div>');
 
       $form.data('submitted', false);
-      $submit.prop('disabled', false).attr('value', buttonText);
+      $submit.prop('disabled', false);
     }
     else {
       var token = response['id'];
@@ -75,27 +73,52 @@
       return;
     }
     $submit = 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');
+    // Find submit buttons which should not submit payment
+    $form.find('[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);
+    });
+
+    $submit.click( function() {
+      debugging('clearing submit-dont-process');
+      $form.data('submit-dont-process', 0);
+    });
+
+    // 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);
+      }
+    });
+
     var isWebform = getIsWebform();
 
     // For CiviCRM Webforms.
     if (isWebform) {
+      // 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" />');
+        $form.append($('<input type="hidden" name="op" id="action" />'));
       }
-      $(document).keypress(function(event) {
+      var $actions = $form.find('[type=submit]');
+      $('[type=submit]').click(function() {
+        $('#action').val(this.value);
+      });
+      // If enter pressed, use our submit function
+      $form.keypress(function(event) {
         if (event.which === 13) {
-          // Enter was pressed
-          event.preventDefault();
+          $('#action').val(this.value);
           submit(event);
         }
       });
-      $(":submit").click(function() {
-        $('#action').val(this.value);
-      });
       $('#billingcheckbox:input').hide();
       $('label[for="billingcheckbox"]').hide();
-
-      var webformPrevious = $('input.webform-previous').first().val();
     }
     else {
       // As we use credit_card_number to pass token, make sure it is empty when shown
@@ -103,32 +126,21 @@
       $form.find("input#cvv2").val('');
     }
 
-    $submit.removeAttr('onclick');
-    $form.unbind('submit');
-
     // Intercept form submission.
     $form.on('submit', function(event) {
-      submit(event);
+        submit(event);
     });
 
     function submit(event) {
       event.preventDefault();
+      debugging('submit handler');
 
-      $form = getBillingForm();
       if ($form.data('submitted') === true) {
-        // Previously submitted - don't submit again
-        alert('Form already submitted. Please wait.');
-        return true;
-      } else {
-        // Mark it so that the next submit can be ignored
-        // ADDED requirement that form be valid
-        if($form.valid()) {
-          $form.data('submitted', true);
-        }
+        debugging('form already submitted');
+        return false;
       }
 
-      $submit = getBillingSubmit();
-      var isWebform = getIsWebform();
+      $form = getBillingForm();
 
       // Don't handle submits generated by non-stripe processors
       if (!$('input#stripe-pub-key').length) {
@@ -136,20 +148,21 @@
         return true;
       }
       // Don't handle submits generated by the CiviDiscount button.
-      if ($form.data('submit-dont-process') === 1) {
-        debugging('Discount is in play');
+      if ($form.data('submit-dont-process')) {
+        debugging('non-payment submit detected - not submitting payment');
+        $form.get(0).submit();
         return true;
       }
+
+      $submit = getBillingSubmit();
+      var isWebform = getIsWebform();
+
       if (isWebform) {
         var $processorFields = $('.civicrm-enabled[name$="civicrm_1_contribution_1_contribution_payment_processor_id]"]');
 
-        if ($('#action').attr('value') === webformPrevious) {
-          // Don't submit if the webform back button was pressed
-          debugging('webform back button');
-          return true;
-        }
-        if ($('#wf-crm-billing-total').length) {
-          if ($('#wf-crm-billing-total').data('data-amount') === '0') {
+        $totalElement = $('#wf-crm-billing-total');
+        if ($totalElement.length) {
+          if ($totalElement.data('data-amount') === '0') {
             debugging('webform total is 0');
             return true;
           }
@@ -161,9 +174,22 @@
           }
         }
       }
-      // Disable the submit button to prevent repeated clicks, cache button text, restore if Stripe returns error
-      buttonText = $submit.attr('value');
-      $submit.prop('disabled', true).attr('value', 'Processing');
+
+      // Lock to prevent multiple submissions
+      if ($form.data('submitted') === true) {
+        // Previously submitted - don't submit again
+        alert('Form already submitted. Please wait.');
+        return true;
+      } else {
+        // Mark it so that the next submit can be ignored
+        // ADDED requirement that form be valid
+        if($form.valid()) {
+          $form.data('submitted', true);
+        }
+      }
+
+      // Disable the submit button to prevent repeated clicks
+      $submit.prop('disabled', true);
 
       // Handle multiple payment options and Stripe not being chosen.
       if ($form.find(".crm-section.payment_processor-section").length > 0) {
@@ -206,16 +232,11 @@
   }
 
   function getIsWebform() {
-    if ($('.webform-client-form').length) {
-      return true;
-    }
-    else {
-      return false;
-    }
+    return $('.webform-client-form').length;
   }
 
   function getBillingForm() {
-    return CRM.$('input#stripe-pub-key').closest('form');
+    return $('input#stripe-pub-key').closest('form');
   }
 
   function getBillingSubmit() {
@@ -223,24 +244,10 @@
     var isWebform = getIsWebform();
 
     if (isWebform) {
-      $submit = $form.find('.button-primary');
+      $submit = $form.find('[type="submit"].webform-submit');
     }
     else {
-      $submit = $form.find('input[type="submit"].validate');
-
-      // 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');
-      // Find submit buttons with formnovalidate=1 and add an onclick handler to set flag
-      $form.find('input[type="submit"][formnovalidate="1"], input[type="submit"].cancel').click( function() {
-        $form.data('submit-dont-process', 1);
-      });
-      // 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);
-        }
-      });
+      $submit = $form.find('[type="submit"].validate');
     }
     return $submit;
   }
diff --git a/stripe.php b/stripe.php
index 31b642dfce528b0e304181ffc495abed200da871..a2758c3abf6be97484684a7f7bae19e5756400b9 100644
--- a/stripe.php
+++ b/stripe.php
@@ -188,16 +188,56 @@ function stripe_civicrm_managed(&$entities) {
     }
   }
 
+// Flag so we don't add the stripe scripts more than once.
+static $_stripe_scripts_added;
+
+/**
+ * Implementation of hook_civicrm_alterContent
+ *
+ * Adding civicrm_stripe.js in a way that works for webforms and (some) Civi forms.
+ * hook_civicrm_buildForm is not called for webforms
+ *
+ * @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,
+   *   which is why we check for $object instanceof CRM_Financial_Form_Payment here to ensure that
+   *   Webforms always have scripts added).
+   * - Almost all forms have context = 'form' and a paymentprocessor object.
+   * - Membership backend form is a 'page' and has a _isPaymentProcessor=true flag.
+   *
+   */
+  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');
+      $content .= "<script src='{$stripeJSURL}'></script>";
+      $content .= "<script src='https://js.stripe.com/v2/'></script>";
+      $_stripe_scripts_added = TRUE;
+    }
+  }
+}
+
 /**
  * Add stripe.js to forms, to generate stripe token
+ * hook_civicrm_alterContent is not called for all forms (eg. CRM_Contribute_Form_Contribution on backend)
  * @param $formName
  * @param $form
  */
 function stripe_civicrm_buildForm($formName, &$form) {
+  global $_stripe_scripts_added;
   if (!empty($form->_paymentProcessor['class_name'])) {
-    // civicrm_stripe.js is not included on backend form renewal unless we add it here.
-    CRM_Core_Resources::singleton()->addScriptUrl('https://js.stripe.com/v2/', 10, 'page-body');
-    CRM_Core_Resources::singleton()->addScriptFile('com.drastikbydesign.stripe', 'js/civicrm_stripe.js');
+    if (!$_stripe_scripts_added) {
+      CRM_Core_Resources::singleton()
+        ->addScriptUrl('https://js.stripe.com/v2/', 10, 'page-body');
+      CRM_Core_Resources::singleton()
+        ->addScriptFile('com.drastikbydesign.stripe', 'js/civicrm_stripe.js');
+    }
+    $_stripe_scripts_added = TRUE;
   }
 }