PageSourceSearch

https://www.ijvcanada.org/wp-content/uploads/civicrm/ext/com.drastikbydesign.stripe/js/civicrmStripe.js

js ijvcanada.org collected 2026-09-24 08:57:07 UTC 30,914 bytes, 810 lines download raw bytes

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.