From bf1d05a3840c8a077b7ebec5474e6f7f4add51e1 Mon Sep 17 00:00:00 2001 From: drastik <jwjoshuawalker@gmail.com> Date: Tue, 14 Jan 2014 19:03:36 -0700 Subject: [PATCH] This is a large update to the core of how the extension works. BillingBlock.tpl is gone! JS For Stripe is now injected on appropriate pages. Known fixes ----------- GitHub Issue: https://github.com/drastik/civicrm_stripe/issues/28 Ability to submit backend/offline contributions for a contact w/ Stripe. Previous fixes -------------- The last commit also fixed GitHub Issue: https://github.com/drastik/civicrm_stripe/issues/9 USD forced on recurring plans. --- CRM/Core/Payment/Stripe.php | 8 +- README.txt | 6 +- js/civicrm_stripe.js | 86 +++++++ stripe.php | 61 +++++ templates/CRM/Core/BillingBlock.tpl | 382 ---------------------------- 5 files changed, 159 insertions(+), 384 deletions(-) create mode 100644 js/civicrm_stripe.js delete mode 100644 templates/CRM/Core/BillingBlock.tpl diff --git a/CRM/Core/Payment/Stripe.php b/CRM/Core/Payment/Stripe.php index 5935f276..8439105f 100644 --- a/CRM/Core/Payment/Stripe.php +++ b/CRM/Core/Payment/Stripe.php @@ -355,10 +355,16 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { } // Prepare the charge array, minus Customer/Card details. + if (empty($params['description'])) { + $params['description'] = ts('CiviCRM backend contribution'); + } + else { + $params['description'] = ts('# CiviCRM Donation Page # ') . $params['description']; + } $stripe_charge = array( 'amount' => $amount, 'currency' => strtolower($params['currencyID']), - 'description' => '# CiviCRM Donation Page # ' . $params['description'] . + 'description' => $params['description'] . ' # Invoice ID # ' . $params['invoiceID'], ); diff --git a/README.txt b/README.txt index 644ddd0f..97f79231 100644 --- a/README.txt +++ b/README.txt @@ -1,9 +1,13 @@ WARNING: ------- -Version 1.8+ of this extension *must* use Stripe's latest API verison as of Jan 9th, 2014. +Version 1.8+ of this extension *must* use Stripe's latest API verison as of Jan 9th, 2014. This is the API setting within your stripe.com account settings. More info on how to change: https://stripe.com/docs/upgrades#how-can-i-upgrade-my-api +Also, your CiviCRM 'Resource URLs' must be set to the extensions directory +relative to Drupal/CRM base. Example: /sites/all/civicrm_extensions/ +This is the admin page for Resource URLs: /civicrm/admin/setting/url + PLEASE READ: ----------- There are 3 versions of this extension available. This is: diff --git a/js/civicrm_stripe.js b/js/civicrm_stripe.js new file mode 100644 index 00000000..8fb7d294 --- /dev/null +++ b/js/civicrm_stripe.js @@ -0,0 +1,86 @@ +/** + * @file + * JS Integration between CiviCRM & Stripe. + */ +(function ($) { + + // Response from Stripe.createToken. + function stripeResponseHandler(status, response) { + 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.stripe-payment-form").prepend('<div class="messages crm-error stripe-message">' + +'<strong>Payment Error Response:</strong>' + +'<ul id="errorList">' + +'<li>Error: ' + response.error.message + '</li>' + +'</ul>' + +'</div>'); + + $('form.stripe-payment-form input.form-submit').removeAttr("disabled"); + } + else { + var token = response['id']; + // Update form with the token & submit. + $("input#stripe-token").val(token); + $("form.stripe-payment-form").get(0).submit(); + } + } + + // Prepare the form. + $(document).ready(function() { + $.getScript('https://js.stripe.com/v1/', function() { + Stripe.setPublishableKey(CRM.stripe.pub_key); + }); + /* + * Identify the payment form. + * Don't reference by form#id since it changes between payment pages + * (Contribution / Event / etc). + */ + $('#crm-container>form').addClass('stripe-payment-form'); + $('form.stripe-payment-form').unbind('submit'); + // Intercept form submission. + $("form.stripe-payment-form").submit(function(event) { + // Disable the submit button to prevent repeated clicks. + $('form.stripe-payment-form input.form-submit').attr("disabled", "disabled"); + if ($(this).find("#priceset input[type='radio']:checked").data('amount') == 0) { + return true; + } + // Handle multiple payment options and Stripe not being chosen. + if ($(this).find(".crm-section.payment_processor-section").length > 0) { + if (!($(this).find('input[name="hidden_processor"]').length > 0)) { + return true; + } + } + + // Handle changes introduced in CiviCRM 4.3. + if ($(this).find('#credit_card_exp_date_M').length > 0) { + var cc_month = $(this).find('#credit_card_exp_date_M').val(); + var cc_year = $(this).find('#credit_card_exp_date_Y').val(); + } + else { + var cc_month = $(this).find('#credit_card_exp_date\\[M\\]').val(); + var cc_year = $(this).find('#credit_card_exp_date\\[Y\\]').val(); + } + + Stripe.createToken({ + name: $('#billing_first_name').val() + ' ' + $('#billing_last_name').val(), + address_zip: $("#billing_postal_code-5").val(), + number: $('#credit_card_number').val(), + cvc: $('#cvv2').val(), + exp_month: cc_month, + exp_year: cc_year + }, stripeResponseHandler); + + // Prevent the form from submitting with the default action. + return false; + }); + + + }); + + +}(jQuery)); diff --git a/stripe.php b/stripe.php index c65e7539..e7430de3 100644 --- a/stripe.php +++ b/stripe.php @@ -110,10 +110,71 @@ function stripe_civicrm_buildForm($formName, &$form) { if (empty($_GET['type'])) { if (!isset($form->_elementIndex['stripe_token'])) { $form->addElement('hidden', 'stripe_token', NULL, array('id'=> 'stripe-token')); + stripe_add_stripe_js($form); } } } } + // For the 'Record Contribution' backend page. + if ($formName == 'CRM_Contribute_Form_Contribution' && !empty($form->_processors)) { + if (!isset($form->_elementIndex['stripe_token'])) { + $form->addElement('hidden', 'stripe_token', NULL, array('id'=> 'stripe-token')); + stripe_add_stripe_js($form); + } + // Add email field as it would usually be found on donation forms. + if (!isset($form->_elementIndex['email']) && !empty($form->userEmail)) { + $form->addElement('hidden', 'email', $form->userEmail, array('id'=> 'user-email')); + } + } +} + +/** + * Add publishable key and event bindings for Stripe.js. + */ +function stripe_add_stripe_js($form) { + CRM_Core_Resources::singleton()->addScriptFile('com.drastikbydesign.stripe', 'js/civicrm_stripe.js'); + + if (!empty($form->_paymentProcessor['password'])) { + $stripe_pub_key = $form->_paymentProcessor['password']; + } + else { + // Find Stripe's payproc ID in Civi. + $query_params = array( + 1 => array('Stripe', 'String'), + ); + $stripe_pp_id = CRM_Core_DAO::singleValueQuery("SELECT id + FROM civicrm_payment_processor_type + WHERE name = %1", $query_params); + + // Find out if the form might use Stripe. + if (!empty($stripe_pp_id)) { + $mode = ($form->_mode == 'live' ? 0 : 1); + $query_params = array( + 1 => array($stripe_pp_id, 'Integer'), + 2 => array($mode, 'Integer'), + ); + $stripe_procs_query = CRM_Core_DAO::executeQuery("SELECT name, password + FROM civicrm_payment_processor + WHERE payment_processor_type_id = %1 AND is_test = %2", $query_params); + // Loop through and see if Stripe is on this form. + while ($stripe_procs_query->fetch()) { + foreach ($form->_processors as $form_processor) { + if ($form_processor == $stripe_procs_query->name) { + $stripe_pub_key = $stripe_procs_query->password; + break; + } + if (!empty($stripe_pub_key)) { break; } + } + } + } + } + + // Add Stripe publishable key to CRM.stripe namespace. + CRM_Core_Resources::singleton()->addSetting(array( + 'stripe' => array( + 'pub_key' => $stripe_pub_key, + ) + )); } /** diff --git a/templates/CRM/Core/BillingBlock.tpl b/templates/CRM/Core/BillingBlock.tpl deleted file mode 100644 index 7567a2f2..00000000 --- a/templates/CRM/Core/BillingBlock.tpl +++ /dev/null @@ -1,382 +0,0 @@ -{* - +--------------------------------------------------------------------+ - | CiviCRM version 4.4 | - +--------------------------------------------------------------------+ - | Copyright CiviCRM LLC (c) 2004-2013 | - +--------------------------------------------------------------------+ - | This file is a part of CiviCRM. | - | | - | CiviCRM is free software; you can copy, modify, and distribute it | - | under the terms of the GNU Affero General Public License | - | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. | - | | - | CiviCRM is distributed in the hope that it will be useful, but | - | WITHOUT ANY WARRANTY; without even the implied warranty of | - | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | - | See the GNU Affero General Public License for more details. | - | | - | You should have received a copy of the GNU Affero General Public | - | License and the CiviCRM Licensing Exception along | - | with this program; if not, contact CiviCRM LLC | - | at info[AT]civicrm[DOT]org. If you have questions about the | - | GNU Affero General Public License or the licensing of CiviCRM, | - | see the CiviCRM license FAQ at http://civicrm.org/licensing | - +--------------------------------------------------------------------+ -*} -{crmRegion name="billing-block"} -{* Add 'required' marker to billing fields in this template for front-end / online contribution and event registration forms only. *} -{if $context EQ 'front-end'} - {assign var=reqMark value=' <span class="crm-marker" title="This field is required.">*</span>'} -{else} - {assign var=reqMark value=''} -{/if} - -{if $form.credit_card_number or $form.bank_account_number} - -<!-- START Stripe --> - {if $paymentProcessor.payment_processor_type == 'Stripe'} - <script type="text/javascript"> - var stripe_publishable_key = '{$paymentProcessor.password}'; - {literal} - cj(function() { - cj(document).ready(function() { - cj.getScript('https://js.stripe.com/v1/', function() { - Stripe.setPublishableKey(stripe_publishable_key); - }); - /* - * Identify the payment form. - * Don't reference by form#id since it changes between payment pages - * (Contribution / Event / etc). - */ - cj('#crm-container>form').addClass('stripe-payment-form'); - cj('form.stripe-payment-form').unbind('submit'); - // Intercept form submission. - cj("form.stripe-payment-form").submit(function(event) { - // Disable the submit button to prevent repeated clicks. - cj('form.stripe-payment-form input.form-submit').attr("disabled", "disabled"); - if (cj(this).find("#priceset input[type='radio']:checked").data('amount') == 0) { - return true; - } - // Handle multiple payment options and Stripe not being chosen. - if (cj(this).find(".crm-section.payment_processor-section").length > 0) { - if (!(cj(this).find('input[name="hidden_processor"]').length > 0)) { - return true; - } - } - - // Handle changes introduced in CiviCRM 4.3. - if (cj(this).find('#credit_card_exp_date_M').length > 0) { - var cc_month = cj(this).find('#credit_card_exp_date_M').val(); - var cc_year = cj(this).find('#credit_card_exp_date_Y').val(); - } - else { - var cc_month = cj(this).find('#credit_card_exp_date\\[M\\]').val(); - var cc_year = cj(this).find('#credit_card_exp_date\\[Y\\]').val(); - } - - Stripe.createToken({ - name: cj('#billing_first_name').val() + ' ' + cj('#billing_last_name').val(), - address_zip: cj("#billing_postal_code-5").val(), - number: cj('#credit_card_number').val(), - cvc: cj('#cvv2').val(), - exp_month: cc_month, - exp_year: cc_year - }, stripeResponseHandler); - - // Prevent the form from submitting with the default action. - return false; - }); - }); - // Response from Stripe.createToken. - function stripeResponseHandler(status, response) { - if (response.error) { - cj('html, body').animate({ scrollTop: 0 }, 300); - // Show the errors on the form. - if (cj(".messages.crm-error.stripe-message").length > 0) { - cj(".messages.crm-error.stripe-message").slideUp(); - cj(".messages.crm-error.stripe-message:first").remove(); - } - cj("form.stripe-payment-form").prepend('<div class="messages crm-error stripe-message">' - +'<strong>Payment Error Response:</strong>' - +'<ul id="errorList">' - +'<li>Error: ' + response.error.message + '</li>' - +'</ul>' - +'</div>'); - - cj('form.stripe-payment-form input.form-submit').removeAttr("disabled"); - } - else { - var token = response['id']; - // Update form with the token & submit. - cj("input#stripe-token").val(token); - cj("form.stripe-payment-form").get(0).submit(); - } - } - }); - {/literal} - </script> - {/if} -<!-- END Stripe --> - - <div id="payment_information"> - <fieldset class="billing_mode-group {if $paymentProcessor.payment_type & 2}direct_debit_info-group{else}credit_card_info-group{/if}"> - <legend> - {if $paymentProcessor.payment_type & 2} - {ts}Direct Debit Information{/ts} - {else} - {ts}Credit Card Information{/ts} - {/if} - </legend> - {if $paymentProcessor.billing_mode & 2 and !$hidePayPalExpress } - <div class="crm-section no-label paypal_button_info-section"> - <div class="content description"> - {ts}If you have a PayPal account, you can click the PayPal button to continue. Otherwise, fill in the credit card and billing information on this form and click <strong>Continue</strong> at the bottom of the page.{/ts} - </div> - </div> - <div class="crm-section no-label {$form.$expressButtonName.name}-section"> - <div class="content description"> - {$form.$expressButtonName.html} - <div class="description">Save time. Checkout securely. Pay without sharing your financial information. </div> - </div> - </div> - {/if} - - {if $paymentProcessor.billing_mode & 1} - <div class="crm-section billing_mode-section {if $paymentProcessor.payment_type & 2}direct_debit_info-section{else}credit_card_info-section{/if}"> - {if $paymentProcessor.payment_type & 2} - <div class="crm-section {$form.account_holder.name}-section"> - <div class="label">{$form.account_holder.label}</div> - <div class="content">{$form.account_holder.html}</div> - <div class="clear"></div> - </div> - <div class="crm-section {$form.bank_account_number.name}-section"> - <div class="label">{$form.bank_account_number.label}</div> - <div class="content">{$form.bank_account_number.html}</div> - <div class="clear"></div> - </div> - <div class="crm-section {$form.bank_identification_number.name}-section"> - <div class="label">{$form.bank_identification_number.label}</div> - <div class="content">{$form.bank_identification_number.html}</div> - <div class="clear"></div> - </div> - <div class="crm-section {$form.bank_name.name}-section"> - <div class="label">{$form.bank_name.label}</div> - <div class="content">{$form.bank_name.html}</div> - <div class="clear"></div> - </div> - {else} - <div class="crm-section {$form.credit_card_type.name}-section"> - <div class="label">{$form.credit_card_type.label} {$reqMark}</div> - <div class="content"> - {$form.credit_card_type.html} - <div class="crm-credit_card_type-icons"></div> - </div> - <div class="clear"></div> - </div> - <div class="crm-section {$form.credit_card_number.name}-section"> - <div class="label">{$form.credit_card_number.label} {$reqMark}</div> - <div class="content">{$form.credit_card_number.html|crmAddClass:creditcard}</div> - <div class="clear"></div> - </div> - <div class="crm-section {$form.cvv2.name}-section"> - <div class="label">{$form.cvv2.label} {$reqMark}</div> - <div class="content"> - {$form.cvv2.html} - <span class="cvv2-icon" title="{ts}Usually the last 3-4 digits in the signature area on the back of the card.{/ts}"> </span> - </div> - <div class="clear"></div> - </div> - <div class="crm-section {$form.credit_card_exp_date.name}-section"> - <div class="label">{$form.credit_card_exp_date.label} {$reqMark}</div> - <div class="content">{$form.credit_card_exp_date.html}</div> - <div class="clear"></div> - </div> - {/if} - </div> - </fieldset> - - {if $profileAddressFields} - <input type="checkbox" id="billingcheckbox" value="0"> <label for="billingcheckbox">{ts}My billing address is the same as above{/ts}</label> - {/if} - <fieldset class="billing_name_address-group"> - <legend>{ts}Billing Name and Address{/ts}</legend> - <div class="crm-section billing_name_address-section"> - <div class="crm-section {$form.billing_first_name.name}-section"> - <div class="label">{$form.billing_first_name.label} {$reqMark}</div> - <div class="content">{$form.billing_first_name.html}</div> - <div class="clear"></div> - </div> - <div class="crm-section {$form.billing_middle_name.name}-section"> - <div class="label">{$form.billing_middle_name.label}</div> - <div class="content">{$form.billing_middle_name.html}</div> - <div class="clear"></div> - </div> - <div class="crm-section {$form.billing_last_name.name}-section"> - <div class="label">{$form.billing_last_name.label} {$reqMark}</div> - <div class="content">{$form.billing_last_name.html}</div> - <div class="clear"></div> - </div> - {assign var=n value=billing_street_address-$bltID} - <div class="crm-section {$form.$n.name}-section"> - <div class="label">{$form.$n.label} {$reqMark}</div> - <div class="content">{$form.$n.html}</div> - <div class="clear"></div> - </div> - {assign var=n value=billing_city-$bltID} - <div class="crm-section {$form.$n.name}-section"> - <div class="label">{$form.$n.label} {$reqMark}</div> - <div class="content">{$form.$n.html}</div> - <div class="clear"></div> - </div> - {assign var=n value=billing_country_id-$bltID} - <div class="crm-section {$form.$n.name}-section"> - <div class="label">{$form.$n.label} {$reqMark}</div> - <div class="content">{$form.$n.html|crmAddClass:big}</div> - <div class="clear"></div> - </div> - {assign var=n value=billing_state_province_id-$bltID} - <div class="crm-section {$form.$n.name}-section"> - <div class="label">{$form.$n.label} {$reqMark}</div> - <div class="content">{$form.$n.html|crmAddClass:big}</div> - <div class="clear"></div> - </div> - {assign var=n value=billing_postal_code-$bltID} - <div class="crm-section {$form.$n.name}-section"> - <div class="label">{$form.$n.label} {$reqMark}</div> - <div class="content">{$form.$n.html}</div> - <div class="clear"></div> - </div> - </div> - </fieldset> - {else} - </fieldset> - {/if} - </div> - - {if $profileAddressFields} - <script type="text/javascript"> - {literal} - -cj( function( ) { - // build list of ids to track changes on - var address_fields = {/literal}{$profileAddressFields|@json_encode}{literal}; - var input_ids = {}; - var select_ids = {}; - var orig_id = field = field_name = null; - - // build input ids - cj('.billing_name_address-section input').each(function(i){ - orig_id = cj(this).attr('id'); - field = orig_id.split('-'); - field_name = field[0].replace('billing_', ''); - if(field[1]) { - if(address_fields[field_name]) { - input_ids['#'+field_name+'-'+address_fields[field_name]] = '#'+orig_id; - } - } - }); - if(cj('#first_name').length) - input_ids['#first_name'] = '#billing_first_name'; - if(cj('#middle_name').length) - input_ids['#middle_name'] = '#billing_middle_name'; - if(cj('#last_name').length) - input_ids['#last_name'] = '#billing_last_name'; - - // build select ids - cj('.billing_name_address-section select').each(function(i){ - orig_id = cj(this).attr('id'); - field = orig_id.split('-'); - field_name = field[0].replace('billing_', '').replace('_id', ''); - if(field[1]) { - if(address_fields[field_name]) { - select_ids['#'+field_name+'-'+address_fields[field_name]] = '#'+orig_id; - } - } - }); - - // detect if billing checkbox should default to checked - var checked = true; - for(var id in input_ids) { - var orig_id = input_ids[id]; - if(cj(id).val() != cj(orig_id).val()) { - checked = false; - break; - } - } - for(var id in select_ids) { - var orig_id = select_ids[id]; - if(cj(id).val() != cj(orig_id).val()) { - checked = false; - break; - } - } - if(checked) { - cj('#billingcheckbox').attr('checked', 'checked'); - cj('.billing_name_address-group').hide(); - } - - // onchange handlers for non-billing fields - for(var id in input_ids) { - var orig_id = input_ids[id]; - cj(id).change(function(){ - var id = '#'+cj(this).attr('id'); - var orig_id = input_ids[id]; - - // if billing checkbox is active, copy other field into billing field - if(cj('#billingcheckbox').attr('checked')) { - cj(orig_id).val( cj(id).val() ); - }; - }); - }; - for(var id in select_ids) { - var orig_id = select_ids[id]; - cj(id).change(function(){ - var id = '#'+cj(this).attr('id'); - var orig_id = select_ids[id]; - - // if billing checkbox is active, copy other field into billing field - if(cj('#billingcheckbox').attr('checked')) { - cj(orig_id+' option').removeAttr('selected'); - cj(orig_id+' option[value="'+cj(id).val()+'"]').attr('selected', 'selected'); - }; - - if(orig_id == '#billing_country_id-5') { - cj(orig_id).change(); - } - }); - }; - - - // toggle show/hide - cj('#billingcheckbox').click(function(){ - if(this.checked) { - cj('.billing_name_address-group').hide(200); - - // copy all values - for(var id in input_ids) { - var orig_id = input_ids[id]; - cj(orig_id).val( cj(id).val() ); - }; - for(var id in select_ids) { - var orig_id = select_ids[id]; - cj(orig_id+' option').removeAttr('selected'); - cj(orig_id+' option[value="'+cj(id).val()+'"]').attr('selected', 'selected'); - }; - } else { - cj('.billing_name_address-group').show(200); - } - }); - - // remove spaces, dashes from credit card number - cj('#credit_card_number').change(function(){ - var cc = cj('#credit_card_number').val() - .replace(/ /g, '') - .replace(/-/g, ''); - cj('#credit_card_number').val(cc); - }); -}); -{/literal} -</script> -{/if} -{/if} -{/crmRegion} -- GitLab