SubscriptionViewModelBuilder.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. /* eslint-disable
  2. handle-callback-err,
  3. max-len,
  4. no-return-assign,
  5. no-unused-vars,
  6. */
  7. // TODO: This file was created by bulk-decaffeinate.
  8. // Fix any style issues and re-enable lint.
  9. /*
  10. * decaffeinate suggestions:
  11. * DS101: Remove unnecessary use of Array.from
  12. * DS102: Remove unnecessary code created because of implicit returns
  13. * DS103: Rewrite code to no longer use __guard__
  14. * DS207: Consider shorter variations of null checks
  15. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  16. */
  17. const Settings = require('settings-sharelatex')
  18. const RecurlyWrapper = require('./RecurlyWrapper')
  19. const PlansLocator = require('./PlansLocator')
  20. const SubscriptionFormatters = require('./SubscriptionFormatters')
  21. const LimitationsManager = require('./LimitationsManager')
  22. const SubscriptionLocator = require('./SubscriptionLocator')
  23. const V1SubscriptionManager = require('./V1SubscriptionManager')
  24. const InstitutionsGetter = require('../Institutions/InstitutionsGetter')
  25. const PublishersGetter = require('../Publishers/PublishersGetter')
  26. const sanitizeHtml = require('sanitize-html')
  27. const logger = require('logger-sharelatex')
  28. const _ = require('underscore')
  29. const async = require('async')
  30. const buildHostedLink = function(recurlySubscription, type) {
  31. const recurlySubdomain = Settings.apis.recurly.subdomain
  32. const hostedLoginToken = recurlySubscription.account.hosted_login_token
  33. let path = ''
  34. if (type === 'billingDetails') {
  35. path = 'billing_info/edit?ht='
  36. }
  37. if (hostedLoginToken && recurlySubdomain) {
  38. return [
  39. 'https://',
  40. recurlySubdomain,
  41. '.recurly.com/account/',
  42. path,
  43. hostedLoginToken
  44. ].join('')
  45. }
  46. }
  47. module.exports = {
  48. buildUsersSubscriptionViewModel(user, callback) {
  49. if (callback == null) {
  50. callback = function(error, data) {}
  51. }
  52. return async.auto(
  53. {
  54. personalSubscription(cb) {
  55. return SubscriptionLocator.getUsersSubscription(user, cb)
  56. },
  57. recurlySubscription: [
  58. 'personalSubscription',
  59. function(cb, { personalSubscription }) {
  60. if (
  61. (personalSubscription != null
  62. ? personalSubscription.recurlySubscription_id
  63. : undefined) == null ||
  64. (personalSubscription != null
  65. ? personalSubscription.recurlySubscription_id
  66. : undefined) === ''
  67. ) {
  68. return cb(null, null)
  69. }
  70. return RecurlyWrapper.getSubscription(
  71. personalSubscription.recurlySubscription_id,
  72. { includeAccount: true },
  73. cb
  74. )
  75. }
  76. ],
  77. recurlyCoupons: [
  78. 'recurlySubscription',
  79. function(cb, { recurlySubscription }) {
  80. if (!recurlySubscription) {
  81. return cb(null, null)
  82. }
  83. const accountId = recurlySubscription.account.account_code
  84. return RecurlyWrapper.getAccountActiveCoupons(accountId, cb)
  85. }
  86. ],
  87. plan: [
  88. 'personalSubscription',
  89. function(cb, { personalSubscription }) {
  90. if (personalSubscription == null) {
  91. return cb()
  92. }
  93. const plan = PlansLocator.findLocalPlanInSettings(
  94. personalSubscription.planCode
  95. )
  96. if (plan == null) {
  97. return cb(
  98. new Error(
  99. `No plan found for planCode '${
  100. personalSubscription.planCode
  101. }'`
  102. )
  103. )
  104. }
  105. return cb(null, plan)
  106. }
  107. ],
  108. memberGroupSubscriptions(cb) {
  109. return SubscriptionLocator.getMemberSubscriptions(user, cb)
  110. },
  111. managedGroupSubscriptions(cb) {
  112. return SubscriptionLocator.getManagedGroupSubscriptions(user, cb)
  113. },
  114. confirmedMemberAffiliations(cb) {
  115. return InstitutionsGetter.getConfirmedAffiliations(user._id, cb)
  116. },
  117. managedInstitutions(cb) {
  118. return InstitutionsGetter.getManagedInstitutions(user._id, cb)
  119. },
  120. managedPublishers(cb) {
  121. return PublishersGetter.getManagedPublishers(user._id, cb)
  122. },
  123. v1SubscriptionStatus(cb) {
  124. return V1SubscriptionManager.getSubscriptionStatusFromV1(
  125. user._id,
  126. function(error, status, v1Id) {
  127. if (error != null) {
  128. return cb(error)
  129. }
  130. return cb(null, status)
  131. }
  132. )
  133. }
  134. },
  135. function(err, results) {
  136. if (err != null) {
  137. return callback(err)
  138. }
  139. let {
  140. personalSubscription,
  141. memberGroupSubscriptions,
  142. managedGroupSubscriptions,
  143. confirmedMemberAffiliations,
  144. managedInstitutions,
  145. managedPublishers,
  146. v1SubscriptionStatus,
  147. recurlySubscription,
  148. recurlyCoupons,
  149. plan
  150. } = results
  151. if (memberGroupSubscriptions == null) {
  152. memberGroupSubscriptions = []
  153. }
  154. if (managedGroupSubscriptions == null) {
  155. managedGroupSubscriptions = []
  156. }
  157. if (confirmedMemberAffiliations == null) {
  158. confirmedMemberAffiliations = []
  159. }
  160. if (managedInstitutions == null) {
  161. managedInstitutions = []
  162. }
  163. if (v1SubscriptionStatus == null) {
  164. v1SubscriptionStatus = {}
  165. }
  166. if (recurlyCoupons == null) {
  167. recurlyCoupons = []
  168. }
  169. if (
  170. (personalSubscription != null
  171. ? personalSubscription.toObject
  172. : undefined) != null
  173. ) {
  174. // Downgrade from Mongoose object, so we can add a recurly and plan attribute
  175. personalSubscription = personalSubscription.toObject()
  176. }
  177. if (plan != null) {
  178. personalSubscription.plan = plan
  179. }
  180. if (personalSubscription != null && recurlySubscription != null) {
  181. const tax =
  182. (recurlySubscription != null
  183. ? recurlySubscription.tax_in_cents
  184. : undefined) || 0
  185. personalSubscription.recurly = {
  186. tax,
  187. taxRate: parseFloat(
  188. __guard__(
  189. recurlySubscription != null
  190. ? recurlySubscription.tax_rate
  191. : undefined,
  192. x => x._
  193. )
  194. ),
  195. billingDetailsLink: buildHostedLink(
  196. recurlySubscription,
  197. 'billingDetails'
  198. ),
  199. accountManagementLink: buildHostedLink(recurlySubscription),
  200. price: SubscriptionFormatters.formatPrice(
  201. (recurlySubscription != null
  202. ? recurlySubscription.unit_amount_in_cents
  203. : undefined) + tax,
  204. recurlySubscription != null
  205. ? recurlySubscription.currency
  206. : undefined
  207. ),
  208. nextPaymentDueAt: SubscriptionFormatters.formatDate(
  209. recurlySubscription != null
  210. ? recurlySubscription.current_period_ends_at
  211. : undefined
  212. ),
  213. currency: recurlySubscription.currency,
  214. state: recurlySubscription.state,
  215. trialEndsAtFormatted: SubscriptionFormatters.formatDate(
  216. recurlySubscription != null
  217. ? recurlySubscription.trial_ends_at
  218. : undefined
  219. ),
  220. trial_ends_at: recurlySubscription.trial_ends_at,
  221. activeCoupons: recurlyCoupons,
  222. account: recurlySubscription.account
  223. }
  224. }
  225. for (let memberGroupSubscription of Array.from(
  226. memberGroupSubscriptions
  227. )) {
  228. if (memberGroupSubscription.teamNotice) {
  229. memberGroupSubscription.teamNotice = sanitizeHtml(
  230. memberGroupSubscription.teamNotice
  231. )
  232. }
  233. }
  234. return callback(null, {
  235. personalSubscription,
  236. managedGroupSubscriptions,
  237. memberGroupSubscriptions,
  238. confirmedMemberAffiliations,
  239. managedInstitutions,
  240. managedPublishers,
  241. v1SubscriptionStatus
  242. })
  243. }
  244. )
  245. },
  246. buildPlansList() {
  247. const { plans } = Settings
  248. const allPlans = {}
  249. plans.forEach(plan => (allPlans[plan.planCode] = plan))
  250. const result = { allPlans }
  251. result.studentAccounts = _.filter(
  252. plans,
  253. plan => plan.planCode.indexOf('student') !== -1
  254. )
  255. result.groupMonthlyPlans = _.filter(
  256. plans,
  257. plan => plan.groupPlan && !plan.annual
  258. )
  259. result.groupAnnualPlans = _.filter(
  260. plans,
  261. plan => plan.groupPlan && plan.annual
  262. )
  263. result.individualMonthlyPlans = _.filter(
  264. plans,
  265. plan =>
  266. !plan.groupPlan &&
  267. !plan.annual &&
  268. plan.planCode !== 'personal' && // Prevent the personal plan from appearing on the change-plans page
  269. plan.planCode.indexOf('student') === -1
  270. )
  271. result.individualAnnualPlans = _.filter(
  272. plans,
  273. plan =>
  274. !plan.groupPlan &&
  275. plan.annual &&
  276. plan.planCode.indexOf('student') === -1
  277. )
  278. return result
  279. }
  280. }
  281. function __guard__(value, transform) {
  282. return typeof value !== 'undefined' && value !== null
  283. ? transform(value)
  284. : undefined
  285. }