subscription-dashboard.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. import _ from 'lodash'
  2. /* global recurly */
  3. /* eslint-disable
  4. camelcase,
  5. max-len,
  6. no-return-assign,
  7. no-unused-vars,
  8. */
  9. // TODO: This file was created by bulk-decaffeinate.
  10. // Fix any style issues and re-enable lint.
  11. /*
  12. * decaffeinate suggestions:
  13. * DS102: Remove unnecessary code created because of implicit returns
  14. * DS104: Avoid inline assignments
  15. * DS204: Change includes calls to have a more natural evaluation order
  16. * DS207: Consider shorter variations of null checks
  17. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  18. */
  19. import App from '../base'
  20. import getMeta from '../utils/meta'
  21. const SUBSCRIPTION_URL = '/user/subscription/update'
  22. const GROUP_PLAN_MODAL_OPTIONS = getMeta('ol-groupPlanModalOptions')
  23. const ensureRecurlyIsSetup = _.once(() => {
  24. if (typeof recurly === 'undefined' || !recurly) {
  25. return false
  26. }
  27. recurly.configure(getMeta('ol-recurlyApiKey'))
  28. return true
  29. })
  30. function getPricePerUser(price, currencySymbol, size) {
  31. let perUserPrice = price / size
  32. if (perUserPrice % 1 !== 0) {
  33. perUserPrice = perUserPrice.toFixed(2)
  34. }
  35. return `${currencySymbol}${perUserPrice}`
  36. }
  37. App.controller('MetricsEmailController', function ($scope, $http) {
  38. $scope.institutionEmailSubscription = function (institutionId) {
  39. const inst = _.find(window.managedInstitutions, function (institution) {
  40. return institution.v1Id === parseInt(institutionId)
  41. })
  42. if (inst.metricsEmail.optedOutUserIds.includes(window.user_id)) {
  43. return 'Subscribe'
  44. } else {
  45. return 'Unsubscribe'
  46. }
  47. }
  48. $scope.changeInstitutionalEmailSubscription = function (institutionId) {
  49. $scope.subscriptionChanging = true
  50. return $http({
  51. method: 'POST',
  52. url: `/institutions/${institutionId}/emailSubscription`,
  53. headers: {
  54. 'X-CSRF-Token': window.csrfToken,
  55. },
  56. }).then(function successCallback(response) {
  57. window.managedInstitutions = _.map(
  58. window.managedInstitutions,
  59. function (institution) {
  60. if (institution.v1Id === parseInt(institutionId)) {
  61. institution.metricsEmail.optedOutUserIds = response.data
  62. }
  63. return institution
  64. }
  65. )
  66. $scope.subscriptionChanging = false
  67. })
  68. }
  69. })
  70. App.factory('RecurlyPricing', function ($q, MultiCurrencyPricing) {
  71. return {
  72. loadDisplayPriceWithTax: function (planCode, currency, taxRate) {
  73. if (!ensureRecurlyIsSetup()) return
  74. const currencySymbol = MultiCurrencyPricing.plans[currency].symbol
  75. const pricing = recurly.Pricing()
  76. return $q(function (resolve, reject) {
  77. pricing
  78. .plan(planCode, { quantity: 1 })
  79. .currency(currency)
  80. .done(function (price) {
  81. const totalPriceExTax = parseFloat(price.next.total)
  82. let taxAmount = totalPriceExTax * taxRate
  83. if (isNaN(taxAmount)) {
  84. taxAmount = 0
  85. }
  86. let total = totalPriceExTax + taxAmount
  87. if (total % 1 !== 0) {
  88. total = total.toFixed(2)
  89. }
  90. resolve({
  91. total: `${currencySymbol}${total}`,
  92. totalValue: total,
  93. subtotal: `${currencySymbol}${totalPriceExTax.toFixed(2)}`,
  94. tax: `${currencySymbol}${taxAmount.toFixed(2)}`,
  95. includesTax: taxAmount !== 0,
  96. })
  97. })
  98. })
  99. },
  100. }
  101. })
  102. App.controller('ChangePlanToGroupFormController', function ($scope, $modal) {
  103. if (!ensureRecurlyIsSetup()) return
  104. const subscription = getMeta('ol-subscription')
  105. const currency = subscription.recurly.currency
  106. const validCurrencies = GROUP_PLAN_MODAL_OPTIONS.currencies.map(
  107. item => item.code
  108. )
  109. if (validCurrencies.includes(currency)) {
  110. $scope.isValidCurrencyForUpgrade = true
  111. }
  112. $scope.openGroupPlanModal = function () {
  113. const planCode = subscription.plan.planCode
  114. $scope.defaultGroupPlan = planCode.includes('professional')
  115. ? 'professional'
  116. : 'collaborator'
  117. $scope.currentPlanCurrency = currency
  118. $modal.open({
  119. templateUrl: 'groupPlanModalUpgradeTemplate',
  120. controller: 'GroupPlansModalUpgradeController',
  121. scope: $scope,
  122. })
  123. }
  124. })
  125. App.controller(
  126. 'GroupPlansModalUpgradeController',
  127. function ($scope, $modal, $location, $http, RecurlyPricing) {
  128. $scope.options = GROUP_PLAN_MODAL_OPTIONS
  129. $scope.prices = getMeta('ol-groupPlans')
  130. const currency = $scope.currentPlanCurrency
  131. // default selected
  132. $scope.selected = {
  133. plan_code: $scope.defaultGroupPlan || 'collaborator',
  134. currency,
  135. size: '10',
  136. usage: 'enterprise',
  137. }
  138. $scope.recalculatePrice = function () {
  139. const subscription = getMeta('ol-subscription')
  140. const { taxRate } = subscription.recurly
  141. const { usage, plan_code, currency, size } = $scope.selected
  142. $scope.discountEligible = size >= 10
  143. const pricePlaceholder = { total: '...' }
  144. let pricePerUserPlaceholder = '...'
  145. const currencySymbol = $scope.options.currencySymbols[currency]
  146. if (taxRate === 0) {
  147. const basePrice = $scope.prices[usage][plan_code][currency][size]
  148. pricePlaceholder.total = `${currencySymbol}${basePrice}`
  149. pricePerUserPlaceholder = getPricePerUser(
  150. basePrice,
  151. currencySymbol,
  152. size
  153. )
  154. }
  155. $scope.displayPrice = pricePlaceholder // Placeholder while we talk to recurly
  156. $scope.displayPricePerUser = pricePerUserPlaceholder // Placeholder while we talk to recurly
  157. const recurlyPlanCode = `group_${plan_code}_${size}_${usage}`
  158. RecurlyPricing.loadDisplayPriceWithTax(
  159. recurlyPlanCode,
  160. currency,
  161. taxRate
  162. ).then(price => {
  163. $scope.displayPrice = price
  164. $scope.displayPricePerUser = getPricePerUser(
  165. price.totalValue,
  166. currencySymbol,
  167. size
  168. )
  169. })
  170. }
  171. $scope.$watch('selected', $scope.recalculatePrice, true)
  172. $scope.recalculatePrice()
  173. $scope.upgrade = function () {
  174. const { plan_code, size, usage } = $scope.selected
  175. const body = {
  176. _csrf: window.csrfToken,
  177. plan_code: `group_${plan_code}_${size}_${usage}`,
  178. }
  179. $scope.inflight = true
  180. $http
  181. .post(`/user/subscription/update`, body)
  182. .then(() => location.reload())
  183. }
  184. }
  185. )
  186. App.controller(
  187. 'ChangePlanFormController',
  188. function ($scope, $modal, RecurlyPricing) {
  189. if (!ensureRecurlyIsSetup()) return
  190. $scope.changePlan = () =>
  191. $modal.open({
  192. templateUrl: 'confirmChangePlanModalTemplate',
  193. controller: 'ConfirmChangePlanController',
  194. scope: $scope,
  195. })
  196. $scope.cancelPendingPlanChange = () =>
  197. $modal.open({
  198. templateUrl: 'cancelPendingPlanChangeModalTemplate',
  199. controller: 'CancelPendingPlanChangeController',
  200. scope: $scope,
  201. })
  202. $scope.$watch('plan', function (plan) {
  203. if (!plan) return
  204. const planCodesChangingAtTermEnd = getMeta(
  205. 'ol-planCodesChangingAtTermEnd'
  206. )
  207. $scope.planChangesAtTermEnd = false
  208. if (
  209. planCodesChangingAtTermEnd &&
  210. planCodesChangingAtTermEnd.indexOf(plan.planCode) > -1
  211. ) {
  212. $scope.planChangesAtTermEnd = true
  213. }
  214. const planCode = plan.planCode
  215. const subscription = getMeta('ol-subscription')
  216. const { currency, taxRate } = subscription.recurly
  217. $scope.price = '...' // Placeholder while we talk to recurly
  218. RecurlyPricing.loadDisplayPriceWithTax(planCode, currency, taxRate).then(
  219. price => {
  220. $scope.price = price.total
  221. }
  222. )
  223. })
  224. }
  225. )
  226. App.controller(
  227. 'ConfirmChangePlanController',
  228. function ($scope, $modalInstance, $http) {
  229. $scope.confirmChangePlan = function () {
  230. const body = {
  231. plan_code: $scope.plan.planCode,
  232. _csrf: window.csrfToken,
  233. }
  234. $scope.genericError = false
  235. $scope.inflight = true
  236. return $http
  237. .post(`${SUBSCRIPTION_URL}?origin=confirmChangePlan`, body)
  238. .then(() => location.reload())
  239. .catch(() => {
  240. $scope.genericError = true
  241. $scope.inflight = false
  242. })
  243. }
  244. return ($scope.cancel = () => $modalInstance.dismiss('cancel'))
  245. }
  246. )
  247. App.controller(
  248. 'CancelPendingPlanChangeController',
  249. function ($scope, $modalInstance, $http) {
  250. $scope.confirmCancelPendingPlanChange = function () {
  251. const body = {
  252. _csrf: window.csrfToken,
  253. }
  254. $scope.genericError = false
  255. $scope.inflight = true
  256. return $http
  257. .post('/user/subscription/cancel-pending', body)
  258. .then(() => location.reload())
  259. .catch(() => {
  260. $scope.genericError = true
  261. $scope.inflight = false
  262. })
  263. }
  264. return ($scope.cancel = () => $modalInstance.dismiss('cancel'))
  265. }
  266. )
  267. App.controller(
  268. 'LeaveGroupModalController',
  269. function ($scope, $modalInstance, $http) {
  270. $scope.confirmLeaveGroup = function () {
  271. $scope.inflight = true
  272. return $http({
  273. url: '/subscription/group/user',
  274. method: 'DELETE',
  275. params: {
  276. subscriptionId: $scope.subscriptionId,
  277. _csrf: window.csrfToken,
  278. },
  279. })
  280. .then(() => location.reload())
  281. .catch(() => console.log('something went wrong changing plan'))
  282. }
  283. return ($scope.cancel = () => $modalInstance.dismiss('cancel'))
  284. }
  285. )
  286. App.controller('GroupMembershipController', function ($scope, $modal) {
  287. $scope.removeSelfFromGroup = function (subscriptionId) {
  288. $scope.subscriptionId = subscriptionId
  289. return $modal.open({
  290. templateUrl: 'LeaveGroupModalTemplate',
  291. controller: 'LeaveGroupModalController',
  292. scope: $scope,
  293. })
  294. }
  295. })
  296. App.controller('RecurlySubscriptionController', function ($scope) {
  297. const recurlyIsSetup = ensureRecurlyIsSetup()
  298. const subscription = getMeta('ol-subscription')
  299. $scope.showChangePlanButton = recurlyIsSetup && !subscription.groupPlan
  300. if (
  301. window.subscription.recurly.account.has_past_due_invoice &&
  302. window.subscription.recurly.account.has_past_due_invoice._ === 'true'
  303. ) {
  304. $scope.showChangePlanButton = false
  305. }
  306. $scope.recurlyLoadError = !recurlyIsSetup
  307. $scope.switchToDefaultView = () => {
  308. $scope.showCancellation = false
  309. $scope.showChangePlan = false
  310. }
  311. $scope.switchToDefaultView()
  312. $scope.switchToCancellationView = () => {
  313. $scope.showCancellation = true
  314. $scope.showChangePlan = false
  315. }
  316. $scope.switchToChangePlanView = () => {
  317. $scope.showCancellation = false
  318. $scope.showChangePlan = true
  319. }
  320. })
  321. App.controller(
  322. 'RecurlyCancellationController',
  323. function ($scope, RecurlyPricing, $http) {
  324. if (!ensureRecurlyIsSetup()) return
  325. const subscription = getMeta('ol-subscription')
  326. const sevenDaysTime = new Date()
  327. sevenDaysTime.setDate(sevenDaysTime.getDate() + 7)
  328. const freeTrialEndDate = new Date(subscription.recurly.trial_ends_at)
  329. const freeTrialInFuture = freeTrialEndDate > new Date()
  330. const freeTrialExpiresUnderSevenDays = freeTrialEndDate < sevenDaysTime
  331. const isMonthlyCollab =
  332. subscription.plan.planCode.indexOf('collaborator') !== -1 &&
  333. subscription.plan.planCode.indexOf('ann') === -1 &&
  334. !subscription.groupPlan
  335. const stillInFreeTrial = freeTrialInFuture && freeTrialExpiresUnderSevenDays
  336. if (isMonthlyCollab && stillInFreeTrial) {
  337. $scope.showExtendFreeTrial = true
  338. } else if (isMonthlyCollab && !stillInFreeTrial) {
  339. $scope.showDowngradeToStudent = true
  340. } else {
  341. $scope.showBasicCancel = true
  342. }
  343. const { currency, taxRate } = subscription.recurly
  344. $scope.studentPrice = '...' // Placeholder while we talk to recurly
  345. RecurlyPricing.loadDisplayPriceWithTax('student', currency, taxRate).then(
  346. price => {
  347. $scope.studentPrice = price.total
  348. }
  349. )
  350. $scope.downgradeToStudent = function () {
  351. const body = {
  352. plan_code: 'student',
  353. _csrf: window.csrfToken,
  354. }
  355. $scope.inflight = true
  356. return $http
  357. .post(`${SUBSCRIPTION_URL}?origin=downgradeToStudent`, body)
  358. .then(() => location.reload())
  359. .catch(() => console.log('something went wrong changing plan'))
  360. }
  361. $scope.cancelSubscription = function () {
  362. const body = { _csrf: window.csrfToken }
  363. $scope.inflight = true
  364. return $http
  365. .post('/user/subscription/cancel', body)
  366. .then(() => (location.href = '/user/subscription/canceled'))
  367. .catch(() => console.log('something went wrong changing plan'))
  368. }
  369. $scope.extendTrial = function () {
  370. const body = { _csrf: window.csrfToken }
  371. $scope.inflight = true
  372. return $http
  373. .put('/user/subscription/extend', body)
  374. .then(() => location.reload())
  375. .catch(() => console.log('something went wrong changing plan'))
  376. }
  377. }
  378. )