PageSourceSearch

https://ifeat.org/app/civi-extensions/mjwshared/js/crm.payment.js?r=QSjBQen_US

js ifeat.org collected 2026-09-25 05:20:16 UTC 27,298 bytes, 680 lines download raw bytes

1/*jshint esversion: 6 */
2(function($, ts) {
3  var payment = {
4    name: 'CRM.payment',
5    form: null,
6    submitButtons: null,
7    scripts: {},
8
9    /**
10     * Get the total amount on the form
11     * @returns {number}
12     */
13    getTotalAmount: function() {
14      var totalAmount = 0.0;
15
16      if (document.getElementById('totalAmount') && document.getElementById('totalAmount').getAttribute('data-totalAmount')) {
17        // See https://github.com/civicrm/civicrm-core/pull/32574 / https://lab.civicrm.org/extensions/stripe/-/issues/348
18        // Get the total amount from the lineItems list
19        totalAmount = parseFloat(document.getElementById('totalAmount').getAttribute('data-totalAmount'));
20      }
21      else if (this.getIsDrupalWebform()) {
22        // This is how webform civicrm calculates the amount in webform_civicrm_payment.js
23        $('.line-item:visible', '#wf-crm-billing-items').each(function() {
24          totalAmount += parseFloat($(this).data('amount'));
25        });
26      }
27      else if (typeof calculateTotalFee == 'function') {
28          // This is ONLY triggered in the following circumstances on a CiviCRM contribution page:
29          // - With a priceset that allows a 0 amount to be selected.
30          // - When we are the ONLY payment processor configured on the page.
31          // It is ALSO replaced by percentagepricesetfield and extrafee extensions
32          totalAmount = parseFloat(calculateTotalFee());
33        }
34      else if (document.getElementById('totalTaxAmount') !== null) {
35        totalAmount = this.calculateTaxAmount();
36        this.debugging(this.name, 'Calculated amount using internal calculateTaxAmount()');
37      }
38      else if ($("#priceset [price]").length > 0) {
39        // This is ONLY triggered in the following circumstances on a CiviCRM contribution page:
40        // - With a priceset that allows a 0 amount to be selected.
41        // - When we are the ONLY payment processor configured on the page.
42        $("#priceset [price]").each(function () {
43          totalAmount = totalAmount + $(this).data('line_raw_total');
44        });
45      }
46      else if (document.getElementById('total_amount')) {
47        // The input#total_amount field exists on backend contribution forms
48        totalAmount = parseFloat(document.getElementById('total_amount').value);
49      }
50      if (this.isEventAdditionalParticipants()) {
51        // The amount shown on the initial page is the total amount for 1 registration.
52        // It is "impossible" to calculate the total amount because we don't know what will be selected
53        // for each additional participant.
54        // Set totalAmount = null to force the use of a setupIntent
55        if (CRM.vars.cividiscount !== undefined && CRM.vars.cividiscount.discountApplied && CRM.vars.cividiscount.totalAmountZero) {
56          // Special case for CiviDiscount and 100% discount. We know that total will be zero for all participants
57          // Setting this to 0.0 means the stripe element will not be loaded or validated.
58          totalAmount = 0.0;
59        }
60        else {
61          // Force a setupIntent because we don't know the total amount
62          totalAmount = null;
63        }
64      }
65      this.debugging(this.name, 'getTotalAmount: ' + totalAmount);
66      return totalAmount;
67    },
68
69    /**
70     * This is calculated in CRM/Contribute/Form/Contribution.tpl and is used to calculate the total
71     *   amount with tax on backend submit contribution forms.
72     *   The only way we can get the amount is by parsing the text field and extracting the final bit after the space.
73     *   eg. "Amount including Tax: $ 4.50" gives us 4.50.
74     *   The PHP side is responsible for converting money formats (we just parse to cents and remove any ,. chars).
75     *
76     * @returns {float}
77     */
78    calculateTaxAmount: function() {
79      var totalTaxAmount = 0;
80      if (document.getElementById('totalTaxAmount') === null) {
81        return totalTaxAmount;
82      }
83
84      // If tax and invoicing is disabled totalTaxAmount div exists but is empty
85      if (document.getElementById('totalTaxAmount').textContent.length === 0) {
86        totalTaxAmount = document.getElementById('total_amount').value;
87      }
88      else {
89        // Otherwise totalTaxAmount div contains a textual amount including currency symbol
90
91        // Use the "separator" variable declared in Contribution.tpl or default to . for decimal point
92        var dPoint = (typeof separator !== 'undefined') && separator || '.';
93
94        // Regular expression to comb for numeric parts of the totalTaxAmount div.
95        var matcher = new RegExp('\\d{1,3}(' + dPoint.replace(/\W/g, '\\$&') + '\\d{0,2})?', 'g');
96
97        // Join all parts and ensure the decimal point is per javascript format.
98        totalTaxAmount = document.getElementById('totalTaxAmount').textContent.match(matcher).join('').replace(dPoint, '.');
99      }
100      totalTaxAmount = parseFloat(totalTaxAmount);
101      if (isNaN(totalTaxAmount)) {
102        totalTaxAmount = 0.0;
103      }
104      return totalTaxAmount;
105    },
106
107    /**
108     * Get currency on the form
109     * @param defaultCurrency
110     * @returns {string}
111     */
112    getCurrency: function(defaultCurrency) {
113      var currency = defaultCurrency;
114      if (this.form.querySelector('#currency')) {
115        currency = this.form.querySelector('#currency').value;
116      }
117      this.debugging(this.name, 'Currency is: ' + currency);
118      return currency;
119    },
120
121    /**
122     * Is the event registering additional participants (this means we do not know the full amount)
123     *
124     * @returns {boolean}
125     */
126    isEventAdditionalParticipants: function() {
127      if ((document.getElementById('additional_participants') !== null) &&
128        (document.getElementById('additional_participants').value.length !== 0)) {
129        this.debugging(this.name, 'Event has additional participants');
130        return true;
131      }
132      return false;
133    },
134
135    /**
136     * Are we currently loaded on a drupal webform?
137     *
138     * @returns {boolean}
139     */
140    getIsDrupalWebform: function() {
141      // form class for drupal webform: webform-client-form (drupal 7); webform-submission-form (drupal 8)
142      if (this.form !== null) {
143        return this.form.classList.contains('webform-client-form') || this.form.classList.contains('webform-submission-form');
144      }
145      return false;
146    },
147
148    /**
149     * Get the Billing Form as a DOM/HTMLElement.
150     * Also set the "form" property on CRM.payment.
151     *
152     * @returns {HTMLElement}
153     */
154    getBillingForm: function() {
155      // If we have a billing form on the page with our processor
156      var billingFormID = $('div#crm-payment-js-billing-form-container').closest('form').attr('id');
157      if ((typeof billingFormID === 'undefined') || (!billingFormID.length)) {
158        // If we have multiple payment processors to select and we are not currently loaded
159        billingFormID = $('input[name=hidden_processor]').closest('form').prop('id');
160      }
161      if (typeof billingFormID === 'undefined' || (!billingFormID.length)) {
162        billingFormID = $('div#billing-payment-block').closest('form').prop('id');
163      }
164      if (typeof billingFormID === 'undefined' || (!billingFormID.length)) {
165        this.debugging(this.name, 'no billing form');
166        this.form = null;
167        return this.form;
168      }
169      // We have to use document.getElementById here so we have the right elementtype for appendChild()
170      this.form = document.getElementById(billingFormID);
171      return this.form;
172    },
173
174    /**
175     * Get all the billing submit buttons on the form as DOM elements
176     * Also set the "submitButtons" property on CRM.payment.
177     *
178     * @returns {NodeList}
179     */
180    getBillingSubmit: function() {
181      if (CRM.payment.getIsDrupalWebform()) {
182        this.submitButtons = this.form.querySelectorAll('[type="submit"].webform-submit');
183        if (this.submitButtons.length === 0) {
184          // drupal 8 webform
185          this.submitButtons = this.form.querySelectorAll('[type="submit"].webform-button--submit');
186        }
187      }
188      else {
189        this.submitButtons = this.form.querySelectorAll('[type="submit"].validate');
190      }
191      if (this.submitButtons.length === 0) {
192        this.debugging(this.name, 'No submit button found!');
193      }
194      return this.submitButtons;
195    },
196
197    /**
198     * Are we creating a recurring contribution?
199     * @returns {boolean}
200     */
201    getIsRecur: function() {
202      if (!this.supportsRecur()) {
203        return false;
204      }
205      var isRecur = false;
206      // Auto-renew contributions for CiviCRM Webforms.
207      if (this.getIsDrupalWebform()) {
208        if (($('input[data-civicrm-field-key$="contribution_installments"]').length !== 0 && $('input[data-civicrm-field-key$="contribution_installments"]').val() != 1) ||
209          ($('input[data-civicrm-field-key$="contribution_frequency_interval"]').length !== 0 && $('input[data-civicrm-field-key$="contribution_frequency_interval"]').val() > 0)
210        ) {
211          isRecur = true;
212        }
213      }
214
215      var membershipAutoRenewCheckBox = document.getElementById('auto_renew');
216      var isRecurCheckBox = document.getElementById('is_recur');
217      // Auto-renew contributions
218      if (isRecurCheckBox !== null) {
219        if (isRecurCheckBox.type == 'hidden') {
220          isRecur = (isRecurCheckBox.value == 1);
221        }
222        else {
223          isRecur = Boolean(isRecurCheckBox.checked);
224        }
225      }
226      // Auto-renew memberships
227      else if (membershipAutoRenewCheckBox !== null) {
228        isRecur = Boolean(membershipAutoRenewCheckBox.checked);
229      }
230      if (!isRecur) {
231        // multi-installment pledges are also recurring....
232        var is_pledge = $('input[name="is_pledge"]:checked');
233        isRecur = is_pledge.length === 1 && parseInt(is_pledge.val()) !== 0 && parseInt($('#pledge_installments').val()) > 1;
234      }
235      this.debugging(this.name, 'isRecur is ' + isRecur);
236      return isRecur;
237    },
238
239    /**
240     * Does the form support recurring contributions?
241     * @returns {boolean}
242     */
243    supportsRecur: function() {
244      var supportsRecur = false;
245      // Auto-renew contributions for CiviCRM Webforms.
246      if (this.getIsDrupalWebform()) {
247        if (($('input[data-civicrm-field-key$="contribution_installments"]').length !== 0) ||
248          ($('input[data-civicrm-field-key$="contribution_frequency_interval"]').length !== 0)
249        ) {
250          supportsRecur = true;
251        }
252      }
253      // Auto-renew contributions
254      if (document.getElementById('is_recur') !== null) {
255        supportsRecur = true;
256      }
257      // Auto-renew memberships
258      // input[name="auto_renew"] : set to 1 when there is a force-renew membership with no priceset.
259      else if ($('input[name="auto_renew"]').length !== 0) {
260        supportsRecur = true;
261      }
262      else if ($('input[name=\'is_pledge\']').length !== 0) {
263        // Pledge payments
264        supportsRecur = true;
265      }
266      this.debugging(this.name, 'supportsRecur is ' + supportsRecur);
267      return supportsRecur;
268    },
269
270    /**
271     * Try and get the billing email(s) from the form
272     * @returns {string} separated by ;
273     */
274    getBillingEmail: function() {
275      var billingEmail = '';
276      $(this.form).find('input[id^=email]').filter(':visible').each(function() { billingEmail += $(this).val() + ';'; });
277      return billingEmail;
278    },
279
280    /**
281     * Try and get the billing contact name from the form
282     * @returns {string} separated by ;
283     */
284    getBillingName: function() {
285      var billingName = '';
286      $(this.form).find('input#first_name,input#last_name').each(function() { billingName += $(this).val() + ';'; });
287      return billingName;
288    },
289
290    /**
291     * Get the selected payment processor on the form
292     * @returns {null|number}
293     */
294    getPaymentProcessorSelectorValue: function() {
295      // Frontend radio selector
296      var paymentProcessorSelected = this.form.querySelector('input[name="payment_processor_id"]:checked');
297      if (paymentProcessorSelected !== null) {
298        return parseInt(paymentProcessorSelected.value);
299      }
300      else {
301        // Backend select dropdown
302        paymentProcessorSelected = this.form.querySelector('select[name="payment_processor_id"]');
303        if (paymentProcessorSelected !== null) {
304          return parseInt(paymentProcessorSelected.value);
305        }
306      }
307      return null;
308    },
309
310    /**
311     * Is the AJAX request a payment form?
312     * @param {string} url
313     * @returns {bool}
314     */
315    isAJAXPaymentForm: function(url) {
316      // /civicrm/payment/form? occurs when a payproc is selected on page
317      // /civicrm/contact/view/participant occurs when payproc is first loaded on event credit card payment
318      // On WordPress these are urlencoded
319      var patterns = [
320        "(\/|%2F)payment(\/|%2F)form",
321        "(\/|\%2F)contact(\/|\%2F)view(\/|\%2F)participant",
322        "(\/|\%2F)contact(\/|\%2F)view(\/|\%2F)membership",
323        "(\/|\%2F)contact(\/|\%2F)view(\/|\%2F)contribution"
324      ];
325
326      if (CRM.config.isFrontend && CRM.vars.payment && CRM.vars.payment.basePage !== 'civicrm') {
327        for (const pattern of patterns) {
328          if (url.match(CRM.vars.payment.basePage + pattern) !== null) {
329            return true;
330          }
331        }
332      }
333      for (const pattern of patterns) {
334        if (url.match('civicrm' + pattern) !== null) {
335          return true;
336        }
337      }
338
339    },
340
341    /**
342     * Call this function before submitting the form to CiviCRM (if you ran setBillingFieldsRequiredForJQueryValidate()).
343     * The "name" parameter on a group of checkboxes where at least one must be checked must be the same or val
343idation will require all of them!
344     * Reset the name of the checkboxes before submitting otherwise CiviCRM will not get the checkbox values.
345     */
346    resetBillingFieldsRequiredForJQueryValidate: function() {
347      $('div#priceset input[type="checkbox"], fieldset.crm-profile input[type="checkbox"], #on-behalf-block input[type="checkbox"]').each(function() {
348        if ($(this).attr('data-name') !== undefined) {
349          $(this).attr('name', $(this).attr('data-name'));
350        }
351      });
352    },
353
354    /**
355     * Call this function before running jQuery validation
356     *
357     * CustomField checkboxes in profiles do not get the "required" class.
358     * This should be fixed in CRM_Core_BAO_CustomField::addQuickFormElement but requires that the "name" is fixed as well.
359     */
360    setBillingFieldsRequiredForJQueryValidate: function() {
361      $('div.label span.crm-marker').each(function() {
362        $(this).closest('div').next('div').find('input[type="checkbox"]').addClass('required');
363      });
364
365      // The "name" parameter on a set of checkboxes where at least one must be checked must be the same or val
365idation will require all of them!
366      // Checkboxes for custom fields are added as quickform "advcheckbox" which seems to require a unique name for each checkbox. But that breaks
367      //   jQuery validation because each checkbox in a required group must have the same name.
368      // We store the original name and then change it. resetBillingFieldsRequiredForJQueryValidate() must be called before submit.
369      // Most checkboxes get names like: "custom_63[1]" but "onbehalf" checkboxes get "onbehalf[custom_63][1]". We change them to "custom_63" and "onbehalf[custom_63]".
370      $('div#priceset input[type="checkbox"], fieldset.crm-profile input[type="checkbox"], #on-behalf-block input[type="checkbox"]').each(function() {
371        var name = $(this).attr('name');
372        if (name !== undefined) {
373          $(this).attr('data-name', name);
374          $(this).attr('name', name.replace('[' + name.split('[').pop(), ''));
375          $(this).removeAttr('required');
376          $(this).removeClass('required');
377          $(this).removeAttr('aria-required');
378        }
379      });
380
381      // Default email validator accepts test@example but on [email protected] is valid (https://jqueryvalidation.org/jQuery.validator.methods/)
382      $.validator.methods.email = function(value, element) {
383        // Regex from https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
384        return this.optional(element) || /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(value);
385      };
386    },
387
388    /**
389     * Drupal webform requires a custom element to determine how it should process the submission
390     *   or if it should do another action. Because we trigger submit via javascript we have to add this manually.
391     * @param submitAction
392     */
393    addDrupalWebformActionElement: function(submitAction) {
394      var hiddenInput = null;
395      if (document.getElementById('action') !== null) {
396        hiddenInput = document.getElementById('action');
397      }
398      else {
399        hiddenInput = document.createElement('input');
400      }
401      hiddenInput.setAttribute('type', 'hidden');
402      hiddenInput.setAttribute('name', 'op');
403      hiddenInput.setAttribute('id', 'action');
404      hiddenInput.setAttribute('value', submitAction);
405      this.form.appendChild(hiddenInput);
406    },
407
408    /**
409     * Delegate to standard form submission (we do not submit via our javascript processing).
410     * @returns {*}
411     */
412    doStandardFormSubmit: function() {
413      // Disable the submit button to prevent repeated clicks
414      for (i = 0; i < this.submitButtons.length; ++i) {
415        this.submitButtons[i].setAttribute('disabled', true);
416      }
417      this.resetBillingFieldsRequiredForJQueryValidate();
418      return this.form.submit();
419    },
420
421    /**
422     * Validate a reCaptcha if it exists on the form.
423     * Ideally we would use grecaptcha.getResponse() but the reCaptcha is already
424     * render()ed by CiviCRM so we don't have clientID and can't be sure we are
425     * checking the reCaptcha that is on our form.
426     *
427     * @returns {boolean}
428     */
429    validateReCaptcha: function() {
430      if (typeof grecaptcha === 'undefined') {
431        // No reCaptcha library loaded
432        this.debugging(this.name, 'reCaptcha library not loaded');
433        return true;
434      }
435      if ($(this.form).find('[name=g-recaptcha-response]').length === 0) {
436        // no reCaptcha on form - we check this first because there could be reCaptcha on another form on the same page that we don't want to validate
437        this.debugging(this.name, 'no reCaptcha on form');
438        return true;
439      }
440      if ($(this.form).find('[name=g-recaptcha-response]').val().length > 0) {
441        // We can't use grecaptcha.getResponse because there might be multiple reCaptchas on the page and we might not be the first one.
442        this.debugging(this.name, 'recaptcha is valid');
443        return true;
444      }
445      this.debugging(this.name, 'recaptcha active and not valid');
446      $('div#card-errors').hide();
447      this.swalFire({
448        icon: 'warning',
449        text: '',
450        title: ts('Please complete the reCaptcha')
451      }, '.recaptcha-section', true);
452      this.triggerEvent('crmBillingFormNotValid');
453      this.form.dataset.submitted = 'false';
454      return false;
455    },
456
457    /**
458     * Validate if the discount code has been applied or the text-field is empty
459     *
460     * @returns {boolean}
461     */
462    validateCiviDiscount: function() {
463      // cividiscount: when a code is applied it stays in the text-field
464      // If it is valid discount-applied attribute is 1, otherwise it's undefined.
465      // Logic: If we have a discountcode field, text in the discountcode field and the discount-applied attribute is not set do not submit form
466      if ($('input#discountcode').length && ($('input#discountcode').val().length > 0) && ($('input#discountcode').attr('discount-applied') != 1)) {
467        this.debugging(this.name,'Discount Code Entered but not applied');
468        this.swalFire({
469          icon: 'error',
470          text: ts('Please apply the Discount Code or clear the Discount Code text-field'),
471          title: ''
472        }, '#crm-container', true);
473        this.triggerEvent('crmBillingFormNotValid');
474        this.form.dataset.submitted = 'false';
475        return false;
476      }
477      return true;
478    },
479
480    /**
481     * Check if form is valid (required fields filled in etc).
482     * @returns {boolean}
483     */
484    validateForm: function() {
485      if (($(this.form).valid() === false) || $(this.form).data('crmBillingFormValid') === false) {
486        this.debugging(this.name, 'Form not valid');
487        $('div#card-errors').hide();
488        CRM.payment.swalFire({
489          icon: 'error',
490          text: ts('Please check and fill in all required fields!'),
491          title: ''
492        }, '#crm-container', true);
493        CRM.payment.triggerEvent('crmBillingFormNotValid');
494        CRM.payment.form.dataset.submitted = 'false';
495        return false;
496      }
497      return true;
498    },
499
500    /**
501     * Find submit buttons which should not submit payment
502     */
503    addHandlerNonPaymentSubmitButtons: function() {
504      // Find submit buttons which should not submit payment
505      var nonPaymentSubmitButtons = this.form.querySelectorAll('[type="submit"][formnovalidate="1"], ' +
506        '[type="submit"][formnovalidate="formnovalidate"], ' +
507        '[type="submit"].cancel, ' +
508        '[type="submit"].webform-previous'), i;
509      for (i = 0; i < nonPaymentSubmitButtons.length; ++i) {
510        nonPaymentSubmitButtons[i].addEventListener('click', submitDontProcess(nonPaymentSubmitButtons[i]));
511      }
512
513      function submitDontProcess(element) {
514        CRM.payment.debugging(CRM.payment.scriptName, 'adding submitdontprocess: ' + element.id);
515        CRM.payment.form.dataset.submitdontprocess = 'true';
516      }
517    },
518
519    /**
520     * This adds handling for the CiviDiscount extension "apply" button.
521     * A better way should really be found.
522     */
523    addSupportForCiviDiscount: function() {
524      // Add a keypress handler to set flag if enter is pressed
525      var cividiscountElements = this.form.querySelectorAll('input#discountcode');
526      var cividiscountHandleKeydown = function(event) {
527        if (event.code === 'Enter') {
528          event.preventDefault();
529          CRM.payment.debugging(this.name, 'adding submitdontprocess');
530          CRM.payment.form.dataset.submitdontprocess = 'true';
531        }
532      };
533
534      for (i = 0; i < cividiscountElements.length; ++i) {
535        cividiscountElements[i].addEventListener('keydown', cividiscountHandleKeydown);
536      }
537    },
538
539    /**
540     * Display an error for the payment element
541     *
542     * @param {string} errorMessage - the error string
543     * @param {boolean} notify - whether to popup a notification as well as
544     *   display on the form.
545     */
546    displayError: function(errorMessage, notify) {
547      // Display error.message in your UI.
548      this.debugging(this.name, 'error: ' + errorMessage);
549      // Inform the user if there was an error
550      var errorElement = document.getElementById('card-errors');
551      errorElement.style.display = 'block';
552      errorElement.textContent = errorMessage;
553      this.form.dataset.submitted = 'false';
554      if (this.submitButtons !== null) {
555        for (i = 0; i < this.submitButtons.length; ++i) {
556          this.submitButtons[i].removeAttribute('disabled');
557        }
558      }
559      this.triggerEvent('crmBillingFormNotValid');
560      if (notify) {
561        this.swalClose();
562        CRM.payment.swalFire({
563          icon: 'error',
564          text: errorMessage,
565          title: ''
566        }, '#card-element', true);
567      }
568    },
569
570    /**
571     * Wrapper around Swal.fire()
572     * @param {array} parameters
573     * @param {string} scrollToElement
574     * @param {boolean} fallBackToAlert
575     */
576    swalFire: function(parameters, scrollToElement, fallBackToAlert) {
577      if (typeof Swal === 'function') {
578        if (scrollToElement.length > 0) {
579          parameters.didClose = function() { window.scrollTo($(scrollToElement).position()); };
580        }
581        Swal.fire(parameters);
582      }
583      else if (fallBackToAlert) {
584        window.alert(parameters.title + ' ' + parameters.text);
585      }
586    },
587
588    /**
589     * Wrapper around Swal.close()
590     */
591    swalClose: function() {
592      if (typeof Swal === 'function') {
593        Swal.close();
594      }
595    },
596
597    /**
598     * Trigger a jQuery event
599     * @param {string} event
600     */
601    triggerEvent: function(event, scriptName) {
602      var triggerNow = true;
603      if (typeof scriptName !== 'undefined') {
604        if (event === 'crmBillingFormReloadComplete') {
605          this.scripts[scriptName].reloadComplete = true;
606          $.each(CRM.payment.scripts, function(scriptName,scriptObject) {
607            if (scriptObject.reloadComplete !== true) {
608              triggerNow = false;
609              return;
610            }
611          });
612        }
613      }
614
615      if (triggerNow) {
616        this.debugging((typeof scriptName !== 'undefined') ? scriptName : this.name, 'Firing Event: ' + event);
617        $(this.form).trigger(event);
618      }
619      else {
620        this.debugging((typeof scriptName !== 'undefined') ? scriptName : this.name, 'Waiting for other scripts (' + event + ')');
621      }
622    },
623
624    /**
625     * This should be called as soon as a script is executed so CRM.payment can handle multiple scripts on the DOM
626     *
627     * @param scriptName
628     */
629    registerScript: function(scriptName) {
630      this.scripts[scriptName] = { reloadComplete: false };
631    },
632
633    /**
634     * Output debug information
635     * @param {string} scriptName
636     * @param {string} errorCode
637     */
638    debugging: function(scriptName, errorCode) {
639      if ((typeof(CRM.vars.payment) !== 'undefined') && (Boolean(CRM.vars.payment.jsDebug) === true)) {
640        console.log(new Date().toISOString() + ' ' + scriptName + ': ' + errorCode);
641      }
642    }
643
644  };
645
646  if (typeof CRM.payment === 'undefined') {
647    CRM.payment = payment;
648  }
649  else {
650    if (CRM.payment.hasOwnProperty('scriptName') && (CRM.payment.scriptName === 'CRM.payment')) {
651      return;
652    }
653    if (CRM.payment.hasOwnProperty('getTotalAmount')) {
654      delete(payment.getTotalAmount);
655      payment.debugging(payment.name, 'Deferring to client getTotalAmount function');
656    }
657    $.extend(CRM.payment, payment);
658  }
659
660  document.addEventListener('DOMContentLoaded', function() {
661    CRM.payment.debugging('CRM.payment', 'loaded via DOMContentLoaded');
662    CRM.payment.getBillingForm();
663  });
664
665  // Re-prep form when we've loaded a new payproc via ajax or via webform
666  $(document).ajaxComplete(function(event, xhr, settings) {
667    // /civicrm/payment/form? occurs when a payproc is selected on page
668    // /civicrm/contact/view/participant occurs when payproc is first loaded on event credit card payment
669    // On wordpress these are urlencoded
670    if (CRM.payment.isAJAXPaymentForm(settings.url)) {
671      CRM.payment.debugging('CRM.payment', 'triggered via ajax');
672      CRM.payment.getBillingForm();
673      // This resets the reload status of all scripts when CRM.payment is reloaded
674      $.each(CRM.payment.scripts, function(scriptName,scriptObject) {
675        CRM.payment.scripts[scriptName].reloadComplete = false;
676      });
677    }
678  });
679
680}(CRM.$, CRM.ts('mjwshared')));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.