diff --git a/src/app/config.js b/src/app/config.js index 1f317c4e76..342fad87c9 100644 --- a/src/app/config.js +++ b/src/app/config.js @@ -115,6 +115,18 @@ angular controller: 'settingsCreateOrganizationController', data: { pageTitle: 'Create Organization' } }) + .state('backend.user.settingsBilling', { + url: '^/settings/billing', + templateUrl: 'app/settings/views/settingsBilling.html', + controller: 'settingsBillingController', + data: { pageTitle: 'Billing' } + }) + .state('backend.user.settingsPremium', { + url: '^/settings/premium', + templateUrl: 'app/settings/views/settingsPremium.html', + controller: 'settingsPremiumController', + data: { pageTitle: 'Go Premium' } + }) .state('backend.user.tools', { url: '^/tools', templateUrl: 'app/tools/views/tools.html', diff --git a/src/app/organization/views/organizationBilling.html b/src/app/organization/views/organizationBilling.html index b499d23b82..7d6ff2cf46 100644 --- a/src/app/organization/views/organizationBilling.html +++ b/src/app/organization/views/organizationBilling.html @@ -19,7 +19,7 @@ Reinstate Plan -
+

Plan

@@ -78,7 +78,7 @@
-
+

User Seats

@@ -99,7 +99,7 @@
-
+

Payment Method

@@ -122,7 +122,7 @@
-
+

Charges

@@ -140,7 +140,7 @@ {{charge.date | date: format: mediumDate}} - + {{charge.paymentSource}} @@ -155,7 +155,7 @@
diff --git a/src/app/services/apiService.js b/src/app/services/apiService.js index 17cc386849..7178ac84e8 100644 --- a/src/app/services/apiService.js +++ b/src/app/services/apiService.js @@ -116,7 +116,11 @@ putKeys: { url: _apiUri + '/accounts/keys', method: 'POST', params: {} }, putKey: { url: _apiUri + '/accounts/key', method: 'POST', params: {} }, 'import': { url: _apiUri + '/accounts/import', method: 'POST', params: {} }, - postDelete: { url: _apiUri + '/accounts/delete', method: 'POST', params: {} } + postDelete: { url: _apiUri + '/accounts/delete', method: 'POST', params: {} }, + postPremium: { url: _apiUri + '/accounts/premium', method: 'POST', params: {} }, + putCancelPremium: { url: _apiUri + '/accounts/cancel-premium', method: 'POST', params: {} }, + putReinstatePremium: { url: _apiUri + '/accounts/reinstate-premium', method: 'POST', params: {} }, + getBilling: { url: _apiUri + '/accounts/billing', method: 'GET', params: {} } }); _service.twoFactor = $resource(_apiUri + '/two-factor', {}, { diff --git a/src/app/settings/settingsBillingController.js b/src/app/settings/settingsBillingController.js new file mode 100644 index 0000000000..4aa1aac704 --- /dev/null +++ b/src/app/settings/settingsBillingController.js @@ -0,0 +1,152 @@ +angular + .module('bit.settings') + + .controller('settingsBillingController', function ($scope, apiService, authService, $state, $uibModal, toastr, $analytics) { + $scope.charges = []; + $scope.paymentSource = null; + $scope.subscription = null; + $scope.loading = true; + + $scope.$on('$viewContentLoaded', function () { + load(); + }); + + $scope.changePayment = function () { + var modal = $uibModal.open({ + animation: true, + templateUrl: 'app/organization/views/settingsBillingChangePayment.html', + controller: 'settingsBillingChangePaymentController', + resolve: { + existingPaymentMethod: function () { + return $scope.paymentSource ? $scope.paymentSource.description : null; + } + } + }); + + modal.result.then(function () { + load(); + }); + }; + + $scope.adjustStorage = function (add) { + var modal = $uibModal.open({ + animation: true, + templateUrl: 'app/settings/views/settingsBillingAdjustStorage.html', + controller: 'settingsBillingAdjustStorageController', + resolve: { + add: function () { + return add; + } + } + }); + + modal.result.then(function () { + load(); + }); + }; + + $scope.cancel = function () { + if (!confirm('Are you sure you want to cancel? You will lose access to all premium features at the end ' + + 'of this billing cycle.')) { + return; + } + + apiService.accounts.putCancelPremium({}, {}) + .$promise.then(function (response) { + $analytics.eventTrack('Canceled Premium'); + toastr.success('Premium subscription has been canceled.'); + load(); + }); + }; + + $scope.reinstate = function () { + if (!confirm('Are you sure you want to remove the cancellation request and reinstate your premium membership?')) { + return; + } + + apiService.accounts.putReinstatePremium({}, {}) + .$promise.then(function (response) { + $analytics.eventTrack('Reinstated Premium'); + toastr.success('Premium cancellation request has been removed.'); + load(); + }); + }; + + function load() { + authService.getUserProfile().then(function (profile) { + $scope.premium = profile.premium; + if (!profile.premium) { + return null; + } + + return apiService.accounts.getBilling({}).$promise; + }).then(function (billing) { + if (!billing) { + return $state.go('backend.user.settingsPremium'); + } + + var i = 0; + + $scope.subscription = null; + if (billing && billing.Subscription) { + $scope.subscription = { + trialEndDate: billing.Subscription.TrialEndDate, + cancelledDate: billing.Subscription.CancelledDate, + status: billing.Subscription.Status, + cancelled: billing.Subscription.Status === 'cancelled', + markedForCancel: billing.Subscription.Status === 'active' && billing.Subscription.CancelledDate + }; + } + + $scope.nextInvoice = null; + if (billing && billing.UpcomingInvoice) { + $scope.nextInvoice = { + date: billing.UpcomingInvoice.Date, + amount: billing.UpcomingInvoice.Amount + }; + } + + if (billing && billing.Subscription && billing.Subscription.Items) { + $scope.subscription.items = []; + for (i = 0; i < billing.Subscription.Items.length; i++) { + $scope.subscription.items.push({ + amount: billing.Subscription.Items[i].Amount, + name: billing.Subscription.Items[i].Name, + interval: billing.Subscription.Items[i].Interval, + qty: billing.Subscription.Items[i].Quantity + }); + } + } + + $scope.paymentSource = null; + if (billing && billing.PaymentSource) { + $scope.paymentSource = { + type: billing.PaymentSource.Type, + description: billing.PaymentSource.Description, + cardBrand: billing.PaymentSource.CardBrand + }; + } + + var charges = []; + if (billing && billing.Charges) { + for (i = 0; i < billing.Charges.length; i++) { + charges.push({ + date: billing.Charges[i].CreatedDate, + paymentSource: billing.Charges[i].PaymentSource ? + billing.Charges[i].PaymentSource.Description : '-', + amount: billing.Charges[i].Amount, + status: billing.Charges[i].Status, + failureMessage: billing.Charges[i].FailureMessage, + refunded: billing.Charges[i].Refunded, + partiallyRefunded: billing.Charges[i].PartiallyRefunded, + refundedAmount: billing.Charges[i].RefundedAmount, + invoiceId: billing.Charges[i].InvoiceId + }); + } + } + $scope.charges = charges; + + $scope.loading = false; + }); + } + }); diff --git a/src/app/settings/settingsPremiumController.js b/src/app/settings/settingsPremiumController.js new file mode 100644 index 0000000000..589d5f24f7 --- /dev/null +++ b/src/app/settings/settingsPremiumController.js @@ -0,0 +1,39 @@ +angular + .module('bit.settings') + + .controller('settingsPremiumController', function ($scope, $state, apiService, toastr, $analytics, authService, stripe) { + authService.getUserProfile().then(function (profile) { + if (profile.premium) { + return $state.go('backend.user.settingsBilling'); + } + }); + + $scope.storageGbPrice = 4; + $scope.premiumPrice = 10; + + $scope.model = { + additionalStorageGb: null + }; + + $scope.totalPrice = function () { + return $scope.premiumPrice + (($scope.model.additionalStorageGb || 0) * $scope.storageGbPrice); + }; + + $scope.submit = function (model) { + $scope.submitPromise = stripe.card.createToken(model.card).then(function (response) { + var request = { + paymentToken: response.id, + additionalStorageGb: model.additionalStorageGb + }; + + return apiService.accounts.postPremium(request).$promise; + }).then(function (result) { + $analytics.eventTrack('Signed Up Premium'); + return authService.refreshAccessToken(); + }).then(function () { + return $state.go('backend.user.settingsBilling'); + }).then(function () { + toastr.success('Premium upgrade complete.', 'Success'); + }); + }; + }); diff --git a/src/app/settings/views/settingsBilling.html b/src/app/settings/views/settingsBilling.html new file mode 100644 index 0000000000..526c43e161 --- /dev/null +++ b/src/app/settings/views/settingsBilling.html @@ -0,0 +1,132 @@ +
+

Billing manage your membership

+
+
+
+
+

Premium Membership

+
+
+
+
+
+
Status
+
{{(subscription && subscription.status) || '-'}}
+
Next Charge
+
{{nextInvoice ? ((nextInvoice.date | date: format: mediumDate) + ', ' + (nextInvoice.amount | currency:'$')) : '-'}}
+
+
+
+ Details +
+ Loading... +
+
+ + + + + + + +
+ {{item.name}} {{item.qty > 1 ? '×' + item.qty : ''}} + @ {{item.amount | currency:'$'}} + {{(item.qty * item.amount) | currency:'$'}} /{{item.interval}}
+
+
+
+
+ +
+
+
+

Storage

+
+
+

You membership has a total of x GB of encrypted file storage. You are currently using y GB.

+
+
+ 56% +
+
+
+ +
+
+
+

Payment Method

+
+
+
+ Loading... +
+
+ No payment method on file. +
+
+ + {{paymentSource.description}} +
+
+ +
+
+
+

Charges

+
+
+
+ Loading... +
+
+ No charges. +
+
+ + + + + + + + + +
+ {{charge.date | date: format: mediumDate}} + + {{charge.paymentSource}} + + {{charge.status}} + + {{charge.amount | currency:'$'}} +
+
+
+ +
+
diff --git a/src/app/settings/views/settingsPremium.html b/src/app/settings/views/settingsPremium.html new file mode 100644 index 0000000000..2845ff1f63 --- /dev/null +++ b/src/app/settings/views/settingsPremium.html @@ -0,0 +1,438 @@ +
+

Premiumget started today!

+
+
+
+
+
+
+
+

Sign up for a premium membership and get:

+
    +
  • + + 1 GB of encrypted file storage +
  • +
  • + + Additional two-step login options such as YubiKey, FIDO U2F, and Duo. +
  • +
  • + + TOTP verification code (2FA) generator for logins in your vault. +
  • +
  • + + Priority customer support. +
  • +
  • + + All future premium features. More coming soon! +
  • +
+
+
+ all for just
+ {{premiumPrice | currency:"$":0}} /year +
+
+
+
+
+
+

Addons

+
+
+
+ +

+ Your plan comes with 1 GB of encrypted file storage. You can add additional + storage for {{storageGbPrice | currency:"$":0}} per GB /year. +

+
+
+ +
+
+
+
+
+
+
+

Billing Summary

+
+
+ Premium Membership: + {{premiumPrice | currency:"$"}}
+ Additional Storage: + {{model.additionalStorageGb || 0}} GB × {{storageGbPrice | currency:"$"}} = + {{(model.additionalStorageGb || 0) * storageGbPrice | currency:"$"}} +
+ +
+
+
+

Payment Information

+
+
+
+
+
+ + +
+
+
+ +
    +
  • +
  • +
  • +
  • +
  • +
  • +
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
+
+
diff --git a/src/app/views/userLayout.html b/src/app/views/userLayout.html index 4da1bfa7c7..c01f7df47d 100644 --- a/src/app/views/userLayout.html +++ b/src/app/views/userLayout.html @@ -78,15 +78,26 @@
  • + $state.is('backend.user.settingsCreateOrg') || $state.is('backend.user.settingsTwoStep') || + $state.is('backend.user.settingsPremium') || $state.is('backend.user.settingsBilling')}"> Settings