PageSourceSearch

https://podcastguestacademy.com/wp-content/plugins/memberpress/app/gateways/stripe/form.js?ver=1.14.1

js podcastguestacademy.com collected 2026-10-06 18:13:44 UTC 20,368 bytes, 645 lines download raw bytes

1(function ($) {
2  $(document).ready(function() {
3    $('.mepr-signup-form, #mepr-stripe-payment-form').each(function () {
4      new MeprStripeForm(this);
5    });
6  });
7
8  /**
9   * The MemberPress Stripe form class
10   *
11   * @constructor
12   * @param {HTMLFormElement} form
13   */
14  function MeprStripeForm (form) {
15    this.form = form;
16    this.$form = $(form);
17    this.isSpc = this.$form.hasClass('mepr-signup-form');
18    this.paymentMethods = [];
19    this.selectedPaymentMethod = null;
20    this.submitting = false;
21    this.updateBillingDetailsTimeout = null;
22    this.initPaymentMethods();
23    this.$form.on('submit', $.proxy(this.handleSubmit, this));
24    this.$form.on('meprAfterCheckoutStateUpdated', $.proxy(this.updateElements, this));
25    this.$form.find('input[name="user_email"]').on('change', $.proxy(this.updateBillingDetails, this));
26  }
27
28  /**
29   * Initialize Stripe elements
30   */
31  MeprStripeForm.prototype.initPaymentMethods = function () {
32    var self = this,
33        templateOutdated = false;
34
35    self.$form.find('.mepr-stripe-card-element').each(function () {
36      var $cardElement = $(this),
37          $cardErrors = $cardElement.closest('.mepr-stripe-elements').find('.mepr-stripe-card-errors'),
38          stripe = Stripe($cardElement.data('stripe-public-key'), {
39            locale: $cardElement.data('locale-code').toLowerCase(),
40            apiVersion: MeprStripeGateway.api_version
41          }),
42          paymentMethodId = $cardElement.data('payment-method-id'),
43          elementsOptions = $cardElement.data('elements-options'),
44          wrapperSelector = self.isSpc ? '.mepr-payment-method' : '.mp_payment_form_wrapper',
45          $wrapper = $cardElement.closest(wrapperSelector),
46          paymentMethod = {
47            id: paymentMethodId,
48            $cardElement: $cardElement,
49            $cardErrors: $cardErrors,
50            $wrapper: $wrapper,
51            stripe: stripe,
52            elements: null,
53            paymentElement: null,
54            paymentElementComplete: false
55          };
56
57      self.paymentMethods.push(paymentMethod);
58
59      if (elementsOptions) {
60        self.createElements(paymentMethod, elementsOptions);
61      } else if (self.isSpc) {
62        templateOutdated = true;
63      }
64    });
65
66    if (templateOutdated) {
67      self.$form.trigger('meprUpdateCheckoutState');
68      console.log('Your MemberPress form template is out of date, please update it to the latest version.');
69    }
70  };
71
72  /**
73   * Create an instance of Elements and a Payment Element
74   *
75   * @param {object} paymentMethod
76   * @param options
77   */
78  MeprStripeForm.prototype.createElements = function (paymentMethod, options) {
79    try {
80      var self = this,
81          applePayConfig = self.processApplePayConfig(options.applePay || null);
82
83      delete options.applePay;
84
85      // Snapshot the server-provided options for the submit-time staleness check,
86      // before the client-side Elements additions below.
87      paymentMethod.appliedElementsContext = JSON.stringify(options);
88
89      options = $.extend({
90        currency: MeprStripeGateway.currency,
91        appearance: MeprStripeGateway.elements_appearance
92      }, options);
93
94      paymentMethod.elements = paymentMethod.stripe.elements(options);
95      paymentMethod.applePayConfig = applePayConfig;
96
97      var paymentElementOptions = {
98        defaultValues: {
99          billingDetails: self.getBillingDetails(paymentMethod)
100        },
101        terms: MeprStripeGateway.payment_element_terms
102      };
103
104      if (applePayConfig) {
105        paymentElementOptions.applePay = applePayConfig;
106      }
107
108      paymentMethod.paymentElement = paymentMethod.elements.create('payment', paymentElementOptions);
109
110      self.attachPaymentElementHandlers(paymentMethod);
111
112      paymentMethod.paymentElement.mount(paymentMethod.$cardElement[0]);
113    } catch (e) {
114      paymentMethod.$cardErrors.html(e.message);
115      throw e;
116    }
117  };
118
119  /**
120   * Process an Apple Pay config from the server, converting trialDays to a Date for regularBilling.recurringPaymentStartDate.
121   *
122   * @param  {object|null} applePayConfig The Apple Pay config from the server.
123   * @return {object|null} The processed config ready for Stripe.
124   */
125  MeprStripeForm.prototype.processApplePayConfig = function (applePayConfig) {
126    if (!applePayConfig || !applePayConfig.recurringPaymentRequest) {
127      return null;
128    }
129
130    var request = applePayConfig.recurringPaymentRequest;
131
132    // Stripe's Payment Element handles paymentTiming internally and their docs don't include it.
133    if (request.regularBilling) {
134      delete request.regularBilling.paymentTiming;
135    }
136
137    if (request.trialDays) {
138      if (request.regularBilling) {
139        var trialEndDate = new Date();
140
141        trialEndDate.setDate(trialEndDate.getDate() + request.trialDays);
142        trialEndDate.setHours(0, 0, 0, 0);
143
144        request.regularBilling.recurringPaymentStartDate = trialEndDate;
145      }
146
147      delete request.trialDays;
148    }
149
150    return applePayConfig;
151  };
152
153  /**
154   * Attach event handlers to a payment element.
155   *
156   * @param {object} paymentMethod The payment method object.
157   */
158  MeprStripeForm.prototype.attachPaymentElementHandlers = function (paymentMethod) {
159    paymentMethod.paymentElement.on('loaderror', function (event) {
160      if (event.error) {
161        paymentMethod.$cardErrors.html(event.error.message || 'Failed to create Payment element');
162      }
163    });
164
165    paymentMethod.paymentElement.on('change', function (event) {
166      if (typeof event.complete === 'boolean') {
167        paymentMethod.paymentElementComplete = event.complete;
168      }
169
170      if (typeof event.value === 'object' && typeof event.value.type === 'string') {
171        paymentMethod.paymentMethodType = event.value.type;
172      } else {
173        paymentMethod.paymentMethodType = null;
174      }
175    });
176  };
177
178  /**
179   * Updates the Elements instance with new options
180   *
181   * @param {object} event
182   * @param {object} response
183   */
184  MeprStripeForm.prototype.updateElements = function (event, response) {
185    var self = this;
186
187    if (response.payment_required && response.elements_options) {
188      $.each(response.elements_options, function (paymentMethodId, options) {
189        var paymentMethod = self.getPaymentMethodById(paymentMethodId);
190
191        if (paymentMethod) {
192          if (paymentMethod.elements) {
193            var newApplePayConfig = self.processApplePayConfig(options.applePay || null);
194
195            delete options.applePay;
196
197            paymentMethod.elements.update(options);
198
199            // Snapshot the applied options for the submit-time staleness check.
200            paymentMethod.appliedElementsContext = JSON.stringify(options);
201
202            // Destroy and recreate the payment element when the Apple Pay config needs
203            // to be removed, since paymentElement.update() uses a shallow merge and
204            // cannot unset an existing Apple Pay config.
205            if (paymentMethod.applePayConfig && !newApplePayConfig) {
206              paymentMethod.applePayConfig = null;
207              paymentMethod.paymentElement.destroy();
208
209              paymentMethod.paymentElement = paymentMethod.elements.create('payment', {
210                defaultValues: {
211                  billingDetails: self.getBillingDetails(paymentMethod)
212                },
213                terms: MeprStripeGateway.payment_element_terms
214              });
215
216              self.attachPaymentElementHandlers(paymentMethod);
217
218              paymentMethod.paymentElement.mount(paymentMethod.$cardElement[0]);
219            } else {
220              paymentMethod.applePayConfig = newApplePayConfig;
221
222              if (newApplePayConfig) {
223                paymentMethod.paymentElement.update({ applePay: newApplePayConfig });
224              }
225            }
226          } else {
227            self.createElements(paymentMethod, options);
228          }
229        }
230      });
231    }
232
233    self.updateBillingDetails();
234  };
235
236  /**
237   * Update the billing details on each Payment Element in this form
238   */
239  MeprStripeForm.prototype.updateBillingDetails = function () {
240    var self = this;
241
242    clearTimeout(self.updateBillingDetailsTimeout);
243
244    self.updateBillingDetailsTimeout = setTimeout(function () {
245      for (var i = 0; i < self.paymentMethods.length; i++) {
246        var paymentMethod = self.paymentMethods[i];
247
248        if (paymentMethod.paymentElement) {
249          paymentMethod.paymentElement.update({
250            defaultValues: {
251              billingDetails: self.getBillingDetails(paymentMethod)
252            }
253          });
254        }
255      }
256    }, 50);
257  };
258
259  /**
260   * Handle the payment form submission
261   *
262   * @param {jQuery.Event} e
263   */
264  MeprStripeForm.prototype.handleSubmit = async function (e) {
265    var self = this;
266
267    if (self.$form.find('.mepr-payment-methods-wrapper').is(':hidden')) {
268      return;
269    }
270
271    self.selectedPaymentMethod = self.getSelectedPaymentMethod();
272
273    if (self.selectedPaymentMethod) {
274      e.preventDefault();
275
276      // Wait for an in-flight checkout state update, so the Payment Element is
277      // reconciled with the final order before confirming.
278      if (self.$form.data('meprCheckoutStateUpdating')) {
279        self.$form.data('meprSubmitWhenUpdated', true);
280        return;
281      }
282
283      if (!self.selectedPaymentMethod.paymentElementComplete) {
284        self.selectedPaymentMethod.$cardErrors.text(MeprStripeGateway.payment_information_incomplete);
285        self.allowResubmission();
286        return;
287      }
288
289      if (self.submitting) {
290        return;
291      }
292
293      self.submitting = true;
294      self.$form.find('.mepr-form-has-errors').hide();
295      self.$form.find('.mepr-submit').prop('disabled', true);
296      self.$form.find('.mepr-loading-gif').show();
297
298      self.selectedPaymentMethod.$cardErrors.html('');
299
300      const { error } = await self.selectedPaymentMethod.elements.submit();
301
302      if (error) {
303        this.handlePaymentError(error.message);
304        return;
305      }
306
307      self.processForm();
308    }
309  };
310
311  /**
312   * Get the data for a payment method by ID
313   *
314   * @param {string} paymentMethodId
315   * @return {object|null}
316   */
317  MeprStripeForm.prototype.getPaymentMethodById = function (paymentMethodId) {
318    for (var i = 0; i < this.paymentMethods.length; i++) {
319      if (this.paymentMethods[i].id === paymentMethodId) {
320        return this.paymentMethods[i];
321      }
322    }
323
324    return null;
325  };
326
327  /**
328   * Get the currently selected payment method data
329   *
330   * @return {object|null}
331   */
332  MeprStripeForm.prototype.getSelectedPaymentMethod = function () {
333    if (this.isSpc) {
334      return this.getPaymentMethodById(this.$form.find('input[name="mepr_payment_method"]:checked').val());
335    } else {
336      return this.paymentMethods.length ? this.paymentMethods[0] : null;
337    }
338  };
339
340  /**
341   * Get the billing details object to pass to Stripe
342   *
343   * @param  {object} paymentMethod The selected payment method.
344   * @return {object}
345   */
346  MeprStripeForm.prototype.getBillingDetails = function (paymentMethod) {
347    var self = this,
348        keys = {
349          line1: 'mepr-address-one',
350          line2: 'mepr-address-two',
351          city: 'mepr-address-city',
352          country: 'mepr-address-country',
353          state: 'mepr-address-state',
354          postal_code: 'mepr-address-zip'
355        },
356        email = '',
357        name = [],
358        address = {},
359        addressFieldsPresent = false,
360        details = {
361          address: {}
362        };
363
364    var $emailField = self.$form.find('input[name="user_email"]');
365
366    if ($emailField.length) {
367      email = $emailField.val();
368    } else {
369      email = paymentMethod.$cardElement.data('user-email');
370    }
371
372    if (email && email.length) {
373      details.email = email;
374    }
375
376    $.each(['user_first_name', 'user_last_name'], function (index, value) {
377      var $field = self.$form.find('input[name="' + value + '"]');
378
379      if ($field.length) {
380        var val = $field.val();
381
382        if (typeof val == 'string' && val.length) {
383          name.push(val);
384        }
385      }
386    });
387
388    if (name.length) {
389      details.name = name.join(' ');
390    } else {
391      $.each(['card-first-name', 'card-last-name'], function (index, value) {
392        var $field = paymentMethod.$wrapper.find('input[name="' + value + '"]');
393
394        if ($field.length) {
395          var val = $field.val();
396
397          if (typeof val == 'string' && val.length) {
398            name.push(val);
399          }
400        }
401      });
402
403      if (name.length) {
404        details.name = name.join(' ');
405      }
406    }
407
408    $.each(keys, function (key, value) {
409      var $field = self.$form.find('input[name="' + value + '"], select[name="' + value + '"]');
410
411      if ($field.length) {
412        var val = $field.val();
413
414        if (typeof val == 'string' && val.length) {
415          address[key] = val;
416        }
417
418        addressFieldsPresent = true;
419      }
420    });
421
422    if (addressFieldsPresent) {
423      details.address = address;
424    } else {
425      $.each(keys, function (key, value) {
426        var cardAddressKey = value.replace('mepr-', 'card-'),
427            $field = paymentMethod.$wrapper.find('input[name="' + cardAddressKey + '"]');
428
429        if ($field.length) {
430          var val = $field.val();
431
432          if (typeof val == 'string' && val.length) {
433            details.address[key] = val;
434          }
435        }
436      });
437    }
438
439    return details;
440  };
441
442  /**
443   * Allow the form to be submitted again
444   */
445  MeprStripeForm.prototype.allowResubmission = function () {
446    this.submitting = false;
447    this.$form.find('.mepr-submit').prop('disabled', false);
448    this.$form.find('.mepr-loading-gif').hide();
449    this.$form.find('.mepr-form-has-errors').show();
450    this.$form.find('.mepr-validation-error, .mepr-top-error').remove();
451  };
452
453  /**
454   * Handle form validation errors
455   *
456   * @param {array} errors The validation errors array
457   */
458  MeprStripeForm.prototype.handleValidationErrors = function (errors) {
459    // Allow the form to be submitted again
460    this.allowResubmission();
461
462    var topErrors = [];
463
464    for (var key in errors) {
465      if (errors.hasOwnProperty(key)) {
466        var $field = this.$form.find('[name="' + key + '"]').first(),
467            $label = $field.closest('.mp-form-row').find('.mp-form-label');
468
469        if ($.isNumeric(key) || !$label.length) {
470          topErrors.push(errors[key]);
471        } else {
472          $label.append($('<span class="mepr-validation-error">').html(errors[key]));
473        }
474      }
475    }
476
477    if (topErrors.length) {
478      var $list = $('<ul>'),
479          $wrap = $('<div class="mepr-top-error mepr_error">');
480
481      for (var i = 0; i < topErrors.length; i++) {
482        $list.append($('<li>').html(MeprStripeGateway.top_error.replace('%s', topErrors[i])));
483      }
484
485      $wrap.append($list).prependTo(this.$form);
486    }
487  };
488
489  /**
490   * Handle an error with the payment
491   *
492   * @param {string} error The error message to display
493   */
494  MeprStripeForm.prototype.handlePaymentError = function (error) {
495    // Allow the form to be submitted again
496    this.allowResubmission();
497
498    // Inform the user if there was an error
499    this.selectedPaymentMethod.$cardErrors.html(error);
500  };
501
502  /**
503   * Handle any SCA actions, and confirms the payment or card setup
504   *
505   * @param {string} action
506   * @param {string} clientSecret
507   * @param {string} returnUrl
508   */
509  MeprStripeForm.prototype.handleAction = async function (action, clientSecret, returnUrl) {
510    var self = this,
511        stripe = self.selectedPaymentMethod.stripe,
512        elements = self.selectedPaymentMethod.elements,
513        billingDetails = self.getBillingDetails(self.selectedPaymentMethod),
514        confirmParams = {
515          return_url: returnUrl,
516          payment_method_data: {
517            billing_details: billingDetails
518          }
519        };
520
521    if (self.selectedPaymentMethod.paymentMethodType === 'affirm') {
522      // Affirm seems to have issues if the billing address is provided
523      delete confirmParams.payment_method_data;
524    }
525
526    if (self.selectedPaymentMethod.paymentMethodType === 'afterpay_clearpay') {
527      // Afterpay/Clearpay require a shipping address
528      confirmParams.shipping = {
529        name: billingDetails.name || '',
530        address: billingDetails.address
531      };
532    }
533
534    const { error } = await stripe[action]({
535      elements: elements,
536      clientSecret: clientSecret,
537      confirmParams: confirmParams
538    });
539
540    if (error) {
541      self.handlePaymentError(error.message);
542    }
543  };
544
545  /**
546   * Process the form.
547   */
548  MeprStripeForm.prototype.processForm = function () {
549    var self = this,
550        formData = new FormData(self.form);
551
552    formData.append('action', 'mepr_process_' + (self.isSpc ? 'signup' : 'payment') + '_form');
553
554    // We don't want to hit our routes for processing the signup or payment forms
555    formData.delete('mepr_process_signup_form');
556    formData.delete('mepr_process_payment_form');
557
558    // Report the applied Elements options so the server can detect a stale
559    // checkout. The field is absent when the Elements were never created (for
560    // example, outdated form templates or cached scripts), in which case the
561    // server skips the reconciliation. The standalone payment form never reports
562    // a context: its render and submit inputs are the same stored transaction
563    // state, so the contexts cannot diverge there.
564    if (self.isSpc && self.selectedPaymentMethod.appliedElementsContext) {
565      formData.append('mepr_stripe_elements_context', self.selectedPaymentMethod.appliedElementsContext);
566    }
567
568    $.ajax({
569      type: 'POST',
570      url: MeprStripeGateway.ajax_url,
571      data: formData,
572      dataType: 'json',
573      cache: false,
574      processData: false,
575      contentType: false,
576      headers: {
577        'cache-control': 'no-cache'
578      }
579    })
580    .done(function (response, textStatus, jqXHR) {
581      if (response === null || typeof response !== 'object' || typeof response.success !== 'boolean') {
582        self.handlePaymentError(MeprStripeGateway.invalid_response_error);
583        self.debugCheckoutError({
584          status: jqXHR.status,
585          status_text: jqXHR.statusText,
586          response_text: jqXHR.responseText,
587          text_status: textStatus,
588          error_thrown: 'Response was null or not an object'
589        });
590      } else {
591        if (response.success) {
592            self.handleAction(response.data.action, response.data.client_secret, response.data.return_url);
593        } else {
594            if (response.data && response.data.checkout_state_changed) {
595                // Force a checkout-state refresh, which updates the invoice, the
596                // price string, and the Payment Element together, so the user can
597                // re-confirm the payment with the correct terms.
598                self.$form.trigger('meprUpdateCheckoutState', [true]);
599                self.handlePaymentError(MeprStripeGateway.checkout_state_changed);
600            } else if (response.data.errors) {
601                self.handleValidationErrors(response.data.errors);
602            } else {
603                self.handlePaymentError(response.data);
604            }
605        }
606      }
607    })
608    .fail(function (jqXHR, textStatus, errorThrown) {
609      if (jqXHR.status === 0) {
610        // Don't send a debug email for errors with status 0
611        self.handlePaymentError(MeprStripeGateway.error_please_try_again);
612      } else {
613        self.handlePaymentError(MeprStripeGateway.ajax_error);
614        self.debugCheckoutError({
615          status: jqXHR.status,
616          status_text: jqXHR.statusText,
617          response_text: jqXHR.responseText,
618          text_status: textStatus,
619          error_thrown: '' + errorThrown
620        });
621      }
622    });
623  };
624
625  /**
626   * Debug a critical checkout error
627   *
628   * @param {object} data
629   */
630  MeprStripeForm.prototype.debugCheckoutError = function (data) {
631    data.transaction_id = this.$form.find('input[name="mepr_transaction_id"]').val();
632    data.customer_email = this.$form.find('input[name="user_email"]').val();
633    data.checkout_token = this.$form.find('input[name="mepr_checkout_token"]').val();
634
635    $.ajax({
636      type: 'POST',
637      url: MeprStripeGateway.ajax_url,
638      dataType: 'json',
639      data: {
640        action: 'mepr_debug_checkout_error',
641        data: JSON.stringify(data)
642      }
643    });
644  };
645})(jQuery);

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.