PageSourceSearch

https://www.weatherlink.com/js/10.0.7.4-c92a18e7/accountSubscription/makePayment.js

js weatherlink.com collected 2026-09-24 08:53:49 UTC 71,929 bytes, 1,931 lines download raw bytes

1/**
2 * Created by Narinder Gaheer on 11/01/2017.
3 */
4var wl = wl || {};
5
6wl.app.module('oPayments', function (oPayments, oPaymentsApp, Backbone, Marionette, $) {
7
8  //disable cache for webview
9  if (location.pathname.indexOf('mobile') !== -1) {
10    $.ajaxSetup({ cache: false });
11  }
12
13  var PaymentModel = Backbone.Model.extend({
14    defaults: {
15      error: undefined,
16      subscriptionEndDate: dayjs(),
17      subscrOnTrialPeriod: false,
18      subscrPricePerMonth: 0,
19      company: undefined,
20      proPlusUpgradeAmount: 0,
21      paymentDetails: null,
22      cardExpired: false,
23      useSavedCard: true,
24      amountDue: 0,
25      taxDue: 0,
26      taxRate: 0,
27      beforeTax: true,
28      existingUpgrades: 1,
29      usedUpgrades: 0,
30      existingUpgradesAmountDue: 0,
31      newUgradesAmountDue: 0,
32      totalAmountDue: 0,
33      couponDiscount: null,
34      validatedAddresses: [],
35      subscriptionTerms: [],
36      newSubscriptionTermId: 1,
37      currentSubscrLengthInMonths: 12,
38      lengthInMonths: 12,
39      showUpgradesDetected: false,
40      selectedAddressIndex: 0,
41      cardholderName: null,
42      cardNumber: null,
43      expiration: null,
44      cvc: null,
45      countryId: null,
46      countryName: undefined,
47      countryCode: null,
48      address1: null,
49      city: null,
50      state: null,
51      zipCode: null,
52      couponCode: null,
53      requestTokenForm: undefined,
54      newSubscrTypeId: null,
55      userSubscriptionTypeId: null,
56      userSubscriptionTypeName: null,
57      expDate: null,
58      proPrice: 47.4,
59      proPlusPrice: 107.4,
60      subscriptionTypes: [],
61      selectedServicePlan: null,
62      trialMode: false,
63      newUpgradesAdjustmentAmount: 0,
64      numNewUpgrades: 1,
65      upgradeType: null,
66      upgradeSystemId: 0,
67      newArchivalRate: null,
68      standalone: false,
69      renewal: false,
70      taxLabel: 'Tax',
71      taxLabelInDesc: 'tax',
72      reactivateDevice: false,
73      customerCurrency: 'USD',
74      totalAmountCustomerCurrency: null,
75      paymentIntentId: null,
76      clientSecret: null
77    }
78  });
79
80  oPayments.checkIfCardExpired = function (paymentDetails) {
81    var expired = false;
82    if (paymentDetails) {
83      var expMonth = paymentDetails.expMonth;
84      var expYear = paymentDetails.expYear;
85      var currentMonth = dayjs().month() + 1;
86      var currentYear = dayjs().year()
87      expired = expYear < currentYear || (expYear === currentYear &&
88          expMonth < currentMonth);
89    }
90    return expired;
91  }
92
93  oPayments.sharedModel = new PaymentModel();
94
95  oPayments.MakePaymentView = Marionette.LayoutView.extend({
96    trialMode: false,
97    model: oPayments.sharedModel,
98    regions: {
99      fullWidthRegion: '#full-width-region',
100      mainRegion: '#main-region',
101      detailsRegion: '#details-region'
102    },
103    childEvents: {
104      'billingAddress:render': function () {
105        this.showBillingAddressForm();
106      },
107      'paymentForm:render': function () {
108        this.showPaymentForm();
109      },
110      'detectedUpgrades:render': function () {
111        this.showDetectedUpgrades();
112      },
113      'detectedUpgradesOrPaymentForm:render': function () {
114        this.showDetectedUpgradesOrPaymentForm();
115      },
116      'resellerAssociated:render': function () {
117        this.showResellerAssociated();
118      },
119      'confirm-address:render': function () {
120        this.showConfirmAddress();
121      },
122      'payment-success:render': function () {
123        this.showPaymentSuccess();
124      },
125      'subscription-select:render': function () {
126        this.showSubscriptionSelect();
127      },
128      'comparison-chart:render': function () {
129        this.showComparisonChart();
130      },
131      'addMoreSensors:render': function () {
132        this.showAddMoreSensors();
133      },
134      'cardDetails:saved': function () {
135        // pass event to parent card details view
136        this.triggerMethod('cardDetails:saved');
137      }
138    },
139    initialize: function (options) {
140      if (options.isTrial) {
141        this.trialMode = (options.isTrial && options.isTrial === 'true') ?
142          true : false;
143      }
144      this.newSubscrType = options.subscrType;
145
146      this.model.set('upgradeSystemId', Number(options.upgradeSystemId));
147      this.model.set('newArchivalRate', Number(options.newArchivalRate));
148
149      //if passed 0 as numNewUpgrades param
150      //upgrade from pro to proplus without buying an additional upgrade
151      if (options.numNewUpgrades && !_.isNaN(Number(options.numNewUpgrades)) &&
152        this.newSubscrType === "ProPlus") {
153        this.model.set('numNewUpgrades', Number(options.numNewUpgrades))
154      }
155
156      //check if user has saved card
157      this.getCreditCardProfile();
158
159      this.checkIfAssociatedWithReseller().then(function (response) {
160        if (response && response.data) {
161          this.model.set('company', response.data);
162        }
163        this.checkIfAlreadySubscribed().then(function (response) {
164          if (response.data && response.data.subscription) {
165            this.model.set('subscrOnTrialPeriod', Boolean(
166              response.data
167                .subscription.isTrialPeriod));
168
169            var alreadySubscribed = this.newSubscrType == 'Pro' &&
170              response.data.subscriptionType.userSubscriptionTypeId ==
171              2;
172
173            alreadySubscribed = alreadySubscribed || (this.newSubscrType ==
174              'ProPlus' && response.data.subscriptionType.userSubscriptionTypeId ==
175              3);
176
177            if (response.data.proPlusUpgradeAmount) {
178              this.model.set('proPlusUpgradeAmount',
179                response.data.proPlusUpgradeAmount);
180              //this.model.set('totalAmountDue', response.data.proPlusUpgradeAmount);
181            }
182
183            this.model.set('existingUpgrades', response.data.subscription
184              .numDeviceUpgrades);
185            this.model.set('userSubscriptionTypeId', response.data
186              .subscription.userSubscriptionTypeId);
187            this.model.set('userSubscriptionTypeName', response.data
188              .subscriptionType.subscriptionTypeName);
189            this.model.set('newSubscriptionTermId', response.data
190              .subscription.subscriptionTermId);
191            //this.model.set('expDate', response.data.subscription.expirationDate);
192
193            var subscriptionEnd = dayjs(response.data.subscription
194              .subscriptionEnd);
195            this.model.set('subscriptionEndDate', subscriptionEnd
196              .format('MMMM Do, YYYY'));
197            var now = dayjs();
198
199            //if not passed in url
200            if (!this.newSubscrType) {
201              this.newSubscrType = this.model.get('userSubscriptionTypeId') === 3
202                ? 'ProPlus' : 'Pro';
203            }
204
205            //if user is on Pro+ can't set Pro
206            if (this.newSubscrType == 'Pro' && this.model.get('userSubscriptionTypeId') === 3) {
207              this.newSubscrType = 'ProPlus';
208              makeChangesToUrl(this.model.get('upgradeSystemId'), this.model.get('newArchivalRate'), 3,
209                this.model.get('trialMode'));
210            }
211
212            if (this.newSubscrType == 'Pro') {
213              this.model.set('newSubscrName', 'Pro');
214              this.model.set('newSubscrTypeId', 2);
215            } else if (this.newSubscrType == 'ProPlus') {
216              this.model.set('newSubscrName', 'Pro+');
217              this.model.set('newSubscrTypeId', 3);
218            }
219
220            //load subscription types
221            this.loadSubscriptionTypes().then(function (response) {
222              if (response.freeTrial === false && this.model.get('trialMode')) {
223                this.showFreeTrialWarning();
224                return;
225              }
226
227              var currentSubscriptionType = _.find(response.subscriptionTypes, {
228                userSubscriptionTypeId: this.model.get(
229                  'userSubscriptionTypeId')
230              });
231              var currentSubscriptionTerms = _.get(
232                currentSubscriptionType,
233                'subscriptionTerms');
234              var currentSubscriptionTerm = _.find(
235                currentSubscriptionTerms, {
236                iUserSubscriptionTermId: this.model.get(
237                  'newSubscriptionTermId')
238              });
239
240              var newSubscriptionType = _.find(response.subscriptionTypes, {
241                userSubscriptionTypeId: this.model.get(
242                  'newSubscrTypeId')
243              });
244              var newSubscriptionTerms = _.get(
245                newSubscriptionType, 'subscriptionTerms');
246              var subscriptionTerm =
247                _.find(newSubscriptionTerms, {
248                  iUserSubscriptionTermId: this.model.get(
249                    'newSubscriptionTermId')
250                });
251
252              //if length in months was passed as url param
253              if (options.lengthInMonths && !_.isNaN(Number(options.lengthInMonths))) {
254                subscriptionTerm = _.find(newSubscriptionTerms, {
255                  lengthInMonths: Number(options.lengthInMonths)
256                });
257
258                this.model.set('newSubscriptionTermId',
259                  subscriptionTerm.iUserSubscriptionTermId);
260              }
261
262              if (!subscriptionTerm) { //when changing subscr type
263                //find subscription term with matching length of the current term
264                subscriptionTerm = _.find(
265                  newSubscriptionTerms, {
266                  lengthInMonths: currentSubscriptionTerm && currentSubscriptionTerm
267                    .lengthInMonths || this.model.get('currentSubscrLengthInMonths')
268                });
269                //if still not found i.e current term is Basic, then select the first one available
270                if (!subscriptionTerm) {
271                  subscriptionTerm = newSubscriptionTerms[0];
272                }
273                this.model.set('newSubscriptionTermId',
274                  subscriptionTerm.iUserSubscriptionTermId);
275              }
276
277              //if no term found on current subscription
278              if (!currentSubscriptionTerm || this.model.get(
279                'userSubscriptionTypeId') === 1) { //basic user
280                currentSubscriptionTerm = subscriptionTerm;
281              }
282
283              this.model.set('currentSubscrLengthInMonths', _
284                .get(currentSubscriptionTerm,
285                  'lengthInMonths'));
286
287              //if user is on 3-year plan, can't set 1-year
288              var lengthInMonths = _.get(subscriptionTerm, 'lengthInMonths') <
289                this.model.get('currentSubscrLengthInMonths') ? this.model.get('currentSubscrLengthInMonths')
290                : _.get(subscriptionTerm, 'lengthInMonths');
291
292              //if it wasn't set on plan selection step
293              if (this.model.get('lengthInMonths') !== 36) {
294                this.model.set('lengthInMonths', lengthInMonths);
295              }
296              this.model.set('subscriptionTypes', response.subscriptionTypes);
297              this.model.set('subscriptionTerms',
298                newSubscriptionTerms);
299
300              if (options.subscrType) {
301                //detect existing upgrades
302                this.showDetectedUpgradesOrPaymentForm();
303              } else {
304                //show if subscr type param is not passed
305                this.showSubscriptionSelect();
306              }
307            }.bind(this));
308          } else {
309            this.showBillingAddressForm();
310          }
311        }.bind(this));
312      }.bind(this));
313
314      this.model.set('trialMode', this.trialMode);
315    },
316    ui: {
317      $cancelPayment: '#cancel-payment',
318    },
319    template: window.tpl['makePayment'],
320    events: {
321      "click @ui.$cancelPayment": "cancelPaymentForm"
322    },
323    onRender: function () {
324    },
325    onShow: function () {
326      var $container = $('#connection-issue');
327      window.addEventListener('online', function () {
328        $container.empty();
329      });
330
331      window.addEventListener('offline', function () {
332        var connectionIssueView = new ConnectionIssueView();
333        connectionIssueView.render();
334        $container.append(connectionIssueView.$el);
335      });
336    },
337    showFreeTrialWarning: function () {
338      this.showChildView('fullWidthRegion', new FreeTrialNotAvailableView());
339      this.hideDetailsAndMainRegions();
340      $('.payment-form-container').show();
341      $('.payments-loading-spinner').hide();
342    },
343    showBillingAddressForm: function (options) {
344      this.showChildView('mainRegion', new oPayments.BillingAddressView(options));
345      if (!this.detailsRegion.hasView()) {
346        this.showChildView('detailsRegion', new oPayments.SubscriptionDetailsView());
347      }
348    },
349    showPaymentForm: function (options) {
350      this.showChildView('mainRegion', new oPayments.PaymentFormView(options));
351      if (!this.detailsRegion.hasView()) {
352        this.showChildView('detailsRegion', new oPayments.SubscriptionDetailsView());
353      }
354    },
355    showCardDetailsForm: function (options) {
356      this.showChildView('mainRegion', new oPayments.CardDetailsFormView(options));
357    },
358    showResellerAssociated: function () {
359      this.showChildView('fullWidthRegion', new ResellerAssociatedView());
360      this.hideDetailsAndMainRegions();
361    },
362    showSubscriptionSelect: function () {
363      this.showChildView('mainRegion', new oPayments.SubscriptionSelectView());
364      if (!this.detailsRegion.hasView()) {
365        this.showChildView('detailsRegion', new oPayments.SubscriptionDetailsView());
366      }
367    },
368    showDetectedUpgrades: function () {
369      this.showChildView('mainRegion', new UpgradesDetectedView());
370      if (!this.detailsRegion.hasView()) {
371        this.showChildView('detailsRegion', new oPayments.SubscriptionDetailsView());
372      }
373    },
374    showPurchaseOrUseUpgrade: function () {
375      this.showChildView('mainRegion', new PurchaseOrUseUpgradeView());
376      if (!this.detailsRegion.hasView()) {
377        this.showChildView('detailsRegion', new oPayments.SubscriptionDetailsView());
378      }
379    },
380    showConfirmAddress: function () {
381      this.showChildView('mainRegion', new oPayments.ConfirmAddressView());
382    },
383    showPaymentSuccess: function () {
384      this.showChildView('fullWidthRegion', new PaymentSuccessView());
385      this.hideDetailsAndMainRegions();
386    },
387    showComparisonChart: function () {
388      this.showChildView('mainRegion', new ComparisonChartView());
389    },
390    showDetectedUpgradesOrPaymentForm: function () {
391      //when upgrading pro to pro+
392      if (this.model.get('userSubscriptionTypeId') === 2 && this.model.get('newSubscrTypeId') === 3) {
393        this.model.set('numNewUpgrades', 0);
394      }
395      wl.app.shareCalls.getDeviceUpgrades()
396        .then(function (res) {
397          if (res) this.model.set('usedUpgrades', res.length);
398          return getAmountDue(
399            this.model.get('trialMode'),
400            this.model.get('newSubscriptionTermId'),
401            this.model.get('newSubscrTypeId'),
402            this.model.get('numNewUpgrades'))
403        }.bind(this))
404        .then(function (response) {
405          if (response.data) {
406            $('.payment-form-container').show();
407            $('.payments-loading-spinner').hide();
408            getAmountDueCallback(this.model, response.data);
409            if (!this.model.get('existingUpgrades') && !response.data.newUpgradesAdjustmentAmount) {
410              if (this.model.get('company') && !this.model.get('company').isDavisManaged) {
411                this.showResellerAssociated();
412              } else {
413                this.showBillingAddressForm({upgrade: this.options.upgrade});
414              }
415            } else {
416              if (window.location.pathname.indexOf('mobile') !== -1 &&
417                (this.model.get('userSubscriptionTypeId') === this.model.get('newSubscrTypeId')) &&
418                this.model.get('currentSubscrLengthInMonths') === this.model.get('lengthInMonths') &&
419                this.model.get('usedUpgrades') < this.model.get('existingUpgrades')) {
420                this.showPurchaseOrUseUpgrade();
421              } else {
422                if (this.model.get('company') && !this.model.get('company').isDavisManaged) {
423                  this.showResellerAssociated();
424                } else {
425                  this.model.set('showUpgradesDetected', true);
426                  this.showDetectedUpgrades();
427                }
428              }
429            }
430          }
431        }.bind(this))
432    },
433    showAddMoreSensors: function () {
434      this.showChildView('fullWidthRegion', new oPayments.AddMoreSensorsView());
435    },
436    getCreditCardProfile: function () {
437      var d = $.Deferred();
438      $.get('/accountSubscription/creditCardProfile', function (response) {
439        if (response.data != null) {
440          this.model.set('paymentDetails', response.data);
441          this.model.set('address1', response.data.address1);
442          this.model.set('city', response.data.city);
443          this.model.set('state', response.data.region);
444          this.model.set('zipCode', response.data.zipCode);
445          this.model.set('countryId', response.data.countryId);
446          this.model.set('cardExpired', oPayments.checkIfCardExpired(response.data));
447          this.model.set('stripePaymentMethodId', response.data.stripePaymentMethodId);
448        }
449        d.resolve(response.data);
450      }.bind(this));
451      return d.promise();
452    },
453    loadSubscriptionTypes: function () {
454      var d = $.Deferred();
455      $.get('/accountSubscription/userSubscriptionPlansPricing', function (response) {
456        if (response.data != null) {
457          d.resolve(response.data);
458        } else {
459          d.resolve([]);
460        }
461      }.bind(this));
462      return d.promise();
463    },
464    checkIfAlreadySubscribed: function () {
465      var d = $.Deferred();
466      var self = this;
467      $.ajax({
468        type: "GET",
469        url: "/accountSubscription/currentSubscription",
470        success: function (response) {
471          d.resolve(response);
472        },
473        error: function (err) {
474        }
475      });
476      return d.promise();
477    },
478
479    checkIfAssociatedWithReseller: function () {
480      var d = $.Deferred();
481      $.ajax({
482        type: "GET",
483        url: "/accountSubscription/isUserAssociatedWithReseller",
484        success: function (response) {
485          d.resolve(response);
486        },
487        error: function (err) {
488          d.resolve(null);
489        }
490      });
491      return d.promise();
492    },
493    // submitPaymentFormFake: function (e) {
494    //   this.ui.$formSections.hide();
495    //   this.ui.$paymentSuccess.show();
496    //   return false;
497    // },
498    cancelPaymentForm: function (e) {
499      if (this.model.get('trialMode')) {
500        window.location = "/accountSubscription#confirmCancel/" + this.model.get('upgradeSystemId')
501          + "/" + this.model.get('newArchivalRate') + "/" + this.newSubscrType;
502      } else {
503        window.location = "/bulletin";
504      }
505      return false;
506    },
507    hideDetailsAndMainRegions: function () {
508      $('#main-region').hide();
509      $('#details-region').hide();
510    }
511  });
512
513  this.SubscriptionSelectView = Marionette.ItemView.extend({
514    template: window.tpl['subscription-plans-select'],
515    model: oPayments.sharedModel,
516    ui: {
517      $subscriptionTermRadio: 'input[name="subscriptionTerm"]',
518      $newSubscrTypeRadio: 'input[name="newSubscrType"]',
519      $continuePurchase: '#goToPayments',
520      $comparisonChartLink: '.comparison-chart-link'
521    },
522    events: {
523      'click button#continuePurchase': 'continuePurchase',
524      'change @ui.$subscriptionTermRadio': 'onChangeSubscriptionTerm',
525      'change @ui.$newSubscrTypeRadio': 'onChangeNewSubscrType',
526      'click @ui.$continuePurchase': 'onClickContinue',
527      'click @ui.$comparisonChartLink': 'onClickComparisonChartLink',
528    },
529    templateHelpers: function () {
530      return {
531        getTermTranslationKey: function (name) {
532          return _getTermTranslationKey(name);
533        }
534      }
535    },
536    initialize: function () {
537      if (this.model.get('newSubscrTypeId') == 2) {
538        this.model.set('totalAmountDue', this.model.get('proPrice').toFixed(
539          2));
540      } else if (this.model.get('newSubscrTypeId') == 3) {
541        this.model.set('totalAmountDue', this.model.get('proPlusPrice').toFixed(
542          2));
543      }
544      this.model.set('trialMode', false);
545      this.model.set('subscrPricePerMonth', (this.model.get(
546        'totalAmountDue') / this.model.get('lengthInMonths')).toFixed(2));
547      makeChangesToUrl(this.model.get('upgradeSystemId'), this.model.get('newArchivalRate'),
548        this.model.get('newSubscrTypeId'), this.model.get('trialMode'));
549      $('.payment-form-container').show();
550      $('.payments-loading-spinner').hide();
551    },
552    onRender: function () {
553      var subscriptionType = _.find(this.model.get('subscriptionTypes'), {
554        userSubscriptionTypeId: this.model.get('newSubscrTypeId')
555      });
556      var subscriptionTerms = _.get(subscriptionType, 'subscriptionTerms');
557      var currentTerm = _.find(subscriptionTerms, {
558        iUserSubscriptionTermId: this.model.get('newSubscriptionTermId')
559      });
560      if (!currentTerm) { //when changing subscr type
561        currentTerm = _.find(subscriptionTerms, {
562          lengthInMonths: this.model.get('lengthInMonths')
563        });
564      }
565      this.model.set('totalAmountDue', currentTerm.price.toFixed(2));
566      this.model.set('newSubscriptionTermId', currentTerm.iUserSubscriptionTermId);
567      this.model.set('subscrPricePerMonth', (this.model.get(
568        'totalAmountDue') / currentTerm.lengthInMonths).toFixed(2));
569      this.model.set('lengthInMonths', currentTerm.lengthInMonths);
570      this.model.set('subscriptionTerms', subscriptionTerms);
571    },
572    onShow: function () {
573      var price = Number($('input[name="subscriptionTerm"]:checked').attr(
574        'data-price'));
575      this.model.set('totalAmountDue', price.toFixed(2));
576    },
577    onChangeSubscriptionTerm: function (e) {
578      var $radioBtn = $(e.target);
579      var lengthInMonth = $radioBtn.attr('data-length');
580      this.model.set('newSubscriptionTermId', Number($radioBtn.val()));
581      this.model.set('lengthInMonths', Number(lengthInMonth));
582      this.render();
583    },
584    onChangeNewSubscrType: function (e) {
585      var $radioBtn = $(e.target);
586      var newSubscrTypeId = Number($radioBtn.val());
587      var newSubscrName = $radioBtn.attr('data-name');
588      this.model.set('newSubscrTypeId', newSubscrTypeId);
589      this.model.set('newSubscrName', newSubscrName);
590      this.render();
591      makeChangesToUrl(this.model.get('upgradeSystemId'), this.model.get('newArchivalRate'),
592        newSubscrTypeId, this.model.get('trialMode'));
593    },
594    onClickComparisonChartLink: function () {
595      this.triggerMethod('comparison-chart:render');
596    },
597    onClickContinue: function () {
598      this.triggerMethod('detectedUpgradesOrPaymentForm:render');
599    }
600  });
601
602  var PurchaseOrUseUpgradeView = Marionette.ItemView.extend({
603    template: window.tpl['purchase-or-use-upgrade'],
604    model: oPayments.sharedModel,
605    ui: {
606      purchaseUpgradeBtn: '#purchaseUpgrade',
607      useUpgradeBtn: '#useUpgrade',
608      cancelBtn: '#cancel',
609      paymentError: '.payment-error',
610    },
611    events: {
612      'click @ui.purchaseUpgradeBtn': 'onClickPurchaseUpgrade',
613      'click @ui.useUpgradeBtn': 'onClickUseUpgradeBtn',
614      'click @ui.cancelBtn': 'onClickCancel'
615    },
616    onClickUseUpgradeBtn: function () {
617      this.ui.paymentError.html('');
618      wl.app.shareCalls.upgradeDevice(this.model.get('upgradeSystemId')).then(
619        function (response) {
620          if (response.data) {
621            window.location.href = "/bulletin";
622          } else if (response.error) {
623            this.ui.paymentError.html(response.error.errorMessage);
624          }
625        }.bind(this))
626    },
627    onClickPurchaseUpgrade: function () {
628      if(this.model.get('company')) {
629        this.triggerMethod('resellerAssociated:render');
630      } else {
631        this.triggerMethod('detectedUpgrades:render');
632      }
633    },
634    onClickCancel: function () {
635      if (this.model.get('trialMode')) {
636        window.location = window.location.pathname + "#confirmCancel/" + this.model.get('upgradeSystemId')
637          + "/" + this.model.get('newArchivalRate') + "/" +
638          this.model.get('userSubscriptionTypeName');
639      } else {
640        window.location = "/bulletin";
641      }
642      return false;
643    }
644  });
645
646  var UpgradesDetectedView = Marionette.ItemView.extend({
647    template: window.tpl['upgrades-detected'],
648    model: oPayments.sharedModel,
649    ui: {
650      $subscriptionTermRadio: 'input[name="subscriptionTerm"]',
651      $oneYearProTooltip: '#1_year_pro_tooltip',
652      $threeYearProTooltip: '#3_year_pro_tooltip',
653    },
654    events: {
655      'click button#continuePurchase': 'continuePurchase',
656      'click button#payment-back': 'onClickBackBtn',
657      'change @ui.$subscriptionTermRadio': 'onChangeSubscriptionTerm'
658    },
659    templateHelpers: function () {
660      return {
661        getTermTranslationKey: function (name) {
662          return _getTermTranslationKey(name);
663        }
664      }
665    },
666    initialize: function () {
667      //$.fn.tooltip = $.fn.tooltip.noConflict();
668    },
669    onRender: function () {
670      var tooltipTranslationKeys = ['upgrades_detected_1_year_tooltip',
671        'upgrades_detected_3_year_tooltip'
672      ];
673      var oneYearProTooltip = _.template(
674        "<div style='text-align:left; width: 300px;'><%= upgrades_detected_1_year_tooltip %></div>"
675      );
676      var threeYearProTooltip = _.template(
677        "<div style='text-align:left; width: 300px;'><%= upgrades_detected_3_year_tooltip %></div>"
678      );
679      Utils.setTooltipTitleHtml(this.ui.$oneYearProTooltip,
680        tooltipTranslationKeys, oneYearProTooltip);
681      Utils.setTooltipTitleHtml(this.ui.$threeYearProTooltip,
682        tooltipTranslationKeys, threeYearProTooltip);
683    },
684    onChangeSubscriptionTerm: function (e) {
685      var $radioBtn = $(e.target);
686      var lengthInMonth = Number($radioBtn.attr('data-length'));
687      this.model.set('newSubscriptionTermId', Number($radioBtn.val()));
688      this.model.set('lengthInMonths', lengthInMonth);
689
690      getAmountDue(this.model.get('trialMode'),
691        this.model.get('newSubscriptionTermId'),
692        this.model.get('newSubscrTypeId'),
693        this.model.get('numNewUpgrades'),
694        this.model.get('validatedAddresses')[e.target.value]
695      ).then(function (response) {
696        getAmountDueCallback(this.model, response.data);
697        this.render();
698      }.bind(this));
699    },
700    continuePurchase: function () {
701      this.model.set('showUpgradesDetected', false);
702      //this.triggerMethod('paymentForm:render');
703      //To be changed when ready
704      this.triggerMethod('billingAddress:render');
705    },
706    onClickBackBtn: function () {
707      this.model.set('showUpgradesDetected', false);
708      this.triggerMethod('subscription-select:render');
709    }
710  });
711
712  oPayments.SubscriptionDetailsView = Marionette.ItemView.extend({
713    template: window.tpl['subscription-details'],
714    model: oPayments.sharedModel,
715    initialize: function () {
716      this.model.on('change', this.customRender.bind(this));
717    },
718    onDestroy: function () {
719      this.model.off('change');
720    },
721    customRender: function () {
722      var textEl = $.parseHTML(this.template(this.model.toJSON()))[8];
723      $(this.$el).find('.info-section').replaceWith(textEl);
724    }
725  });
726
727  var FreeTrialNotAvailableView = Marionette.ItemView.extend({
728    template: window.tpl['free-trial-unavailable'],
729    model: oPayments.sharedModel
730  });
731
732  var ResellerAssociatedView = Marionette.ItemView.extend({
733    template: window.tpl['reseller-associated'],
734    model: oPayments.sharedModel
735  });
736
737  oPayments.BillingAddressView = Marionette.ItemView.extend({
738    template: window.tpl['billing-address-form'],
739    model: oPayments.sharedModel,
740    ui: {
741      $paymentForm: '#payment-form',
742      $paymentError: '.payment-error',
743      $formInput: '.payment-form input',
744      $country: '#country',
745      $address1: '#address1',
746      $city: '#city',
747      $state: '#state',
748      $countryInput: '.country',
749      $combobox: '.custom-combobox',
750      $zip: '#zipCode',
751      $couponCode: '#couponCode',
752      $couponRow: '#couponRow',
753      $addressSection: '.address-section',
754      $subscriptionTermRadio: 'input[name="subscriptionTerm"]',
755      $backBtn: '#payment-back',
756      $continuePurchase: "#continuePurchase",
757      $invalidAddress: "#invalid-address",
758      $continueWithInvalidAddress: "#invalid-address-continue"
759    },
760    events: {
761      "focus @ui.$formInput": "onInputFocus",
762      "input @ui.$formInput": "onInput",
763      "blur @ui.$formInput": "onInputBlur",
764      "blur @ui.$countryInput": "onBlurCountryInput",
765      "blur @ui.$couponCode": "onBlurCouponCodeInput",
766      "select @ui.$country": "onChangeCountry",
767      "keypress @ui.$countryInput": "onCountryKeyPress",
768      "change @ui.$subscriptionTermRadio": "onChangeSubscriptionTerm",
769      "click @ui.$backBtn": "onClickBackBtn",
770      "click @ui.$continuePurchase": "onClickContinue",
771      "click @ui.$continueWithInvalidAddress": "onClickContinueWithInvalidAddress"
772    },
773    templateHelpers: function () {
774      return {
775        getTermTranslationKey: function (name) {
776          return _getTermTranslationKey(name);
777        },
778        upgrade: this.options.upgrade,
779        mobile: window.location.pathname.indexOf('mobile') !== -1
780      }
781    },
782    initialize: function () {
783      this.on('external:validate-address', function () {
784        this.validateAddress();
785      });
786    },
787    onRender: function () {
788      $.validator.setDefaults({
789        ignore: ":hidden:not(.not-masked)"
790      });
791      this.oFormValidator = FORM_VALIDATOR.addressForm(this.ui.$paymentForm);
792      //load countries
793      this.loadCountries();
794      if (this.model.get('countryId') === '114' || this.model.get(
795        'countryId') === '17') {
796        this.ui.$addressSection.find('.form-group').show();
797      }
798      $('.payment-form-container').show();
799      $('.payments-loading-spinner').hide();
800    },
801    onChangeSubscriptionTerm: function (e) {
802      var $radioBtn = $(e.target);
803      var lengthInMonth = Number($radioBtn.attr('data-length'));
804      this.model.set('newSubscriptionTermId', Number($radioBtn.val()));
805      this.model.set('lengthInMonths', lengthInMonth);
806
807      getAmountDue(this.model.get('trialMode'),
808        this.model.get('newSubscriptionTermId'),
809        this.model.get('newSubscrTypeId'),
810        this.model.get('numNewUpgrades'),
811        this.model.get('validatedAddresses')[e.target.value]
812      ).then(function (response) {
813        getAmountDueCallback(this.model, response.data);
814        this.customRender.call(this);
815      }.bind(this));
816    },
817    customRender: function () {
818      var textEl = $($.parseHTML(this.template(
819        _.assign(this.model.toJSON(), this.templateHelpers())))[0]).find('.payment-info--mobile');
820      $(this.$el).find('.payment-info--mobile').replaceWith(textEl);
821    },
822    onInputFocus: function (e) {
823      $(e.target).closest('.form-group').addClass('focused');
824    },
825    onInputBlur: function (e) {
826      $(e.target).closest('.form-group').removeClass('focused');
827    },
828    onInput: function (e) {
829      this.triggerMethod('form:change');
830    },
831    onChangeCountry: function (e) {
832      var countryCode = $(e.target).find('option:selected').attr(
833        'data-code');
834      if (countryCode === "US" || countryCode === "CA") {
835        this.ui.$addressSection.find('.form-group').show();
836      } else {
837        this.ui.$addressSection.find('.form-group').hide();
838        this.ui.$addressSection.find(
839          '.form-group.country-group, .form-group.zip-code').show();
840      }
841      this.triggerMethod('form:change');
842    },
843    loadCountries: function () {
844      if (this.countriesList) {
845        this.populateCountriesList(this.countriesList);
846        return;
847      }
848      if (this.xhr && this.xhr.readyState === 1) return;
849      this.xhr = $.get('/iris/countries', function (response) {
850        if (response) {
851          this.countriesList = response;
852          this.triggerMethod('countriesList:get', response);
853          this.populateCountriesList(response);
854        }
855      }.bind(this))
856    },
857    populateCountriesList: function (countries) {
858      var $selectEl = this.ui.$country;
859      var countryId = this.model.get('countryId');
860      $selectEl.append('<option></option>');
861      _.forEach(countries, function (country) {
862        $selectEl.append('<option data-code="' +
863          country.iso2Code + '" value="' + country.countryId +
864          '">' + country.countryName + '</option>')
865      });
866      if (this.model.get('countryId')) {
867        $selectEl.val(this.model.get('countryId'));
868        $selectEl.find('option[value=' + countryId + ']').attr('selected',
869          true);
870      }
871      $selectEl.combobox();
872      var countryCode = $selectEl.find('option[value=' + countryId + ']').attr('data-code');
873      if (countryCode === "US" || countryCode === "CA") {
874        this.ui.$addressSection.find('.form-group').show();
875      } else {
876        this.ui.$addressSection.find('.form-group').hide();
877        this.ui.$addressSection.find(
878          '.form-group.country-group, .form-group.zip-code').show();
879      }
880      //$selectEl.trigger('select');
881      setTimeout(function () {
882        this.ui.$countryInput.autocomplete("enable");
883      }.bind(this));
884    },
885    onCountryKeyPress: function (e) {
886      if (e.which == 13) {
887        this.ui.$countryInput.blur()
888      }
889    },
890    onBlurCountryInput: function (e) {
891      var val = $(e.target).val();
892      if (val) {
893        var startsWithMatcher = new RegExp("^" + val, "i"),
894            option = this.ui.$country.children("option").filter(function(i, elem) {
895              return startsWithMatcher.test(elem.label || elem.value || elem);
896            }).first()
897        option.attr('selected', true);
898        this.ui.$countryInput.val(option.text());
899      }
900      if (!Utils.isIE()) {
901        $('.ui-menu:not(:hidden) .ui-menu-item-wrapper').first().click();
902      }
903      if (this.oFormValidator.element(this.ui.$countryInput)) {
904        this.ui.$combobox.find('.help-block').remove();
905      }
906    },
907    onBlurCouponCodeInput: function (e) {
908      if (e.target.value === "") {
909        this.oFormValidator.element(this.ui.$couponCode);
910      }
911    },
912    validateAddress: function () {
913      if (this.oFormValidator.form()) {
914        this.triggerMethod('processing:start');
915        this.$loadingIcon = $('.loading-icon');
916        this.ui.$paymentError.html("");
917        this.ui.$continuePurchase.prop('disabled', true);
918        this.$loadingIcon.show();
919        var self = this;
920        var countryId = this.ui.$country.val();
921        var $selectedOption = this.ui.$country.find('option[value="' +
922          countryId + '"]');
923        if (!$selectedOption.length) {
924          this.oFormValidator.showErrors({
925            country: "<span data-l10n-id='country_select_required'></span>"
926          });
927          this.ui.$continuePurchase.prop('disabled', false);
928          return;
929        }
930        var countryCode = $selectedOption.attr('data-code');
931        this.model.set('countryId', countryId);
932        this.model.set('countryCode', countryCode);
933        this.model.set('countryName', $selectedOption.text());
934        this.model.set('address1', this.ui.$address1.val());
935        this.model.set('city', this.ui.$city.val());
936        this.model.set('state', this.ui.$state.val());
937        this.model.set('zipCode', this.ui.$zip.val());
938        this.model.set('couponCode', this.ui.$couponCode.val());
939
940        var billingAddress = {
941          countryCode: countryCode,
942          zipCode: this.model.get('zipCode')
943        };
944
945        var url = '/accountSubscription/validateAddress';
946
947        // if (this.model.get('standalone')) {
948        //   url = '/deviceSubscription/validateAddress';
949        //   var $submitBtn = $('#submit')
950        //   var $loadingIcon = $('.loading-icon');
951        //   $submitBtn.prop('disabled', 'disabled');
952        //   $loadingIcon.show();
953        // }
954
955        self.ui.$invalidAddress.hide();
956
957        if (countryCode === 'US' || countryCode === 'CA') {
958          billingAddress.address1 = this.model.get('address1');
959          billingAddress.city = this.model.get('city');
960          billingAddress.state = this.model.get('state');
961
962          $.ajax({
963            type: "POST",
964            url: url,
965            contentType: 'application/json',
966            data: JSON.stringify(billingAddress),
967            success: function (response) {
968              if (response.data) {
969                if (response.data.length > 0) {
970                  self.model.set('validatedAddresses', response.data);
971                  self.triggerMethod('confirm-address:render', self.model.attributes);
972                }
973              } else if (response.error) {
974                var errorObj;
975                try {
976                  errorObj = JSON.parse(response.error.errorMessage);
977                } catch (e) {
978                  errorObj = response.error.errorMessage;
979                }
980
981                if(errorObj === 'INVALID_ADDRESS' || errorObj.error === 'INVALID_ADDRESS' || errorObj.error.errorMessage === 'INVALID_ADDRESS'){
982                    self.ui.$invalidAddress.show();
983                }else{
984                  self.ui.$paymentError.html(errorObj.error && errorObj.error
985                    .message || errorObj);
986                }
987                self.ui.$continuePurchase.prop('disabled', false);
988                self.triggerMethod('processing:stop');
989                self.$loadingIcon.hide();
990              }
991            },
992            error: function (response) {
993              console.log(response);
994            }
995          })
996        } else { //not us or canada
997          this.model.set('validatedAddresses', [billingAddress]);
998          this.triggerMethod('confirm-address:render', this.model.attributes);
999        }
1000      }
1001    },
1002    onClickContinue: function () {
1003      var self = this;
1004      var couponCode = this.ui.$couponCode.val();
1005      if (couponCode) {
1006        self.ui.$paymentError.html('');
1007        $.ajax({
1008          type: 'POST',
1009          url: '/accountSubscription/applyCouponCode',
1010          data: {couponCode: couponCode},
1011          success: function (res) {
1012            if (res.data) {
1013              self.validateAddress();
1014            } else if (res.error) {
1015                self.ui.$paymentError.html(res.error.errorMessage);
1016            }
1017          },
1018          error: function (err) {
1019            self.ui.$paymentError.html(err.responseText);
1020          }
1021        });
1022      } else {
1023        this.validateAddress();
1024      }
1025    },
1026
1027    onClickContinueWithInvalidAddress : function(){
1028        var countryId = this.ui.$country.val();
1029        var $selectedOption = this.ui.$country.find('option[value="' +
1030          countryId + '"]');
1031        var countryCode = $selectedOption.attr('data-code');
1032        var billingAddress = {
1033          countryCode: countryCode,
1034          zipCode: this.model.get('zipCode')
1035        };
1036
1037        if (countryCode === 'US' || countryCode === 'CA') {
1038          billingAddress.address1 = this.model.get('address1');
1039          billingAddress.city = this.model.get('city');
1040          billingAddress.state = this.model.get('state');
1041        }
1042
1043      //special indicator to avalara to use zip code for taxation purposes as exact
1044      //street address is not found in Avalara
1045      billingAddress.address2 = "Local Delivery";
1046      this.model.set('validatedAddresses', [billingAddress]);
1047      this.triggerMethod('confirm-address:render', this.model.attributes);
1048    },
1049
1050    onClickBackBtn: function () {
1051      if (this.options.upgrade) {
1052        window.location.href = '/bulletin';
1053        return;
1054      }
1055      if (!this.model.get('existingUpgrades') && !this.model.get('newUpgradesAdjustmentAmount')) {
1056        this.triggerMethod('subscription-select:render');
1057      } else {
1058        this.triggerMethod('detectedUpgradesOrPaymentForm:render');
1059      }
1060    },
1061    onDestroy: function () {
1062      this.off('external:validate-address');
1063    }
1064  });
1065
1066  oPayments.PaymentFormView = Marionette.ItemView.extend({
1067    template: window.tpl['payment-form'],
1068    model: oPayments.sharedModel,
1069    initialize: function (options) {
1070       wl.app.stripe = Stripe(wl.stripePublisherKey);
1071      if (options.model) {
1072        this.model = new PaymentModel(
1073          _.assign(oPayments.sharedModel.attributes, options.model.attributes));
1074      }
1075      if (options.standalone) {
1076        this.model.set('standalone', options.standalone)
1077      }
1078    },
1079    ui: {
1080      $paymentForm: '#payment-form',
1081      $paymentError: '.payment-error',
1082      $formInput: '.payment-form input',
1083      $useSavedLink: '.use-saved, .use-saved--mobile',
1084      $enterNewCardLink: '.saved-card-link',
1085      $cardNumber: '#accountCardNumber',
1086      $cardExp: '#accountCardExpiration',
1087      $cardCvc: '#accountCardCvc',
1088      $couponCode: '#couponCode',
1089      $couponRow: '#couponRow',
1090      $newCardBlocks: '#payment-form-buttons, ' +
1091        '.credit-card-section, .use-saved, .use-saved--mobile',
1092      $savedCardBlocks: '#saved-card-buttons, #saved-card',
1093      $subscriptionTermRadio: 'input[name="subscriptionTerm"]',
1094      $submitPaymentBtn: '.submitPayment',
1095      $paymentProcessing: '#paymentProcessing',
1096      submit: '.submitPayment',
1097      errorBlock: '.payment-error',
1098      loadingIcon: '.loading-icon'
1099    },
1100    events: {
1101      "blur @ui.$couponCode": "onBlurCouponCodeInput",
1102      "click @ui.$useSavedLink": "showSavedCard",
1103      "click @ui.$enterNewCardLink": "showPaymentForm",
1104      "click button#submitPayment": "submitPaymentForm",
1105      "click button#submitPaymentSaved": "submitPaymentFormSaved",
1106      "click button#payment-back, button#payment-back-saved": "onClickBackBtn",
1107    },
1108    templateHelpers: function () {
1109      return {
1110        getTermTranslationKey: function (name) {
1111          return _getTermTranslationKey(name);
1112        },
1113        upgrade: this.options.upgrade      
1114      }
1115    },
1116    onRender: function () {
1117      $.validator.setDefaults({
1118        ignore: ":hidden:not(.not-masked)"
1119      });
1120      if (this.model.get('paymentDetails') && this.model.get('stripePaymentMethodId')
1121        && this.model.get('useSavedCard')) {
1122        this.showSavedCard();
1123      } else {
1124        this.showPaymentForm();
1125      }
1126      $('.payment-form-container').show();
1127      $('.payments-loading-spinner').hide();
1128    },
1129    onShow: function () {
1130      initializeStripeElements(this);
1131    },
1132    showSavedCard: function () {
1133      this.ui.$newCardBlocks.hide();
1134      this.ui.$savedCardBlocks.show();
1135      this.model.set('useSavedCard', true);
1136      this.triggerMethod('useSavedCardOption:change', true);
1137    },
1138    showPaymentForm: function () {
1139      this.ui.$newCardBlocks.show();
1140      this.ui.$savedCardBlocks.hide();
1141      this.model.set('useSavedCard', false);
1142      this.triggerMethod('useSavedCardOption:change', false);
1143    },
1144    getSetupIntent: function () {
1145      var d = $.Deferred();
1146      $.ajax({
1147        type: 'GET',
1148        url: '/accountSubscription/setupIntent',
1149        success: function (response) {
1150          d.resolve(response);
1151        },
1152        error: function (err) {
1153          d.resolve(null);
1154        }
1155      });
1156      return d.promise();
1157    },
1158    setupCardPaymentMethod: function (stripePaymentMethodId, isTrial, zeroAmount) { //used for trial and amount = 0
1159      var self = this;
1160      this.getSetupIntent().then(function(response) {
1161        if (response && response.data) {
1162          console.log(`got payment intent ${response}`);
1163          self.clientSecret = response.data.clientSecret;
1164          self.setupIntent = response.data.setupIntent;
1165          wl.app.stripe.confirmCardSetup(self.clientSecret, {
1166            payment_method: stripePaymentMethodId ? stripePaymentMethodId : {
1167              card: self.cardNumber
1168            }
1169          }).then(function (result) {
1170            // Handle result.error or result.setupIntent
1171            if (result.setupIntent) {
1172              self.model.set('stripePaymentMethodId', result.setupIntent.payment_method);
1173              self.postPayment(isTrial, zeroAmount);
1174            } else if (result.error) {
1175              self.ui.$paymentError.html(result.error.message);
1176              self.ui.$submitPaymentBtn.prop('disabled', false);
1177              self.ui.loadingIcon.hide();
1178              self.ui.$paymentProcessing.hide();
1179            }
1180          });
1181        }
1182      });
1183    },
1184    payWithCard: function (stripePaymentMethodId) {
1185      this.ui.$paymentError.html("");
1186      // Disable the submit button to prevent repeated clicks:
1187      this.ui.$submitPaymentBtn.prop('disabled', true);
1188      this.ui.$paymentProcessing.show();
1189      this.ui.loadingIcon.show();
1190      var clientSecret = this.model.get('clientSecret');
1191      var card = this.cardNumber;
1192      if (this.model.get('trialMode')) {
1193        this.setupCardPaymentMethod(stripePaymentMethodId, true);
1194      } else if (clientSecret) { // amount > 0
1195        wl.app.stripe
1196          .confirmCardPayment(clientSecret, {
1197            payment_method: stripePaymentMethodId ? stripePaymentMethodId : {
1198              card: card
1199            }
1200          })
1201          .then(function (result) {
1202            if (result.error) {
1203              // Show error to your customer
1204              //showError(result.error.message);
1205              console.log(result.error.message);
1206              this.ui.$paymentError.html(result.error.message);
1207              this.ui.$submitPaymentBtn.prop('disabled', false);
1208              this.ui.loadingIcon.hide();
1209              this.ui.$paymentProcessing.hide();
1210            } else if (result.paymentIntent) {
1211              // The payment succeeded!
1212              this.model.set('stripePaymentMethodId', result.paymentIntent.payment_method);
1213              this.model.set('paymentIntentId', result.paymentIntent.id);
1214              if (this.model.get('renewal')) {
1215                this.renewSubscription();
1216              } else {
1217                this.postPayment();
1218              }
1219              console.log(`stripe response ${JSON.stringify(result)}` )
1220              console.log("payment success")
1221            }
1222          }.bind(this));
1223      } else { //amount is 0 due to coupon code
1224        this.setupCardPaymentMethod(stripePaymentMethodId, false, true);
1225      }
1226    },
1227    onBlurCouponCodeInput: function (e) {
1228      if (e.target.value === "") {
1229        this.oFormValidator.element(this.ui.$couponCode);
1230      }
1231    },
1232    submitPaymentForm: function () {
1233      this.payWithCard();
1234      return false;
1235    },
1236    submitPaymentFormSaved: function () {
1237      this.payWithCard(this.model.get('stripePaymentMethodId'));
1238      return false;
1239    },
1240    postPayment: function (isTrial, zeroAmount) { // coupon code
1241      var url = isTrial ? '/accountSubscription/postTrialSubscription' :
1242        '/accountSubscription/postPayment/' + this.model.get('paymentIntentId');
1243      if (zeroAmount) {
1244        url = '/accountSubscription/postPayment';
1245      }
1246      var self = this;
1247      var data = {
1248        numNewUpgrades: this.model.get('numNewUpgrades'),
1249        newSubscriptionTermId: this.model.get('newSubscriptionTermId'),
1250        newSubscriptionType: this.model.get('newSubscrTypeId'),
1251        isTrial: this.model.get('trialMode'),
1252        billingAddress: this.model.get('validatedAddresses')[0],
1253        paymentMethodId: this.model.get('stripePaymentMethodId')
1254      };
1255      if (this.model.get('couponCode')) {
1256        data.couponCode = this.model.get('couponCode');
1257      }
1258      $.ajax({
1259        type: "POST",
1260        url: url,
1261        contentType: 'application/json',
1262        data: JSON.stringify(data),
1263        success: function (response) {
1264          if (response.data) {
1265            console.log(response.data);
1266            onPaymentSuccess.call(self, response);
1267          } else if (response.error) {
1268            self.ui.$paymentError.html(response.error.errorDetail || response.error.errorMessage);
1269            self.ui.$submitPaymentBtn.prop('disabled', false);
1270            self.ui.$paymentProcessing.hide();
1271          }
1272        },
1273        error: function (err) {
1274          self.ui.$paymentError.html(err.responseText);
1275          self.ui.$submitPaymentBtn.prop('disabled', false);
1276          self.ui.$paymentProcessing.hide();
1277        }
1278      });
1279    },
1280    renewSubscription: function () {
1281      var self = this;
1282      $.ajax({
1283        type: "GET",
1284        url: '/accountSubscription/renewSubscription/' + this.model.get('paymentIntentId'),
1285        success: function (response) {
1286          if (response) {
1287            console.log(response);
1288            self.triggerMethod('renewal-success:render');
1289          } else if (response.error) {
1290            self.ui.$paymentError.html(response.error.errorDetail || response.error.errorMessage);
1291            self.ui.$submitPaymentBtn.prop('disabled', false);
1292            self.ui.$paymentProcessing.hide();
1293          }
1294        },
1295        error: function (err) {
1296          self.ui.$paymentError.html(err.responseText);
1297          self.ui.$submitPaymentBtn.prop('disabled', false);
1298          self.ui.$paymentProcessing.hide();
1299        }
1300      });
1301    },
1302    onClickBackBtn: function () {
1303      if (this.options.upgrade || this.model.get('renewal')) {
1304        window.location.href = '/bulletin';
1305        return;
1306      }
1307      if (this.model.get('taxDue') && this.model.get('totalAmountDue')) {
1308        var amountDue = this.model.get('totalAmountDue') - this.model.get('taxDue');
1309        this.model.set('totalAmountDue', amountDue.toFixed(2));
1310      }
1311      this.model.set('totalAmountCustomerCurrency', null);
1312      this.model.set('beforeTax', true);
1313      this.triggerMethod('confirm-address:render', this.model.attributes);
1314      this.triggerMethod('external:confirm-address:render', this.model.attributes);
1315    }
1316  });
1317
1318  //account and mobile card details
1319  oPayments.CardDetailsFormView = oPayments.BillingAddressView.extend({
1320    template: window.tpl['card-details-form'],
1321    ui: _.extend({}, oPayments.BillingAddressView.prototype.ui, {
1322      $useSavedLink: '.use-saved, .use-saved--mobile',
1323      $enterNewCardLink: '.saved-card-link',
1324      $cardNumber: '#accountCardNumber',
1325      $cardExp: '#accountCardExpiration',
1326      $cardCvc: '#accountCardCvc',
1327      $newCardBlocks: '#payment-form-buttons, ' +
1328        '.credit-card-section, .use-saved, .use-saved--mobile',
1329      $savedCardBlocks: '#saved-card-buttons, #saved-card',
1330	}),
1331    events: {
1332      "focus @ui.$formInput": "onInputFocus",
1333      "input @ui.$formInput": "onInput",
1334      "blur @ui.$formInput": "onInputBlur",
1335      "blur @ui.$countryInput": "onBlurCountryInput",
1336      "select @ui.$country": "onChangeCountry",
1337      "keypress @ui.$countryInput": "onCountryKeyPress",
1338      "click @ui.$useSavedLink": "showSavedCard",
1339      "click @ui.$enterNewCardLink": "showPaymentForm",
1340      "click @ui.$continueWithInvalidAddress": "continueWithInvalidAddress",
1341    },
1342    initialize: function (options) {
1343      if (this.options.stripeKey) {
1344        wl.app.stripe = Stripe(this.options.stripeKey);
1345      }
1346      if (options.model) {
1347        this.model = new Backbone.Model(options.model.attributes);
1348      }
1349      if (options.standalone) {
1350        this.model.set('standalone', options.standalone)
1351      }
1352      this.on('external:save-card-details', this.saveCardDetails.bind(this));
1353    },
1354    onRender: function () {
1355      $.validator.setDefaults({
1356        ignore: ":hidden:not(.not-masked)"
1357      });
1358      this.oFormValidator = FORM_VALIDATOR.addressForm(this.ui.$paymentForm);
1359      this.loadSetupIntent();
1360      if (this.model.get('paymentDetails') && this.model.get('stripePaymentMethodId')
1361        && this.model.get('useSavedCard')) {
1362        this.showSavedCard();
1363      } else {
1364        this.showPaymentForm();
1365      }
1366      //load countries
1367      this.loadCountries();
1368      if (this.model.get('countryId') === '114' || this.model.get(
1369        'countryId') === '17') {
1370        this.ui.$addressSection.find('.form-group').show();
1371      }
1372      //setup external ui elements
1373      this.$submitBtn = $('#submit');
1374      this.$loadingIcon = $('.loading-icon');
1375      this.$cardSavedMsg = $('.credit-card-saved-msg');
1376
1377      setTimeout(function () {
1378        initializeStripeElements(this);
1379        console.log("initialized card element");
1380      }.bind(this), 100);
1381    },
1382    showSavedCard: function () {
1383      this.ui.$newCardBlocks.hide();
1384      this.ui.$savedCardBlocks.show();
1385      this.model.set('useSavedCard', true);
1386      this.triggerMethod('useSavedCardOption:change', true);
1387    },
1388    showPaymentForm: function () {
1389      this.ui.$newCardBlocks.show();
1390      this.ui.$savedCardBlocks.hide();
1391      this.model.set('useSavedCard', false);
1392      this.triggerMethod('useSavedCardOption:change', false);
1393    },
1394    loadSetupIntent: function () {
1395      $.get('/accountSubscription/setupIntent', function (response) {
1396        if (response && response.data) {
1397          console.log(`got payment intent ${response}`);
1398          this.clientSecret = response.data.clientSecret;
1399          this.setupIntent = response.data.setupIntent;
1400          this.$submitBtn.prop('disabled', false);
1401          this.$loadingIcon.hide();
1402        }
1403      }.bind(this))
1404    },
1405    confirmCardSetup: function (bypassAddressValidation) {
1406      var self = this;
1407      var countryCode = this.model.get('countryCode');
1408      var billingAddress = {
1409        countryCode: countryCode,
1410        zipCode: this.model.get('zipCode')
1411      };
1412
1413      if (countryCode === 'US' || countryCode === 'CA') {
1414        billingAddress.address1 = this.model.get('address1');
1415        billingAddress.city = this.model.get('city');
1416        billingAddress.state = this.model.get('state');
1417        if(bypassAddressValidation){
1418          billingAddress.address2 = "Local Delivery";
1419        }
1420      }
1421      
1422      if (this.model.get('useSavedCard') && this.model.get('paymentDetails') 
1423          && this.model.get('stripePaymentMethodId')) {
1424        this.processSavePaymentMethod(this.model.get('paymentDetails').stripePaymentMethodId, billingAddress);
1425      } else {
1426        var d = $.Deferred();
1427        //we already have a saved Payment method from prior success and we are in "continueWithBadAddress" mode
1428        if(self.savedPaymentMethod){
1429          d.resolve(self.savedPaymentMethod);
1430        }else{
1431            wl.app.stripe.confirmCardSetup(this.clientSecret, {
1432              payment_method: {
1433                card: this.cardNumber
1434              }
1435            }).then(function(result){
1436              if (result.setupIntent) {
1437                self.savedPaymentMethod = result.setupIntent.payment_method;
1438                d.resolve(result.setupIntent.payment_method);
1439              } else if (result.error) {
1440                  self.ui.$paymentError.html(result.error.message);
1441                  self.$submitBtn.prop('disabled', false);
1442                  self.$loadingIcon.hide();
1443              }
1444            });
1445        }
1446
1447        d.promise().then(function(payment_method){
1448            self.processSavePaymentMethod(payment_method, billingAddress);
1449
1450          // Handle result.error or result.setupIntent
1451        }.bind(this));
1452      }
1453    },
1454    continueWithInvalidAddress: function(){
1455      this.confirmCardSetup(true);
1456    },
1457    processSavePaymentMethod: function (paymentMethodId, billingAddress) {
1458      var self = this;
1459      self.ui.$invalidAddress.hide();
1460      $.ajax({
1461        type: "POST",
1462        url: '/accountSubscription/savePaymentMethod?paymentMethodId='
1463          + paymentMethodId,
1464        contentType: 'application/json',
1465        data: JSON.stringify(billingAddress),
1466        success: function (response) {
1467          if (response.error) {
1468            if(response.error.errorMessage === 'INVALID_ADDRESS'){
1469              self.ui.$invalidAddress.show();
1470            }else{
1471              self.ui.$paymentError.html(response.error.errorMessage);
1472            }
1473
1474            if (self.$submitBtn && self.$loadingIcon) {
1475              self.$submitBtn.prop('disabled', false);
1476              self.$loadingIcon.hide();
1477            }
1478          } else {
1479            if (!self.model.get('useSavedCard')) {
1480              // load new intent for next save calls
1481              self.loadSetupIntent();
1482              self.model.set('paymentDetails', response);
1483              self.model.set('stripePaymentMethodId', response.stripePaymentMethodId);
1484              self.model.set('useSavedCard', true);
1485              self.render();
1486            } else {
1487              self.$submitBtn.prop('disabled', false);
1488              self.$loadingIcon.hide();
1489            }
1490            self.$cardSavedMsg.show();
1491            setTimeout(function () {
1492              self.$cardSavedMsg.hide();
1493            }, 3000);
1494            self.triggerMethod('cardDetails:saved');
1495          }
1496        },
1497        error: function (response) {
1498          console.log(response);
1499        }
1500      });
1501    },
1502    saveCardDetails: function () {
1503      if (this.oFormValidator.form()) {
1504        this.ui.$paymentError.html("");
1505        var countryId = this.ui.$country.val();
1506        var $selectedOption = this.ui.$country.find('option[value="' +
1507          countryId + '"]');
1508        if (!$selectedOption.length) {
1509          this.oFormValidator.showErrors({
1510            country: "<span data-l10n-id='country_select_required'></span>"
1511          });
1512          return;
1513        }
1514        var countryCode = $selectedOption.attr('data-code');
1515        this.model.set('countryId', countryId);
1516        this.model.set('countryCode', countryCode);
1517        this.model.set('countryName', $selectedOption.text());
1518        this.model.set('address1', this.ui.$address1.val());
1519        this.model.set('city', this.ui.$city.val());
1520        this.model.set('state', this.ui.$state.val());
1521        this.model.set('zipCode', this.ui.$zip.val());
1522
1523        this.$submitBtn.prop('disabled', 'disabled');
1524        this.$loadingIcon.show();
1525
1526        this.confirmCardSetup();
1527      }
1528    },
1529    onDestroy: function () {
1530      this.off('external:save-card-details');
1531    }
1532  });
1533
1534  oPayments.ConfirmAddressView = Marionette.ItemView.extend({
1535    template: window.tpl['confirm-address'],
1536    model: oPayments.sharedModel,
1537    ui: {
1538      $confirmAddressBtn: '#confirmAddressBtn, #submit',
1539      $confirmedAddressRadio: 'input[name="confirmed_address"]',
1540      $paymentError: '.payment-error',
1541      $backBtn: '#payment-back, #back',
1542      $loadingIcon: '.loading-icon'
1543    },
1544    events: {
1545      'click @ui.$confirmAddressBtn': 'onClickConfirmAddress',
1546      'change @ui.$confirmedAddressRadio': 'onChangeConfirmedAddress',
1547      'click @ui.$backBtn': 'onClickBack'
1548    },
1549    onChangeConfirmedAddress: function (e) {
1550      var selectedIndex = e.target.value;
1551      this.model.set('selectedAddressIndex', selectedIndex);
1552    },
1553    onClickConfirmAddress: function () {
1554      if (this.model.get('standalone')) {
1555        this.getAmountWithTaxByOrderId.call(this);
1556      } else {
1557        this.getAmountDueWithTax.call(this);
1558      }
1559    },
1560    getAmountDueWithTax: function () {
1561      this.ui.$confirmAddressBtn.prop('disabled', true);
1562      this.ui.$loadingIcon.show();
1563      getAmountDue(
1564        this.model.get('trialMode'),
1565        this.model.get('newSubscriptionTermId'),
1566        this.model.get('newSubscrTypeId'),
1567        this.model.get('numNewUpgrades'),
1568        this.model.get('validatedAddresses')[this.model.get('selectedAddressIndex')],
1569        this.model.get('couponCode'),
1570      ).then(function (response) {
1571        if (response.data) {
1572          getAmountDueCallback(this.model, response.data);
1573          this.model.set('beforeTax', false);
1574          //when coupon code field is not empty
1575          if (this.model.get('couponCode') && response.data.couponDiscount == null) {
1576            this.ui.$paymentError.html("<span data-l10n-id='invalid_coupon_code'></span>");
1577            this.ui.$confirmAddressBtn.prop('disabled', false);
1578            this.ui.$loadingIcon.hide();
1579          } else {
1580            this.triggerMethod('paymentForm:render');
1581          }
1582          //show confirm address and taxes
1583        } else if (response.error) {
1584          this.ui.$paymentError.html(response.error.errorMessage);
1585          this.ui.$confirmAddressBtn.prop('disabled', false);
1586          this.ui.$loadingIcon.hide();
1587          console.log(response);
1588        }
1589      }.bind(this));
1590    },
1591    getAmountWithTaxByOrderId: function () {
1592      this.ui.$paymentError.text('');
1593      this.ui.$confirmAddressBtn.prop('disabled', 'disabled');
1594      this.ui.$loadingIcon.show();
1595      var data = {
1596        orderId: this.model.get('orderId'),
1597        billingAddressInfo: this.model.get('validatedAddresses')[this.model.get('selectedAddressIndex')]
1598      }
1599      if (this.model.get('paymentDetails')) {
1600        data.cardProfileId = this.model.get('paymentDetails').id;
1601      }
1602      getTaxAmount(data);
1603      var self = this;
1604      function getTaxAmount(data) {
1605        $.ajax({
1606          type: 'POST',
1607          contentType: 'application/json',
1608          data: JSON.stringify(data),
1609          url: '/deviceSubscription/amountDueWithTaxes',
1610          success: function (response) {
1611            console.log(response)
1612            if (response.data) {
1613              self.model.set('totalAmountDue', response.data.totalAmountDue);
1614              self.model.set('taxDue', response.data.taxDue);
1615              self.model.set('taxRate', response.data.appliedTaxRate);
1616              self.model.set('reactivationFeeDetail', response.data.reactivationFeeDetail);
1617              self.model.set('customerCurrency', response.data.customerCurrency);
1618              self.model.set('totalAmountCustomerCurrency', response.data.totalAmountCustomerCurrency);
1619              self.model.set('renewalDate',  dayjs(response.data.subscriptionEndDate).format('ll'));
1620              self.model.set('paymentIntentId', response.data.paymentIntentId);
1621              self.model.set('clientSecret', response.data.clientSecret);
1622              self.triggerMethod('paymentForm:render', self.model.attributes);
1623            } else {
1624              self.ui.$paymentError.text(response.error.errorMessage);
1625              self.ui.$confirmAddressBtn.prop('disabled', false);
1626              self.ui.$loadingIcon.hide();
1627            }
1628          }.bind(this),
1629          error: function (err) {
1630            self.ui.$confirmAddressBtn.prop('disabled', false);
1631            self.ui.$loadingIcon.hide();
1632            console.log(err);
1633          }
1634        });
1635      }
1636    },
1637    onClickBack: function () {
1638      this.triggerMethod('billingAddress:render');
1639    }
1640  });
1641
1642  var PaymentSuccessView = Marionette.ItemView.extend({
1643    template: window.tpl['payment-success'],
1644    model: oPayments.sharedModel,
1645    ui: {
1646      'gotIt': '#gotIt'
1647    },
1648    events: {
1649      'click @ui.gotIt': 'onClickGotIt'
1650    },
1651    templateHelpers: function () {
1652      return {
1653        isMobile: window.location.pathname.indexOf('mobile') !== -1
1654      }
1655    },
1656    onClickGotIt: function () {
1657      if (window.location.pathname.indexOf('mobile') !== -1) {
1658        window.location.href = '/bulletin'
1659      } else {
1660        if(sessionStorage.getItem('showAddMoreSensors')) {
1661          sessionStorage.removeItem("showAddMoreSensors");
1662          this.triggerMethod('addMoreSensors:render');
1663        } else {
1664          window.location.href = '/bulletin'
1665        }
1666      }
1667    }
1668  });
1669
1670  var ComparisonChartView = Marionette.ItemView.extend({
1671    template: window.tpl['comparison-chart'],
1672    ui: {
1673      'featureRow': '.comparison-chart-feature-row:not(.empty)',
1674      'featureName': '.feature-name',
1675      'featureDesc': '.feature-desc',
1676      'close': 'button.btn-payment-primary',
1677      'historical_data_tooltip': '#historical-data-tooltip',
1678      'bulletin_tooltip': '#bulletin-tooltip',
1679      'map_tooltip': '#map-tooltip',
1680      'chart_tooltip': '#chart-tooltip',
1681      'data_tooltip': '#data-tooltip',
1682      'mobilize_tooltip': '#mobilize-tooltip',
1683      'ipm_tooltip': '#ipm-tooltip',
1684      'pro_shares_tooltip': '#pro-shares-tooltip',
1685      'third_party_intervals_tooltip': '#third-party-intervals-tooltip'
1686    },
1687    events: {
1688      'click @ui.featureRow': 'onClickFeatureRow',
1689      'click @ui.close': 'onClickClose'
1690    },
1691    onRender: function () {
1692      this.setTooltipTitles();
1693    },
1694    setTooltipTitles: function () {
1695      var self = this;
1696      var promises = [];
1697      var tooltipTranslationKeys = [
1698        'historical_data_tooltip',
1699        'bulletin_tooltip',
1700        'map_tooltip',
1701        'chart_tooltip',
1702        'data_tooltip',
1703        'mobilize_tooltip',
1704        'ipm_tooltip',
1705        'pro_shares_tooltip',
1706        'third_party_intervals_tooltip'
1707      ];
1708      _.each(tooltipTranslationKeys, function (key) {
1709        var prom = document.l10n.formatValue(key);
1710        promises.push(prom);
1711      });
1712      Promise.all(promises).then(function (values) {
1713        _.forEach(values, function (value, i) {
1714          if (self.ui[tooltipTranslationKeys[i]]) {
1715            self.ui[tooltipTranslationKeys[i]].attr(
1716              'data-bs-original-title',
1717              '<div class="">' +
1718              values[i] + '</div>');
1719            self.ui[tooltipTranslationKeys[i]].tooltip();
1720            $('.tooltip:visible').hide();
1721          }
1722        });
1723      });
1724    },
1725    onClickFeatureRow: function (e) {
1726      this.ui.featureRow.removeClass('selected');
1727      var row = $(e.target);
1728      if (e.target !== e.currentTarget) {
1729        row = $(e.target).closest('.comparison-chart-feature-row')
1730      }
1731
1732      row.addClass('selected');
1733      var featureDesc = row.find('.icon-faq').attr('data-bs-original-title');
1734      var featureName = row.find('span').text();
1735      this.ui.featureName.text(featureName);
1736      this.ui.featureDesc.html(featureDesc);
1737    },
1738    onClickClose: function () {
1739      this.triggerMethod('subscription-select:render');
1740    }
1741  });
1742
1743  var ConnectionIssueView = Marionette.ItemView.extend({
1744    template: window.tpl['connection-issue'],
1745    ui: {
1746      retry: '#connection-issue__retry'
1747    },
1748    events: {
1749      'click @ui.retry': 'onClickRetry'
1750    },
1751    onClickRetry: function () {
1752      if (navigator.onLine) {
1753        $('#connection-issue').empty();
1754        $('.btn-payment-primary').prop('disabled', false);
1755      }
1756    }
1757  });
1758
1759  oPayments.AddMoreSensorsView = Marionette.ItemView.extend({
1760    template: window.tpl['add-more-sensors'],
1761    ui: {
1762      'submit': '#submit',
1763      'finish': '#back'
1764    },
1765    events: {
1766      'click @ui.submit': 'onClickAddMore',
1767      'click @ui.finish': 'onClickFinish'
1768    },
1769    initialize: function (options) {
1770    },
1771    onClickAddMore: function () {
1772      sessionStorage.setItem("loadDeviceConfig", true);
1773      var identifier = this.options.deviceIdentifier || wl.deviceIdentifier || wl.lastViewedSystemId;
1774
1775      window.location.replace(
1776        "/manageDevices/enviroMonitorConfiguration/" + identifier
1777      );
1778    },
1779    onClickFinish: function () {
1780      window.location.href = '/bulletin/' + this.options.deviceIdentifier;
1781    }
1782  });
1783
1784  function onPaymentSuccess(response) {
1785    console.log("response success");
1786    console.log(response);
1787    if (response.data) {
1788      this.triggerMethod('payment-success:render');
1789      if (this.model.get('trialMode')) {
1790        gtag('event', 'proTrialUpgrade');
1791      } else {
1792        gtag('event', 'proUpgrade');
1793      }
1794    } else if (response.error) {
1795      this.ui.$paymentError.html(response.error.errorDetail || response.error.errorMessage);
1796      this.ui.$submitPaymentBtn.prop('disabled', false);
1797    }
1798    $('#paymentProcessing').hide();
1799  }
1800
1801  function getAmountDue(isTrial, newSubscriptionTermId, subscrTypeId,
1802    numNewUpgrades, billingAddress, couponCode) {
1803    var d = $.Deferred();
1804    var url = "/accountSubscription/amountDue";
1805    var data = {
1806      numNewUpgrades: numNewUpgrades,
1807      newSubscriptionTermId: newSubscriptionTermId,
1808      newSubscriptionType: subscrTypeId,
1809      isTrial: isTrial
1810    };
1811
1812    if (couponCode) {
1813      data.couponCode = couponCode;
1814    }
1815
1816    if (billingAddress) {
1817      data.billingAddress = billingAddress;
1818      url = "/accountSubscription/amountDueWithTaxes";
1819    }
1820    $.ajax({
1821      type: "POST",
1822      contentType: 'application/json',
1823      data: JSON.stringify(data),
1824      url: url,
1825      success: function (response) {
1826        d.resolve(response);
1827      },
1828      error: function (err) {
1829        console.log(err);
1830      }
1831    });
1832    return d.promise();
1833  }
1834
1835  function getAmountDueCallback(model, data) {
1836    var totalAmountDue = data.totalAmountDue.toFixed(2);
1837    var newUgradesAmountDue = data.newUgradesAmountDue.toFixed(2);
1838    var existingUpgradesAmountDue = data.existingUpgradesAmountDue.toFixed(2);
1839    var subscrPricePerMonth = (totalAmountDue / model.get('lengthInMonths')).toFixed(
1840      2);
1841    var taxDue = data.taxDue ? data.taxDue.toFixed(2) : 0;
1842    var couponDiscount = data.couponDiscount ? data.couponDiscount.toFixed(2) : 0;
1843    var newUpgradesAdjustmentAmount = data.newUpgradesAdjustmentAmount ?
1844      data.newUpgradesAdjustmentAmount.toFixed(2) : 0;
1845
1846    model.set('totalAmountDue', totalAmountDue);
1847    model.set('newUgradesAmountDue', newUgradesAmountDue);
1848    model.set('existingUpgradesAmountDue', existingUpgradesAmountDue);
1849    model.set('subscrPricePerMonth', subscrPricePerMonth);
1850    model.set('taxDue', taxDue);
1851    model.set('taxRate', data.appliedTaxRate);
1852    model.set('couponDiscount', couponDiscount);
1853    model.set('newUpgradesAdjustmentAmount', newUpgradesAdjustmentAmount);
1854    model.set('upgradeType', data.upgradeType);
1855    model.set('expDate', dayjs(data.subscriptionEndDate).format('MMM D YYYY'));
1856    model.set('appliedTaxRate', data.appliedTaxRate);
1857    model.set('customerCurrency', data.customerCurrency);
1858    model.set('totalAmountCustomerCurrency', data.totalAmountCustomerCurrency);
1859    model.set('paymentIntentId', data.paymentIntentId);
1860    model.set('clientSecret', data.clientSecret);
1861  }
1862
1863  function makeChangesToUrl(upgradeSystemId, newArchivalRate, newSubscrTypeId, trialMode) {
1864    window.history.pushState("", "", window.location.pathname +
1865      '#makePayment/' + upgradeSystemId + '/' +
1866      newArchivalRate + '/' + trialMode + '/' +
1867      (newSubscrTypeId === 2 ? 'Pro' : 'ProPlus'));
1868  }
1869
1870  function showConnectionIssue() {
1871    var connectionIssueView = new ConnectionIssueView();
1872    connectionIssueView.render();
1873    $('#connection-issue').append(connectionIssueView.$el);
1874  }
1875
1876  function _getTermTranslationKey(name) {
1877    if (name.indexOf('1') !== -1) {
1878      return '<span data-l10n-id="year_1"></span>'
1879    }
1880    if (name.indexOf('3') !== -1) {
1881      return '<span data-l10n-id="year_3"></span>'
1882    }
1883    return name;
1884  }
1885
1886  function initializeStripeElements(view) {
1887    var elementStyles = {
1888      base: {
1889        color: '#3c464d',
1890        fontWeight: 300,
1891        fontSize: '14px',
1892        fontSmoothing: 'antialiased',
1893        '::placeholder': {
1894          fontSize: '13px',
1895          color: '#b3b6ba'
1896        }
1897      },
1898      invalid: {
1899        color: '#f15b2a'
1900      },
1901    };
1902
1903    var elementClasses = {
1904      focus: 'focused',
1905      empty: 'empty',
1906      invalid: 'invalid',
1907    };
1908
1909    var elements = wl.app.stripe.elements();
1910    view.cardNumber = elements.create('cardNumber', {
1911      style: elementStyles,
1912      classes: elementClasses,
1913    });
1914    view.cardExpiry = elements.create('cardExpiry', {
1915      style: elementStyles,
1916      classes: elementClasses,
1917    });
1918    view.cardCvc = elements.create('cardCvc', {
1919      style: elementStyles,
1920      classes: elementClasses,
1921    });
1922    view.cardNumber.mount('#accountCardNumber');
1923    view.cardExpiry.mount('#accountCardExpiration');
1924    view.cardCvc.mount('#accountCardCvc');
1925    console.log("initialized card element");
1926  }
1927
1928  function logger(text) {
1929    $('#userSubscription').append('<div style="padding: 5px 15px">'+text+'</div>')
1930  }
1931});

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.