1/*jshint esversion: 8 */ 2/** 3 * JS Integration between CiviCRM & Stripe. 4 */ 5(function($, ts) { 6 7 var script = { 8 name: 'stripe', 9 stripe: null, 10 elements: { 11 card: null, 12 }, 13 scriptLoading: false, 14 paymentProcessorID: null, 15 16 paymentData: { 17 clientSecret: null, 18 }, 19 20 /** 21 * Called when payment details have been entered and validated successfully 22 * 23 * @param {string} objectType 24 * @param {string} objectID 25 */ 26 async successHandler(objectType, objectID) { 27 script.debugging(objectType + ': success - submitting form'); 28 29 // Insert the token ID into the form so it gets submitted to the server 30 var hiddenInput = document.createElement('input'); 31 hiddenInput.setAttribute('type', 'hidden'); 32 hiddenInput.setAttribute('name', objectType); 33 hiddenInput.setAttribute('value', objectID); 34 CRM.payment.form.appendChild(hiddenInput); 35 36 CRM.payment.resetBillingFieldsRequiredForJQueryValidate(); 37 38 if (script.getReCAPTCHAToken()) { 39 let recaptcha = await script.reloadReCAPTCHA(); 40 41 // Insert the token ID into the form so it gets submitted to the server 42 var hiddenInput2 = document.createElement('input'); 43 hiddenInput2.setAttribute('type', 'hidden'); 44 hiddenInput2.setAttribute('name', 'captcha'); 45 hiddenInput2.setAttribute('value', script.getReCAPTCHAToken()); 46 CRM.payment.form.appendChild(hiddenInput2); 47 } 48 49 // Submit the form 50 CRM.payment.form.submit(); 51 52 }, 53 54 /** 55 * Get a list of jQuery elements for all possible Stripe elements that 56 * could be loaded 57 * 58 * @returns {{card: 59 * (*|jQuery|HTMLElement)}} 60 */ 61 getJQueryPaymentElements: function() { 62 return { 63 card: $('div#card-element'), 64 }; 65 }, 66 67 /** 68 * Hide any visible payment elements 69 */ 70 hideJQueryPaymentElements: function() { 71 var jQueryPaymentElements = script.getJQueryPaymentElements(); 72 for (var elementName in jQueryPaymentElements) { 73 var element = jQueryPaymentElements[elementName]; 74 element.hide(); 75 } 76 }, 77 78 /** 79 * Destroy any payment elements we have already created 80 */ 81 destroyPaymentElements: function() { 82 for (var elementName in script.elements) { 83 var element = script.elements[elementName]; 84 if (element !== null) { 85 script.debugging("destroying " + elementName + " element"); 86 element.destroy(); 87 script.elements[elementName] = null; 88 } 89 } 90 $('.is_recur-section #stripe-recurring-start-date').remove(); 91 }, 92 93 /** 94 * Check that payment elements are valid 95 * @returns {boolean} 96 */ 97 checkPaymentElementsAreValid: function() { 98 var jQueryPaymentElements = script.getJQueryPaymentElements(); 99 for (var elementName in jQueryPaymentElements) { 100 var element = jQueryPaymentElements[elementName]; 101 if ((element.length !== 0) && (element.children().length !== 0)) { 102 script.debugging(elementName + ' element found'); 103 return true; 104 } 105 } 106 script.debugging('no valid elements found'); 107 return false; 108 }, 109 110 /** 111 * Get the ReCAPTCHA token if available (function currently only implemented by formprotection) 112 * 113 * @returns {*|string} 114 */ 115 getReCAPTCHAToken: function() { 116 return (typeof getReCAPTCHAToken === 'function') ? getReCAPTCHAToken() : ''; 117 }, 118 119 /** 120 * Wrapper for formprotection reloadReCAPTCHA function 121 */ 122 /** 123 * 124 * @returns {promise} 125 */ 126 reloadReCAPTCHA: function() { 127 if (typeof reloadReCAPTCHA === 'function') { 128 return reloadReCAPTCHA(); 129 } 130 }, 131 132 /** 133 * Handle the "Card" submission to Stripe 134 * 135 * @param submitEvent 136 */ 137 async handleSubmitCard(submitEvent) { 138 script.debugging('handle submit card'); 139 let createPaymentMethodResult = await stripe.createPaymentMethod('card', script.elements.card); 140 if (createPaymentMethodResult.error) { 141 // Show error in payment form 142 CRM.payment.displayError(createPaymentMethodResult.error.message, true); 143 } 144 else { 145 // For recur, additional participants we do NOT know the final 146 // amount so must create a paymentMethod and only create the 147 // paymentIntent once the form is finally submitted. We should 148 // never get here with amount=0 as we should be doing a 149 // "nonStripeSubmit()" instead. This may become needed when we save 150 // cards 151 var totalAmount = CRM.payment.getTotalAmount(); 152 if (totalAmount) { 153 totalAmount = totalAmount.toFixed(2); 154 } 155 if (CRM.payment.getIsRecur() || (totalAmount === null)) { 156 try { 157 let paymentIntentProcessResponse = await CRM.api4('StripePaymentintent', 'processPublic', { 158 setup: true, 159 paymentMethodID: createPaymentMethodResult.paymentMethod.id, 160 paymentProcessorID: CRM.vars[script.name].id, 161 description: document.title, 162 csrfToken: CRM.vars[script.name].csrfToken, 163 extraData: CRM.payment.getBillingEmail() + CRM.payment.getBillingName(), 164 captcha: script.getReCAPTCHAToken() 165 }); 166 CRM.payment.swalClose();
167 CRM.payment.debugging(script.name, 'StripePaymentintent.Process done (setupIntent)'); 168 if (paymentIntentProcessResponse.status === 'requires_action') { 169 // Use Stripe.js to handle a pending card action (eg. 3d-secure) 170 CRM.payment.debugging(script.name, 'card action required (setupIntent)'); 171 script.paymentData.clientSecret = paymentIntentProcessResponse.client_secret; 172 173 if (typeof Swal === 'function') { 174 // Display a message indicating 0 amount display on the 3DS verification popup is normal. 175 const handle3DSecureAuthentication = async () => { 176 let zero_amount = CRM.formatMoney(0); 177 Swal.fire({ 178 title: ts('3D Secure Authentication'), 179 text: ts("The next step will complete your 3D Secure authentification. Don't worry if the amount displayed is %1, it's just an authorisation for your recurring payments. Please click continue to proceed.", { 1: zero_amount }), 180 icon: 'success', 181 confirmButtonText: 'Continue' 182 }).then(async (result) => { 183 if (result.isConfirmed) { 184 let cardActionResult = await stripe.confirmCardSetup(script.paymentData.clientSecret); 185 if (cardActionResult.error) { 186 // Show error in payment form 187 CRM.payment.displayError(cardActionResult.error.message, true); 188 } else { 189 // The card action has been handled 190 // The PaymentIntent can be confirmed again on the server 191 script.successHandler('setupIntentID', cardActionResult.setupIntent.id); 192 } 193 } 194 }); 195 }; 196 await handle3DSecureAuthentication(); 197 } else { 198 let cardActionResult = await stripe.confirmCardSetup(script.paymentData.clientSecret); 199 if (cardActionResult.error) { 200 // Show error in payment form 201 CRM.payment.displayError(cardActionResult.error.message, true); 202 } else { 203 // The card action has been handled 204 // The PaymentIntent can be confirmed again on the server 205 script.successHandler('setupIntentID', cardActionResult.setupIntent.id); 206 } 207 } 208 } 209 // WordPress returns a 200 not 500 HTTP response because headers are sent beforehand. So we check for an error code in the response. 210 else if (paymentIntentProcessResponse.hasOwnProperty('error_code')) { 211 script.stripePaymentIntentProcessFail(paymentIntentProcessResponse); 212 } 213 else { 214 // All good, we can submit the form 215 script.successHandler('paymentMethodID', createPaymentMethodResult.paymentMethod.id); 216 } 217 } catch (failObject) { 218 script.stripePaymentIntentProcessFail(failObject); 219 } 220 } 221 else { 222 // Send paymentMethod.id to server 223 CRM.payment.debugging(script.name, 'Waiting for pre-auth'); 224 CRM.payment.swalFire({ 225 title: ts('Please wait'), 226 text: ts(' while we pre-authorize your card...'), 227 allowOutsideClick: false, 228 willOpen: function () { 229 Swal.showLoading(Swal.getConfirmButton()); 230 } 231 }, '', false); 232 233 try { 234 let processParams = { 235 // payment_method_id: createPaymentMethodResult.paymentMethod.id, 236 paymentMethodID: createPaymentMethodResult.paymentMethod.id, 237 amount: CRM.payment.getTotalAmount().toFixed(2), 238 currency: CRM.payment.getCurrency(CRM.vars[script.name].currency), 239 paymentProcessorID: CRM.vars[script.name].id, 240 description: document.title, 241 extraData: CRM.payment.getBillingEmail() + CRM.payment.getBillingName(), 242 }; 243 244 let processMode = 'Public'; 245 if (CRM.vars.stripe.moto && document.getElementById('enableMOTO').checked) { 246 processMode = 'MOTO'; 247 } 248 else { 249 processParams.csrfToken = CRM.vars[script.name].csrfToken; 250 processParams.captcha = script.getReCAPTCHAToken(); 251 } 252 253 let paymentIntentProcessResponse = await CRM.api4('StripePaymentintent', 'process'
253+ processMode, processParams); 254 CRM.payment.swalClose(); 255 CRM.payment.debugging(script.name, 'StripePaymentintent.Process done (paymentIntent)'); 256 if (paymentIntentProcessResponse.requires_action) { 257 // Use Stripe.js to handle a pending card action (eg. 3d-secure) 258 script.paymentData.clientSecret = paymentIntentProcessResponse.paymentIntentClientSecret; 259 let cardActionResult = await stripe.handleCardAction(script.paymentData.clientSecret); 260 if (cardActionResult.error) { 261 // Show error in payment form 262 CRM.payment.displayError(cardActionResult.error.message, true); 263 script.reloadReCAPTCHA(); 264 } 265 else { 266 // The card action has been handled 267 // The PaymentIntent can be confirmed again on the 268 // server 269 script.successHandler('paymentIntentID', cardActionResult.paymentIntent.id); 270 } 271 } 272 // WordPress returns a 200 not 500 HTTP response because headers are sent beforehand. So we check for an error code in the response. 273 else if (paymentIntentProcessResponse.hasOwnProperty('error_code')) { 274 script.stripePaymentIntentProcessFail(paymentIntentProcessResponse); 275 } 276 else { 277 // All good, we can submit the form 278 script.successHandler('paymentIntentID', paymentIntentProcessResponse.paymentIntent.id); 279 } 280 } catch (failObject) { 281 script.stripePaymentIntentProcessFail(failObject); 282 } 283 } 284 } 285 }, 286 287 /** 288 * Display a helpful error message if call to StripePaymentintent.Process 289 * fails 290 * @param {object} failObject 291 * @returns {boolean} 292 */ 293 stripePaymentIntentProcessFail: function(failObject) { 294 var error = ts('Unknown error'); 295 if (typeof failObject !== 'undefined') { 296 if (failObject.hasOwnProperty('error_message')) { 297 // From an API4 exception 298 error = failObject.error_message; 299 } 300 else 301 if (failObject.hasOwnProperty('statusText') && (failObject.statusText !== 'OK')) { 302 // A PHP exit can return 200 "OK" but we don't want to display "OK" as the error! 303 if (failObject.statusText === 'parsererror') { 304 error = ts('Configuration error - unable to process paymentIntent'); 305 } 306 else { 307 error = failObject.statusText; 308 } 309 } 310 } 311 CRM.payment.displayError(error, true); 312 script.reloadReCAPTCHA(); 313 return true; 314 }, 315 316 /** 317 * Payment processor is not Stripe - cleanup 318 */ 319 notScriptProcessor: function() { 320 script.debugging('New payment processor is not ' + script.name + ', clearing CRM.vars.' + script.name); 321 script.destroyPaymentElements(); 322 delete (CRM.vars[script.name]); 323 $(CRM.payment.getBillingSubmit()).show(); 324 CRM.payment.resetBillingFieldsRequiredForJQueryValidate(); 325 }, 326 327 /** 328 * Check environment and trigger loadBillingBlock() 329 */ 330 checkAndLoad: function() { 331 if (typeof CRM.vars[script.name] === 'undefined') { 332 script.debugging('CRM.vars.' + script.name + ' not defined!'); 333 return; 334 } 335 336 if (typeof Stripe === 'undefined') { 337 if (script.scriptLoading) { 338 return; 339 } 340 script.scriptLoading = true; 341 script.debugging('Stripe.js is not loaded!'); 342 343 $.ajax({ 344 url: 'https://js.stripe.com/v3', 345 dataType: 'script', 346 cache: true, 347 timeout: 5000, 348 crossDomain: true 349 }) 350 .done(function(data) { 351 script.scriptLoading = false; 352 script.debugging("Script loaded and executed."); 353 script.loadBillingBlock(); 354 }) 355 .fail(function() { 356 script.scriptLoading = false; 357 script.debugging('Failed to load Stripe.js'); 358 script.triggerEventCrmBillingFormReloadFailed(); 359 }); 360 } 361 else { 362 script.loadBillingBlock(); 363 } 364 }, 365 366 /** 367 * This actually loads the billingBlock and the chosen Stripe element 368 */ 369 loadBillingBlock: function() { 370 script.debugging('loadBillingBlock'); 371 372 // When switching payment processors we need to make sure these are empty 373 script.paymentData.clientSecret = null; 374 375 var oldPaymentProcessorID = script.paymentProcessorID; 376 script.paymentProcessorID = CRM.payment.getPaymentProcessorSelectorValue(); 377 script.debugging('payment processor old: ' + oldPaymentProcessorID + ' new: ' + script.paymentProcessorID + ' id: ' + CRM.vars[script.name].id); 378 if ((script.paymentProcessorID !== null) && (script.paymentProcessorID !== parseInt(CRM.vars[script.name].id))) { 379 script.debugging('not ' + script.name); 380 return script.notScriptProcessor(); 381 } 382 383 script.debugging('New ID: ' + CRM.vars[script.name].id + ' pubKey: ' + CRM.vars[script.name].publishableKey); 384 385 if (CRM.vars[script.name].publishableKey === '') { 386 script.debugging('Invalid payment configuration!'); 387 CRM.payment.swalFire({ 388 icon: 'error', 389 text: '', 390 title: ts('Invalid payment configuration!') 391 }, '', true); 392 } 393
394 stripe = Stripe(CRM.vars[script.name].publishableKey); 395 396 script.debugging('locale: ' + CRM.vars[script.name].locale); 397 var stripeElements = stripe.elements({locale: CRM.vars[script.name].locale}); 398 399 // Load card element 400 script.createElementCard(stripeElements); 401 }, 402 403 /** 404 * This is called once Stripe elements have finished loading onto the form 405 */ 406 doAfterStripeElementsHaveLoaded: function() { 407 CRM.payment.setBillingFieldsRequiredForJQueryValidate(); 408 409 // If another submit button on the form is pressed (eg. apply discount) 410 // add a flag that we can set to stop payment submission 411 CRM.payment.form.dataset.submitdontprocess = 'false'; 412 413 CRM.payment.addHandlerNonPaymentSubmitButtons(); 414 415 for (i = 0; i < CRM.payment.submitButtons.length; ++i) { 416 CRM.payment.submitButtons[i].addEventListener('click', submitButtonClick); 417 } 418 419 function submitButtonClick(clickEvent) { 420 // Take over the click function of the form. 421 if (typeof CRM.vars[script.name] === 'undefined') { 422 // Do nothing. Not our payment processor 423 return false; 424 } 425 script.debugging('clearing submitdontprocess'); 426 CRM.payment.form.dataset.submitdontprocess = 'false'; 427 428 // Run through our own submit, that executes Stripe submission if 429 // appropriate for this submit. 430 return script.submit(clickEvent); 431 } 432 433 // Remove the onclick attribute added by CiviCRM. 434 for (i = 0; i < CRM.payment.submitButtons.length; ++i) { 435 CRM.payment.submitButtons[i].removeAttribute('onclick'); 436 } 437 438 CRM.payment.addSupportForCiviDiscount(); 439 440 // For CiviCRM Webforms. 441 if (CRM.payment.getIsDrupalWebform()) { 442 // We need the action field for back/submit to work and redirect properly after submission 443 444 $('[type=submit]').click(function() { 445 CRM.payment.addDrupalWebformActionElement(this.value); 446 }); 447 // If enter pressed, use our submit function 448 CRM.payment.form.addEventListener('keydown', function (keydownEvent) { 449 if (keydownEvent.code === 'Enter') { 450 CRM.payment.addDrupalWebformActionElement(this.value); 451 script.submit(keydownEvent); 452 } 453 }); 454 455 $('#billingcheckbox:input').hide(); 456 $('label[for="billingcheckbox"]').hide(); 457 } 458 459 if (CRM.vars.stripe.moto) { 460 script.addMOTOPaymentCheckbox(); 461 } 462 463 if (script.checkPaymentElementsAreValid()) { 464 CRM.payment.triggerEvent('crmBillingFormReloadComplete', script.name); 465 CRM.payment.triggerEvent('crmStripeBillingFormReloadComplete', script.name); 466 } 467 else { 468 script.debugging('Failed to load payment elements'); 469 script.triggerEventCrmBillingFormReloadFailed(); 470 } 471 }, 472 473 addMOTOPaymentCheckbox: function() { 474 const div = document.createElement('div'); 475 div.id = 'crm-stripe-moto'; 476 const checkbox = document.createElement('input'); 477 checkbox.type = 'checkbox'; 478 checkbox.id = 'enableMOTO'; 479 checkbox.checked = true; 480 const label = document.createElement('label'); 481 label.htmlFor = 'enableMOTO'; 482 label.appendChild(document.createTextNode(ts('Enable MOTO for this transaction?'))); 483 div.appendChild(checkbox); 484 div.appendChild(label); 485 document.getElementById('crm-payment-js-billing-form-container').after(div); 486 }, 487 488 submit: function(submitEvent) { 489 submitEvent.preventDefault(); 490 script.debugging('submit handler'); 491 492 if (CRM.payment.form.dataset.submitted === 'true') { 493 return; 494 } 495 CRM.payment.form.dataset.submitted = 'true'; 496 497 if (!CRM.payment.validateCiviDiscount()) { 498 return false; 499 } 500 501 if (!CRM.payment.validateForm()) { 502 return false; 503 } 504 505 var cardError = CRM.$('#card-errors').text(); 506 if (CRM.$('#card-element.StripeElement--empty').length && (CRM.payment.getTotalAmount() !== 0.0)) { 507 script.debugging('card details not entered!'); 508 if (!cardError) { 509 cardError = ts('Please enter your card details'); 510 } 511 CRM.payment.swalFire({ 512 icon: 'warning', 513 text: '', 514 title: cardError 515 }, '#card-element', true); 516 CRM.payment.triggerEvent('crmBillingFormNotValid');
517 CRM.payment.form.dataset.submitted = 'false'; 518 return false; 519 } 520 521 if (CRM.$('#card-element.StripeElement--invalid').length) { 522 if (!cardError) { 523 cardError = ts('Please check your card details!'); 524 } 525 script.debugging('card details not valid!'); 526 CRM.payment.swalFire({ 527 icon: 'error', 528 text: '', 529 title: cardError 530 }, '#card-element', true); 531 CRM.payment.triggerEvent('crmBillingFormNotValid'); 532 CRM.payment.form.dataset.submitted = 'false'; 533 return false; 534 } 535 536 if (!CRM.payment.validateReCaptcha()) { 537 return false; 538 } 539 540 if (typeof CRM.vars[script.name] === 'undefined') { 541 script.debugging('Submitting - not a ' + script.name + ' processor'); 542 return true; 543 } 544 545 var scriptProcessorId = parseInt(CRM.vars[script.name].id); 546 var chosenProcessorId = null; 547 548 // Handle multiple payment options and Stripe not being chosen. 549 if (CRM.payment.getIsDrupalWebform()) { 550 // this element may or may not exist on the webform, but we are dealing with a single (stripe) processor enabled. 551 if (!$('input[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]').length) { 552 chosenProcessorId = scriptProcessorId; 553 } 554 else { 555 chosenProcessorId = parseInt(CRM.payment.form.querySelector('input[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]:checked').value); 556 } 557 } 558 else { 559 // Most forms have payment_processor-section but event registration has credit_card_info-section 560 if ((CRM.payment.form.querySelector(".crm-section.payment_processor-section") !== null) || 561 (CRM.payment.form.querySelector(".crm-section.credit_card_info-section") !== null)) { 562 scriptProcessorId = CRM.vars[script.name].id; 563 if (CRM.payment.form.querySelector('input[name="payment_processor_id"]:checked') !== null) { 564 chosenProcessorId = parseInt(CRM.payment.form.querySelector('input[name="payment_processor_id"]:checked').value); 565 } 566 } 567 } 568 569 // If any of these are true, we are not using the stripe processor: 570 // - Is the selected processor ID pay later (0) 571 // - Is the Stripe processor ID defined? 572 // - Is selected processor ID and stripe ID undefined? If we only have stripe ID, then there is only one (stripe) processor on the page 573 if ((chosenProcessorId === 0) || (scriptProcessorId === null) || 574 ((chosenProcessorId === null) && (scriptProcessorId === null))) { 575 script.debugging('Not a ' + script.name + ' transaction, or pay-later'); 576 return CRM.payment.doStandardFormSubmit(); 577 } 578 else { 579 script.debugging(script.name + ' is the selected payprocessor'); 580 } 581 582 // Don't handle submits generated by non-stripe processors 583 if (typeof CRM.vars[script.name].publishableKey === 'undefined') { 584 script.debugging('submit missing stripe-pub-key element or value'); 585 return true; 586 } 587 // Don't handle submits generated by the CiviDiscount button. 588 if (CRM.payment.form.dataset.submitdontprocess === 'true') { 589 script.debugging('non-payment submit detected - not submitting payment'); 590 return true; 591 } 592 593 if (CRM.payment.getIsDrupalWebform()) { 594 // If we have selected Stripe but amount is 0 we don't submit via Stripe 595 if ($('#billing-payment-block').is(':hidden')) { 596 script.debugging('no payment processor on webform'); 597 return true; 598 } 599 600 // If we have more than one processor (user-select) then we have a set of radio buttons: 601 var $processorFields = $('[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]'); 602 if ($processorFields.length) { 603 if ($processorFields.filter(':checked') 604 .val() === '0' || $processorFields.filter(':checked').val() === 0) { 605 script.debugging('no payment processor selected'); 606 return true; 607 } 608 } 609 } 610 611 var totalAmount = CRM.payment.getTotalAmount(); 612 if (totalAmount === 0.0) { 613 script.debugging("Total amount is 0"); 614 return CRM.payment.doStandardFormSubmit(); 615 } 616 617 // Disable the submit button to prevent repeated clicks 618 for (i = 0; i < CRM.payment.submitButtons.length; ++i) { 619 CRM.payment.submitButtons[i].setAttribute('disabled', true); 620 } 621 622 // When we click the stripe element we already have the elementType. 623 // But clicking an alternative submit button we have to work it out. 624 var elementType = 'card'; 625 if (submitEvent.hasOwnProperty(elementType)) { 626 elementType = submitEvent.elementType; 627 } 628 // Create a token when the form is submitted. 629 switch (elementType) { 630 case 'card': 631 script.handleSubmitCard(submitEvent); 632 break; 633 } 634 635 if ($('#stripe-recurring-start-date').is(':hidden')) { 636 $('#stripe-recurring-start-date').remove(); 637 } 638 639 return true; 640 }, 641 642 createElementCard: function(stripeElements) { 643 script.debugging('try to create card element'); 644 var style = { 645 base: { 646 fontSize: '1.1em', fontWeight: 'lighter' 647 }, 648 649 invalid: { 650 "::placeholder": { 651 color: "#E25950", 652 fontWeight: '500', 653 }, 654 color: "#E25950", 655 fontWeight: '500', 656 } 657 }; 658 659 var elementsCreateParams = {style: style, value: {}}; 660 661 var postCodeElement = document.getElementById('billing_postal_code-' + CRM.vars[script.name].billingAddressID); 662 if (postCodeElement) { 663 var postCode = document.getElementById('billing_postal_code-' + CRM.vars[script.name].billingAddressID).value; 664 script.debugging('existing postcode: ' + postCode); 665 elementsCreateParams.value.postalCode = postCode;
666 } 667 668 // disableLink - https://lab.civicrm.org/extensions/stripe/-/issues/458 669 if (CRM.vars[script.name].disablelink) { 670 elementsCreateParams.disableLink = true; 671 } 672 673 // Regulation (EU) 2015/751 requires businesses in the European Economic Area (EEA) to honor customersâ card brand choice for co-badged cards 674 // https://docs.stripe.com/co-badged-cards-compliance?type=web-elements&ui=card-element#selecting-a-default-network 675 elementsCreateParams.hideIcon = false; 676 elementsCreateParams.preferredNetwork = ['cartes_bancaires', 'visa', 'mastercard']; 677 678 // Cleanup any classes leftover from previous switching payment processors 679 script.getJQueryPaymentElements().card.removeClass(); 680 // Create an instance of the card Element. 681 script.elements.card = stripeElements.create('card', elementsCreateParams); 682 script.elements.card.mount('#card-element'); 683 script.debugging("created new card element", script.elements.card); 684 685 if (postCodeElement) { 686 // Hide the CiviCRM postcode field so it will still be submitted but will contain the value set in the stripe card-element. 687 postCodeElement.setAttribute('readonly', true); 688 } 689 690 // All containers start as display: none and are enabled on demand 691 document.getElementById('card-element').style.display = 'block'; 692 693 script.elements.card.addEventListener('change', function (event) { 694 script.cardElementChanged(event); 695 }); 696 697 script.doAfterStripeElementsHaveLoaded(); 698 }, 699 700 cardElementChanged: function(event) { 701 if (event.empty) { 702 $('div#card-errors').hide(); 703 } 704 else 705 if (event.error) { 706 CRM.payment.displayError(event.error.message, false); 707 } 708 else 709 if (event.complete) { 710 $('div#card-errors').hide(); 711 var postCodeElement = document.getElementById('billing_postal_code-' + CRM.vars[script.name].billingAddressID); 712 if (postCodeElement) { 713 postCodeElement.value = event.value.postalCode; 714 } 715 } 716 }, 717 718 /** 719 * Output debug information 720 * @param {string} errorCode 721 */ 722 debugging: function(errorCode) { 723 CRM.payment.debugging(script.name, errorCode); 724 }, 725 726 /** 727 * Trigger the crmBillingFormReloadFailed event and notify the user 728 */ 729 triggerEventCrmBillingFormReloadFailed: function() { 730 CRM.payment.triggerEvent('crmBillingFormReloadFailed'); 731 script.hideJQueryPaymentElements();
732 CRM.payment.displayError(ts('Could not load payment element - Is there a problem with your network connection?'), true); 733 } 734 }; 735 736 // Disable the browser "Leave Page Alert" which is triggered because we mess with the form submit function. 737 window.onbeforeunload = null; 738 739 if (CRM.payment.hasOwnProperty(script.name)) { 740 return; 741 } 742 743 // Currently this just flags that we've already loaded 744 var crmPaymentObject = {}; 745 crmPaymentObject[script.name] = script; 746 $.extend(CRM.payment, crmPaymentObject); 747 748 CRM.payment.registerScript(script.name); 749 750 // Re-prep form when we've loaded a new payproc via ajax or via webform 751 $(document).ajaxComplete(function (event, xhr, settings) { 752 if (CRM.payment.isAJAXPaymentForm(settings.url)) { 753 CRM.payment.debugging(script.name, 'triggered via ajax'); 754 load(); 755 } 756 }); 757 758 document.addEventListener('DOMContentLoaded', function() { 759 CRM.payment.debugging(script.name, 'DOMContentLoaded'); 760 load(); 761 }); 762 763 /** 764 * Called on every load of this script (whether billingblock loaded via AJAX 765 * or DOMContentLoaded) 766 */ 767 function load() { 768 if (window.civicrmStripeHandleReload) { 769 // Call existing instance of this, instead of making new one.
770 CRM.payment.debugging(script.name, "calling existing HandleReload."); 771 window.civicrmStripeHandleReload(); 772 } 773 } 774 775 /** 776 * This function boots the UI. 777 */ 778 window.civicrmStripeHandleReload = function() { 779 CRM.payment.scriptName = script.name; 780 CRM.payment.debugging(script.name, 'HandleReload'); 781 782 // Get the form containing payment details 783 CRM.payment.form = CRM.payment.getBillingForm(); 784 if (typeof CRM.payment.form.length === 'undefined' || CRM.payment.form.length === 0) { 785 CRM.payment.debugging(script.name, 'No billing form!'); 786 return; 787 } 788 789 // If we are reloading start with the form submit buttons visible 790 // They may get hidden later depending on the element type. 791 $(CRM.payment.getBillingSubmit()).show(); 792 793 // Load Stripe onto the form. 794 var cardElement = document.getElementById('card-element'); 795 if ((typeof cardElement !== 'undefined') && (cardElement)) { 796 if (!cardElement.children.length) { 797 CRM.payment.debugging(script.name, 'checkAndLoad from document.ready'); 798 script.checkAndLoad(); 799 } 800 else { 801 CRM.payment.debugging(script.name, 'already loaded'); 802 } 803 } 804 else { 805 script.notScriptProcessor(); 806 CRM.payment.triggerEvent('crmBillingFormReloadComplete', script.name); 807 } 808 }; 809 810}(CRM.$, CRM.ts('com.drastikbydesign.stripe')));
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.