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; } }