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.