380 lines
		
	
	
		
			No EOL
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			380 lines
		
	
	
		
			No EOL
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * Helper service for loading, changing and working with subscription plans.
 | |
|  */
 | |
| angular.module('quay')
 | |
|        .factory('PlanService', ['KeyService', 'UserService', 'CookieService', 'ApiService', 'Features', 'Config',
 | |
| 
 | |
| function(KeyService, UserService, CookieService, ApiService, Features, Config) {
 | |
|   var plans = null;
 | |
|   var planDict = {};
 | |
|   var planService = {};
 | |
|   var listeners = [];
 | |
| 
 | |
|   var previousSubscribeFailure = false;
 | |
| 
 | |
|   planService.getFreePlan = function() {
 | |
|     return 'free';
 | |
|   };
 | |
| 
 | |
|   planService.registerListener = function(obj, callback) {
 | |
|     listeners.push({'obj': obj, 'callback': callback});
 | |
|   };
 | |
| 
 | |
|   planService.unregisterListener = function(obj) {
 | |
|     for (var i = 0; i < listeners.length; ++i) {
 | |
|       if (listeners[i].obj == obj) {
 | |
|         listeners.splice(i, 1);
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   planService.notePlan = function(planId) {
 | |
|     if (Features.BILLING) {
 | |
|       CookieService.putSession('quay.notedplan', planId);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   planService.isOrgCompatible = function(plan) {
 | |
|     return plan['stripeId'] == planService.getFreePlan() || plan['bus_features'];
 | |
|   };
 | |
| 
 | |
|   planService.getMatchingBusinessPlan = function(callback) {
 | |
|     planService.getPlans(function() {
 | |
|       planService.getSubscription(null, function(sub) {
 | |
|         var plan = planDict[sub.plan];
 | |
|         if (!plan) {
 | |
|           planService.getMinimumPlan(0, true, callback);
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         var count = Math.max(sub.usedPrivateRepos, plan.privateRepos);
 | |
|         planService.getMinimumPlan(count, true, callback);
 | |
|       }, function() {
 | |
|         planService.getMinimumPlan(0, true, callback);
 | |
|       });
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   planService.handleNotedPlan = function() {
 | |
|     var planId = planService.getAndResetNotedPlan();
 | |
|     if (!planId || !Features.BILLING) { return false; }
 | |
| 
 | |
|     UserService.load(function() {
 | |
|       if (UserService.currentUser().anonymous) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       planService.getPlan(planId, function(plan) {
 | |
|         if (planService.isOrgCompatible(plan)) {
 | |
|           document.location = '/organizations/new/?plan=' + planId;
 | |
|         } else {
 | |
|           document.location = '/user?plan=' + planId;
 | |
|         }
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     return true;
 | |
|   };
 | |
| 
 | |
|   planService.getAndResetNotedPlan = function() {
 | |
|     var planId = CookieService.get('quay.notedplan');
 | |
|     CookieService.clear('quay.notedplan');
 | |
|     return planId;
 | |
|   };
 | |
| 
 | |
|   planService.handleCardError = function(resp) {
 | |
|     if (!planService.isCardError(resp)) { return; }
 | |
| 
 | |
|     bootbox.dialog({
 | |
|       "message": resp.data.carderror,
 | |
|       "title": "Credit card issue",
 | |
|       "buttons": {
 | |
|         "close": {
 | |
|           "label": "Close",
 | |
|           "className": "btn-primary"
 | |
|         }
 | |
|       }
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   planService.isCardError = function(resp) {
 | |
|     return resp && resp.data && resp.data.carderror;
 | |
|   };
 | |
| 
 | |
|   planService.verifyLoaded = function(callback) {
 | |
|     if (!Features.BILLING) { return; }
 | |
| 
 | |
|     if (plans && plans.length) {
 | |
|       callback(plans);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     ApiService.listPlans().then(function(data) {
 | |
|       plans = data.plans || [];
 | |
|       for(var i = 0; i < plans.length; i++) {
 | |
|         planDict[plans[i].stripeId] = plans[i];
 | |
|       }
 | |
|       callback(plans);
 | |
|     }, function() { callback([]); });
 | |
|   };
 | |
| 
 | |
|   planService.getPlans = function(callback, opt_includePersonal) {
 | |
|     planService.verifyLoaded(function(plans) {
 | |
|       var filtered = [];
 | |
|       for (var i = 0; i < plans.length; ++i) {
 | |
|         var plan = plans[i];
 | |
|         if (plan['deprecated']) { continue; }
 | |
|         if (!opt_includePersonal && !planService.isOrgCompatible(plan)) { continue; }
 | |
|         filtered.push(plan);
 | |
|       }
 | |
|       callback(filtered);
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   planService.getPlan = function(planId, callback) {
 | |
|     planService.getPlanIncludingDeprecated(planId, function(plan) {
 | |
|       if (!plan['deprecated']) {
 | |
|         callback(plan);
 | |
|       }
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   planService.getPlanIncludingDeprecated = function(planId, callback) {
 | |
|     planService.verifyLoaded(function() {
 | |
|       if (planDict[planId]) {
 | |
|         callback(planDict[planId]);
 | |
|       }
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   planService.getPlanImmediately = function(planId) {
 | |
|     // Get the plan by name, without bothering to check if the plans are loaded.
 | |
|     // This method will return undefined if planId is undefined or null, or if
 | |
|     // the planDict has not yet been loaded.
 | |
|     return planDict[planId];
 | |
|   };
 | |
| 
 | |
|   planService.getMinimumPlan = function(privateCount, isBusiness, callback) {
 | |
|     planService.getPlans(function(plans) {
 | |
|       for (var i = 0; i < plans.length; i++) {
 | |
|         var plan = plans[i];
 | |
|         if (plan.privateRepos >= privateCount) {
 | |
|           callback(plan);
 | |
|           return;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       callback(null);
 | |
|     }, /* include personal */!isBusiness);
 | |
|   };
 | |
| 
 | |
|   planService.getSubscription = function(orgname, success, failure) {
 | |
|     if (!Features.BILLING) { return; }
 | |
| 
 | |
|     ApiService.getSubscription(orgname).then(success, failure);
 | |
|   };
 | |
| 
 | |
|   planService.setSubscription = function(orgname, planId, success, failure, opt_token) {
 | |
|     if (!Features.BILLING) { return; }
 | |
| 
 | |
|     var subscriptionDetails = {
 | |
|       plan: planId
 | |
|     };
 | |
| 
 | |
|     if (opt_token) {
 | |
|       subscriptionDetails['token'] = opt_token.id;
 | |
|     }
 | |
| 
 | |
|     ApiService.updateSubscription(orgname, subscriptionDetails).then(function(resp) {
 | |
|       success(resp);
 | |
|       planService.getPlan(planId, function(plan) {
 | |
|         for (var i = 0; i < listeners.length; ++i) {
 | |
|           listeners[i]['callback'](plan);
 | |
|         }
 | |
|       });
 | |
|     }, failure);
 | |
|   };
 | |
| 
 | |
|   planService.getCardInfo = function(orgname, callback) {
 | |
|     if (!Features.BILLING) { return; }
 | |
| 
 | |
|     ApiService.getCard(orgname).then(function(resp) {
 | |
|       callback(resp.card);
 | |
|     }, function() {
 | |
|       callback({'is_valid': false});
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   planService.changePlan = function($scope, orgname, planId, callbacks, opt_async) {
 | |
|     if (!Features.BILLING) { return; }
 | |
| 
 | |
|     if (callbacks['started']) {
 | |
|       callbacks['started']();
 | |
|     }
 | |
| 
 | |
|     planService.getSubscription(orgname, function(sub) {
 | |
|       planService.getPlanIncludingDeprecated(sub.plan, function(subscribedPlan) {
 | |
|         planService.changePlanInternal($scope, orgname, planId, callbacks, opt_async,
 | |
|                                        subscribedPlan.price > 0);
 | |
|       });
 | |
|     }, function() {
 | |
|       planService.changePlanInternal($scope, orgname, planId, callbacks, opt_async, false);
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   planService.changePlanInternal = function($scope, orgname, planId, callbacks, opt_async,
 | |
|                                             opt_reuseCard) {
 | |
|     if (!Features.BILLING) { return; }
 | |
| 
 | |
|     planService.getPlan(planId, function(plan) {
 | |
|       if (orgname && !planService.isOrgCompatible(plan)) { return; }
 | |
| 
 | |
|       planService.getCardInfo(orgname, function(cardInfo) {
 | |
|         if (plan.price > 0 && (previousSubscribeFailure || !cardInfo.last4 || !opt_reuseCard)) {
 | |
|           var title = cardInfo.last4 ? 'Subscribe' : 'Start Trial ({{amount}} plan)';
 | |
|           planService.showSubscribeDialog($scope, orgname, planId, callbacks, title, /* async */true);
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         previousSubscribeFailure = false;
 | |
| 
 | |
|         planService.setSubscription(orgname, planId, callbacks['success'], function(resp) {
 | |
|           previousSubscribeFailure = true;
 | |
|           planService.handleCardError(resp);
 | |
|           callbacks['failure'](resp);
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   planService.changeCreditCard = function($scope, orgname, callbacks) {
 | |
|     if (!Features.BILLING) { return; }
 | |
| 
 | |
|     if (callbacks['opening']) {
 | |
|       callbacks['opening']();
 | |
|     }
 | |
| 
 | |
|     var submitted = false;
 | |
|     var submitToken = function(token) {
 | |
|       if (submitted) { return; }
 | |
|       submitted = true;
 | |
|       $scope.$apply(function() {
 | |
|         if (callbacks['started']) {
 | |
|           callbacks['started']();
 | |
|         }
 | |
| 
 | |
|         var cardInfo = {
 | |
|           'token': token.id
 | |
|         };
 | |
| 
 | |
|         ApiService.setCard(orgname, cardInfo).then(callbacks['success'], function(resp) {
 | |
|           planService.handleCardError(resp);
 | |
|           callbacks['failure'](resp);
 | |
|         });
 | |
|       });
 | |
|     };
 | |
| 
 | |
|     var email = planService.getEmail(orgname);
 | |
|     StripeCheckout.open({
 | |
|         key:            KeyService.stripePublishableKey,
 | |
|         email:          email,
 | |
|         currency:       'usd',
 | |
|         name:           'Update credit card',
 | |
|         description:    'Enter your credit card number',
 | |
|         panelLabel:     'Update',
 | |
|         token:          submitToken,
 | |
|         image:          'static/img/quay-icon-stripe.png',
 | |
|         billingAddress: true,
 | |
|         zipCode:        true,
 | |
|         opened:         function() { $scope.$apply(function() { callbacks['opened']() }); },
 | |
|         closed:         function() { $scope.$apply(function() { callbacks['closed']() }); }
 | |
|      });
 | |
|   };
 | |
| 
 | |
|   planService.getEmail = function(orgname) {
 | |
|     var email = null;
 | |
|     if (UserService.currentUser()) {
 | |
|       email = UserService.currentUser().email;
 | |
| 
 | |
|       if (orgname) {
 | |
|         org = UserService.getOrganization(orgname);
 | |
|         if (org) {
 | |
|           emaiil = org.email;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     return email;
 | |
|   };
 | |
| 
 | |
|   planService.showSubscribeDialog = function($scope, orgname, planId, callbacks, opt_title, opt_async) {
 | |
|     if (!Features.BILLING) { return; }
 | |
| 
 | |
|     // If the async parameter is true and this is a browser that does not allow async popup of the
 | |
|     // Stripe dialog (such as Mobile Safari or IE), show a bootbox to show the dialog instead.
 | |
|     var isIE = navigator.appName.indexOf("Internet Explorer") != -1;
 | |
|     var isMobileSafari = navigator.userAgent.match(/(iPod|iPhone|iPad)/) && navigator.userAgent.match(/AppleWebKit/);
 | |
| 
 | |
|     if (opt_async && (isIE || isMobileSafari)) {
 | |
|       bootbox.dialog({
 | |
|         "message": "Please click 'Subscribe' to continue",
 | |
|         "buttons": {
 | |
|           "subscribe": {
 | |
|             "label": "Subscribe",
 | |
|             "className": "btn-primary",
 | |
|             "callback": function() {
 | |
|               planService.showSubscribeDialog($scope, orgname, planId, callbacks, opt_title, false);
 | |
|             }
 | |
|           },
 | |
|           "close": {
 | |
|             "label": "Cancel",
 | |
|             "className": "btn-default"
 | |
|           }
 | |
|         }
 | |
|       });
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (callbacks['opening']) {
 | |
|       callbacks['opening']();
 | |
|     }
 | |
| 
 | |
|     var submitted = false;
 | |
|     var submitToken = function(token) {
 | |
|       if (submitted) { return; }
 | |
|       submitted = true;
 | |
| 
 | |
|       if (Config.MIXPANEL_KEY) {
 | |
|         mixpanel.track('plan_subscribe');
 | |
|       }
 | |
| 
 | |
|       $scope.$apply(function() {
 | |
|         if (callbacks['started']) {
 | |
|           callbacks['started']();
 | |
|         }
 | |
|         planService.setSubscription(orgname, planId, callbacks['success'], callbacks['failure'], token);
 | |
|       });
 | |
|     };
 | |
| 
 | |
|     planService.getPlan(planId, function(planDetails) {
 | |
|       var email = planService.getEmail(orgname);
 | |
|       StripeCheckout.open({
 | |
|         key:            KeyService.stripePublishableKey,
 | |
|         email:          email,
 | |
|         amount:         planDetails.price,
 | |
|         currency:       'usd',
 | |
|         name:           'Quay ' + planDetails.title + ' Subscription',
 | |
|         description:    'Up to ' + planDetails.privateRepos + ' private repositories',
 | |
|         panelLabel:     opt_title || 'Subscribe',
 | |
|         token:          submitToken,
 | |
|         image:          'static/img/quay-icon-stripe.png',
 | |
|         billingAddress: true,
 | |
|         zipCode:        true,
 | |
|         opened:         function() { $scope.$apply(function() { callbacks['opened']() }); },
 | |
|         closed:         function() { $scope.$apply(function() { callbacks['closed']() }); }
 | |
|       });
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   return planService;
 | |
| }]); |