var checkoutApp = angular.module('checkout', ['ngSanitize', 'angularPayments', 'smoothScroll']) .config(function ($interpolateProvider, $httpProvider){ $interpolateProvider.startSymbol('{[').endSymbol(']}'); $httpProvider.defaults.xsrfCookieName = CSRF_COOKIE_NAME; $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken'; }); checkoutApp.controller('authCtrl', ['$scope', '$rootScope', '$attrs', 'CheckoutService', 'smoothScroll', function ($scope, $rootScope, $attrs, CheckoutService, smoothScroll) { $rootScope.currentField = 'creds'; $rootScope.authMode = 'register'; $rootScope.errors = null; $scope.register = function () { CheckoutService.createAccount($scope.email, $scope.password).then(function (data) { $rootScope.errors = null; $scope.userId = data.id; $rootScope.credsValid = true; $rootScope.currentField = 'basic'; CheckoutService.trackCartBail($attrs.productId); if (is_mobile) smoothScroll(document.getElementById('basicinfo'), {offset:100}); }, function (data) { $rootScope.errors = data; analytics.track('Checkout: Create Account Error', { error: $rootScope.errors }); }); }; $scope.updateInfo = function () { CheckoutService.updateAccount($scope.userId, $scope.registerFullName, $scope.registerPhone).then(function (data) { $rootScope.errors = null; $rootScope.basicValid = true; $rootScope.currentField = ''; CheckoutService.trackCartBail($attrs.productId); if (is_mobile) smoothScroll(document.getElementById('payment'), {offset:100}); }, function (data) { $rootScope.errors = data; analytics.track('Checkout: Update Account Error', { error: $rootScope.errors }); }); }; $scope.editInfo = function (){ $scope.cachedInfo = [$scope.loginFullName,$scope.loginPhone]; $scope.editBasic = true; }; $scope.cancelEdit = function () { $scope.loginFullName = $scope.cachedInfo[0]; $scope.loginPhone = $scope.cachedInfo[1]; $scope.editBasic = false; }; $scope.saveEdit = function () { CheckoutService.updateAccount($scope.user.id, $scope.loginFullName, $scope.loginPhone).then(function (data) { $rootScope.user = data; $scope.editBasic = false; $rootScope.errors = null; CheckoutService.trackCartBail($attrs.productId); }, function (data) { $rootScope.errors = data; analytics.track('Checkout: Update Account Error', { error: $rootScope.errors }); }); }; $scope.login = function () { CheckoutService.login($scope.loginEmail, $scope.loginPassword).then(function (data) { $rootScope.user = data; $rootScope.errors = null; CheckoutService.trackCartBail($attrs.productId).then(function (data) { }, function (data) { if (data.non_field_errors[0] == "Your account already has access to this product") { window.location.reload(); } }); CheckoutService.getPaymentProfiles(data.id).then(function (data) { $scope.$parent.paymentProfiles = []; $scope.$parent.paymentProfile = {}; if (data.results.length > 0) { for (var i = 0; i < data.results.length; i++) { var paymentProfile = data.results[i]; paymentProfile.text = paymentProfile.profile_name+" ("+paymentProfile.card_number+")"; paymentProfile.value = paymentProfile.id; $scope.paymentProfiles.push(paymentProfile); } $scope.$parent.paymentProfile = $scope.$parent.paymentProfiles[0]; $scope.$parent.paymentProfiles.push({value: null, profile_name: 'Add new credit card'}); } }); }, function (data) { $rootScope.errors = data; $scope.loading = false; analytics.track('Checkout: Login Error', { error: $rootScope.errors }); }); }; $scope.$watch('user', function(newval) { if (newval){ CheckoutService.getCurrentUser().then( function(data) { $scope.loginPhone = data.profile.phone; }); $scope.loginFullName = newval.first_name + ' ' + newval.last_name; // $scope.loginPhone = newval.profile.phone; $scope.loginEmail = newval.email; $scope.loginPassword = '********'; $scope.authMode = 'login'; } }); $scope.cardClass = function(card) { var cc = ''; switch (card){ case 'v': cc = 'chkot-cc--v'; break; case 'm': cc = 'chkot-cc--m'; break; case 'd': cc = 'chkot-cc--d'; break; case 'a': cc = 'chkot-cc--a'; break; case 'none': cc = 'chkot-cc--none'; break; default: cc = 'chkot-cc--x'; } return cc; } }]); checkoutApp.controller('couponCtrl', ['$scope', '$attrs', 'CheckoutService', 'queryParams', '$rootScope', function ($scope, $attrs, CheckoutService, queryParams, $rootScope) { $scope.couponVisible = false; $scope.coupon = $scope.$parent.coupon; $scope.$watch(function () { return ($scope.$parent.plan) ? $scope.$parent.plan.id : null; }, function () { if ($scope.couponCode) { $scope.applyCoupon($scope.couponCode); } }); $scope.applyCoupon = function () { $rootScope.errors = null; if (!$scope.plan) { return; } if ($scope.couponCode.length < 1) { $scope.couponCode = null; $scope.$parent.coupon = null; return; } CheckoutService.applyCoupon($scope.couponCode, $scope.$parent.plan.id) .then(function (data) { $scope.$parent.coupon = data; }, function (errorMsg) { $rootScope.errors = errorMsg; analytics.track('Checkout: Coupon Error', { coupon: $scope.couponCode }); $scope.$parent.coupon = null; }); }; // Initialize with Query Params var couponQuery = queryParams('coupon'); if (couponQuery) { if (couponQuery == 'Loyal1') { window.location = '?coupon=Loyal2'; } $scope.couponCode = couponQuery; $scope.applyCoupon(); $scope.couponVisible = true; } }]); checkoutApp.controller('checkoutCtrl', ['$scope', '$rootScope', '$attrs', 'CheckoutService', '$window', 'smoothScroll', function ($scope, $rootScope, $attrs, CheckoutService, $window, smoothScroll) { if (!$attrs.packageId) throw new Error('No package ID given for checkoutCtrl'); var packageId = $attrs.packageId; var planId = $attrs.planId; var productId = $attrs.productId; $scope.product = null; $scope.package = null; $scope.plan = null; $scope.plans = null; $scope.coupon = null; $scope.rendered_stripe = false; $rootScope.user = null; $scope.paymentProfiles = null; $scope.paymentProfile = null; $scope.card = null; $scope.scroll = 0; CheckoutService.getCurrentUser().then(function (data) { $rootScope.user = data; $rootScope.authMode = 'login'; CheckoutService.trackCartBail($attrs.productId); CheckoutService.getPaymentProfiles($rootScope.user.id).then(function (data) { $scope.paymentProfiles = []; $scope.paymentProfile = {}; if (data.results.length > 0) { for (var i = 0; i < data.results.length; i++) { var paymentProfile = data.results[i]; paymentProfile.text = paymentProfile.profile_name+" ("+paymentProfile.card_number+")"; paymentProfile.value = paymentProfile.id; $scope.paymentProfiles.push(paymentProfile); } $scope.paymentProfile = $scope.paymentProfiles[0]; } $scope.paymentProfiles.push({value: null, profile_name: 'Add new credit card', new_card:true}); loadProduct(); }); }, function (data) { loadProduct(); }); var loadProduct = function () { CheckoutService.getProductDetail(productId).then(function (data) { $scope.product = data; analytics.track('Viewed Checkout', { logged_in: ($rootScope.user) ? true: false, product_slug: data.slug, products: [ { id: data.id, sku: data.slug, name: data.name } ] }); for (var i = 0; i < data.packages.length; i++) { // Loop through and find current package selected if (data.packages[i].id != packageId) { continue; } $scope.package = data.packages[i]; if ($scope.package.plans.length < 1) { throw 'No plans found for this package'; } $scope.plans = []; $scope.plan = {}; // Build out plans for dropdown for (var j = 0; j < $scope.package.plans.length; j++) { var plan = $scope.package.plans[j]; if (plan.is_hidden) { continue; } plan.text = '$' + plan.price + ' / ' + plan.subscription_interval_display_name; plan.value = plan.id; $scope.plans.push(plan); if (plan.id == planId) { angular.copy(plan, $scope.plan); } } if (!$scope.plan.id) { angular.copy($scope.plans[0], $scope.plan); } break; } }); }; $scope.completeOrder = function () { $scope.sending = true; $rootScope.errors = null; var coupon = null; if ($scope.coupon) { coupon = $scope.coupon.code; } if ($scope.paymentProfile && $scope.paymentProfile.id) { CheckoutService.purchaseWithPaymentProfile($scope.plan.id, coupon, $scope.paymentProfile.id).then(purchaseCallback, purchaseError); } else { // Get rid of NaN chars in expiration, causes exp date to get a too long error function success(data) { CheckoutService.purchaseWithCard($scope.plan.id, coupon, data.token.id).then(purchaseCallback, purchaseError); } $scope.stripeform.update(function(result, error) { if(error !== undefined) { if(typeof error === 'string') { purchaseError({token: [error]}); } else if (error.message) { purchaseError({token: [error.message]}); } } else { success(result); } }); } analytics.track('Checkout: Clicked Purchase'); }; var purchaseCallback = function (data) { // $scope.loading = false; // $scope.order = order; if(data.coupon != null) { if(data.coupon.discount_type == "t") { analytics.track("Trial Started", { 'product': data.item.slug, 'duration': data.coupon.billing_interval }) } } analytics.track('Checkout: Completed', {orderId: data.id}); analytics.alias(data.user.id); analytics.identify(data.user.id); analytics.track('Completed Order', { orderId: data.id, total: data.amount, revenue: data.amount, product_slug: data.item.slug, coupon: (data.coupon) ? data.coupon.code : undefined, currency: 'USD', products: [ { id: data.item.id, sku: data.item.slug, name: data.item.name, packageName: data.package.name, planName: data.plan.name, price: data.amount, quantity: 1, category: '' } ] }); setTimeout(function () { window.location = '/store/order/' + data.id + '/?token=' + data.access_token; }, 500); }; var purchaseError = function (errors) { $scope.sending = false; $rootScope.errors = errors; // smoothScroll(document.body.scrollTop); analytics.track('Checkout: Charge Error', { error: $rootScope.errors }); }; $scope.calculateOrderTotal = function () { if ($scope.coupon) { return $scope.coupon.plan_discounted_price; } if ($scope.plan) { return $scope.plan.price; } } $scope.$watch('basicValid', function(newval) { if(newval === true) { var AddStripeCardForm = new StripeCardEntry(STRIPE_PUBLIC_KEY); AddStripeCardForm.style.base.fontSize="1.333rem"; $scope.stripeform = AddStripeCardForm; document.getElementById("register_create_source").appendChild(AddStripeCardForm.create()); } }); $scope.$watch('paymentProfile', function(newval) { if(!newval || !newval.value && !$scope.rendered_stripe) { var AddStripeCardForm = new StripeCardEntry(STRIPE_PUBLIC_KEY); AddStripeCardForm.style.base.fontSize="1.333rem"; $scope.stripeform = AddStripeCardForm; document.getElementById("checkout_create_source").appendChild(AddStripeCardForm.create()); newval = false; $scope.rendered_stripe = true; } !newval || !newval.value ? $scope.newCard = true : $scope.newCard = false; }); }]); checkoutApp.directive('bindEnter', function () { return function (scope, element, attrs) { element.keydown(function (e) { if (e.keyCode == 13) { scope.$apply(attrs.bindEnter); return false; } }); } }); checkoutApp.directive('autoFocus', function($timeout) { return { restrict: 'A', link: function(scope, element) { $timeout(function(){ element[0].focus(); }, 300); } }; }); checkoutApp.directive('numbersOnly', function () { return { require: 'ngModel', link: function(scope, element, attrs, modelCtrl) { modelCtrl.$parsers.push(function (inputValue) { if (inputValue){ var transformedInput = inputValue.replace(/[^0-9\.]+/g,''); if (transformedInput!=inputValue) { modelCtrl.$setViewValue(transformedInput); modelCtrl.$render(); } } return transformedInput; }); } }; }); checkoutApp.directive('scrollToFixed', function($window) { return { scope: { scroll: '=scrollToFixed' }, link: function(scope, element, attrs) { var windowEl = angular.element($window); var handler = function() { scope.scroll = windowEl.scrollTop(); } windowEl.on('scroll', scope.$apply.bind(scope, handler)); handler(); } }; }); checkoutApp.factory('CheckoutService', function ($http, $rootScope, $q) { return { getProductDetail: function (productId) { var deferred = $q.defer(); $http.get('/api/v2/products/' + productId + '/') .success(function (d) { deferred.resolve(d); }) .error(function (d, status) { }); return deferred.promise; }, applyCoupon: function (couponCode, planId) { var deferred = $q.defer(); $http.get('/api/v2/store/coupons/' + couponCode + '/apply/' + planId + '/') .success(function (d) { deferred.resolve(d); }) .error(function (d, status) { if (status == 404) { deferred.reject({'coupon': ['Coupon does not exist']}) } deferred.reject(d); }); return deferred.promise; }, getCurrentUser: function () { var deferred = $q.defer(); $http.get('/api/v2/account/user/info/') .success(function (d) { deferred.resolve(d); }).error(function (d, status) { deferred.reject(d); }); return deferred.promise; }, getPaymentProfiles: function (userId) { var deferred = $q.defer(); $http.get('/api/v2/account/' + userId + '/payment-profiles/') .success(function (d) { deferred.resolve(d); }).error(function (d, status) { deferred.reject(d); }); return deferred.promise; }, login: function (email, password) { var deferred = $q.defer(); var data = {email: email, password: password}; $http.post('/api/v2/account/user/login/', data) .success(function (d) { deferred.resolve(d); }).error(function (d, status) { deferred.reject(d); }); return deferred.promise; }, createAccount: function (email, password) { var deferred = $q.defer(); var data = {email: email, password: password}; $http.post('/api/v2/account/user/simple_user/', data) .success(function (d) { deferred.resolve(d); }).error(function (d, status) { deferred.reject(d); }); return deferred.promise; }, updateAccount: function (id, fullName, phone){ var deferred = $q.defer(); var data = {id: id, full_name: fullName, phone: phone}; $http.post('/api/v2/account/user/simple_user_update/', data) .success(function (d) { deferred.resolve(d); }).error(function (d, status) { deferred.reject(d); }); return deferred.promise; }, trackCartBail: function (productId) { var deferred = $q.defer(); $http.get('/api/v2/store/checkout/' + productId + '/cart-bail/') .success(function (d) { deferred.resolve(d); }).error(function (d, status) { deferred.reject(d); }); return deferred.promise; }, purchaseWithCard: function (plan, couponCode, token) { var deferred = $q.defer(); var data = {plan: plan, coupon: couponCode, token: token, }; $http.post('/secure/api/v2/store/purchase/', data) .success(function (d) { deferred.resolve(d); }) .error(function (d, status) { console.debug({d, status}) deferred.reject(d); }); return deferred.promise; }, purchaseWithPaymentProfile: function (plan, couponCode, paymentProfile) { var deferred = $q.defer(); var data = {plan: plan, coupon: couponCode, payment_profile: paymentProfile}; $http.post('/secure/api/v2/store/purchase/', data) .success(function (d) { deferred.resolve(d); }) .error(function (d, status) { deferred.reject(d); }); return deferred.promise; } } }); checkoutApp.directive('serverError', function ($compile) { return { require: '?ngModel', restrict: 'A', link: function (scope, element, attrs) { // Render to DOM // attrs.serverError is what came in the html attribute 'server-error' // this is custom here??? var errorField = "errors." + attrs.serverError; var tpl = "