PageSourceSearch

https://www.ijvcanada.org/wp-content/uploads/civicrm/ext/mjwshared/js/crm.payment.js?r=k4Lj9en_CA

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