SubscriptionGroupHandler.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. const { callbackify } = require('util')
  2. const SubscriptionUpdater = require('./SubscriptionUpdater')
  3. const SubscriptionLocator = require('./SubscriptionLocator')
  4. const SubscriptionController = require('./SubscriptionController')
  5. const { Subscription } = require('../../models/Subscription')
  6. const SessionManager = require('../Authentication/SessionManager')
  7. const RecurlyClient = require('./RecurlyClient')
  8. const PlansLocator = require('./PlansLocator')
  9. const SubscriptionHandler = require('./SubscriptionHandler')
  10. const PLAN_UPGRADE_MAP = {
  11. group_collaborator: 'group_professional',
  12. group_collaborator_educational: 'group_professional_educational',
  13. }
  14. async function removeUserFromGroup(subscriptionId, userIdToRemove) {
  15. await SubscriptionUpdater.promises.removeUserFromGroup(
  16. subscriptionId,
  17. userIdToRemove
  18. )
  19. }
  20. async function replaceUserReferencesInGroups(oldId, newId) {
  21. await Subscription.updateOne({ admin_id: oldId }, { admin_id: newId }).exec()
  22. await _replaceInArray(Subscription, 'manager_ids', oldId, newId)
  23. await _replaceInArray(Subscription, 'member_ids', oldId, newId)
  24. }
  25. async function isUserPartOfGroup(userId, subscriptionId) {
  26. const subscription =
  27. await SubscriptionLocator.promises.getSubscriptionByMemberIdAndId(
  28. userId,
  29. subscriptionId
  30. )
  31. return !!subscription
  32. }
  33. async function getTotalConfirmedUsersInGroup(subscriptionId) {
  34. const subscription =
  35. await SubscriptionLocator.promises.getSubscription(subscriptionId)
  36. return subscription?.member_ids?.length
  37. }
  38. async function _replaceInArray(model, property, oldValue, newValue) {
  39. // Mongo won't let us pull and addToSet in the same query, so do it in
  40. // two. Note we need to add first, since the query is based on the old user.
  41. const query = {}
  42. query[property] = oldValue
  43. const setNewValue = {}
  44. setNewValue[property] = newValue
  45. const setOldValue = {}
  46. setOldValue[property] = oldValue
  47. await model.updateMany(query, { $addToSet: setNewValue })
  48. await model.updateMany(query, { $pull: setOldValue })
  49. }
  50. async function ensureFlexibleLicensingEnabled(plan) {
  51. if (!plan?.canUseFlexibleLicensing) {
  52. throw new Error('The group plan does not support flexible licencing')
  53. }
  54. }
  55. async function getUsersGroupSubscriptionDetails(req) {
  56. const userId = SessionManager.getLoggedInUserId(req.session)
  57. const subscription =
  58. await SubscriptionLocator.promises.getUsersSubscription(userId)
  59. if (!subscription.groupPlan) {
  60. throw new Error('User subscription is not a group plan')
  61. }
  62. const plan = PlansLocator.findLocalPlanInSettings(subscription.planCode)
  63. const recurlySubscription = await RecurlyClient.promises.getSubscription(
  64. subscription.recurlySubscription_id
  65. )
  66. return {
  67. subscription,
  68. recurlySubscription,
  69. plan,
  70. }
  71. }
  72. async function _addSeatsSubscriptionChange(req) {
  73. const adding = req.body.adding
  74. const { recurlySubscription, plan } =
  75. await getUsersGroupSubscriptionDetails(req)
  76. await ensureFlexibleLicensingEnabled(plan)
  77. const userId = SessionManager.getLoggedInUserId(req.session)
  78. const currentAddonQuantity =
  79. recurlySubscription.addOns.find(
  80. addOn => addOn.code === plan.membersLimitAddOn
  81. )?.quantity ?? 0
  82. // Keeps only the new total quantity of addon
  83. const nextAddonQuantity = currentAddonQuantity + adding
  84. const changeRequest = recurlySubscription.getRequestForAddOnUpdate(
  85. plan.membersLimitAddOn,
  86. nextAddonQuantity
  87. )
  88. return {
  89. changeRequest,
  90. userId,
  91. currentAddonQuantity,
  92. recurlySubscription,
  93. plan,
  94. }
  95. }
  96. async function previewAddSeatsSubscriptionChange(req) {
  97. const { changeRequest, userId, currentAddonQuantity, plan } =
  98. await _addSeatsSubscriptionChange(req)
  99. const paymentMethod = await RecurlyClient.promises.getPaymentMethod(userId)
  100. const subscriptionChange =
  101. await RecurlyClient.promises.previewSubscriptionChange(changeRequest)
  102. const subscriptionChangePreview =
  103. await SubscriptionController.makeChangePreview(
  104. {
  105. type: 'add-on-update',
  106. addOn: {
  107. code: plan.membersLimitAddOn,
  108. quantity: subscriptionChange.nextAddOns.find(
  109. addon => addon.code === plan.membersLimitAddOn
  110. ).quantity,
  111. prevQuantity: currentAddonQuantity,
  112. },
  113. },
  114. subscriptionChange,
  115. paymentMethod
  116. )
  117. return subscriptionChangePreview
  118. }
  119. async function createAddSeatsSubscriptionChange(req) {
  120. const { changeRequest, userId, recurlySubscription } =
  121. await _addSeatsSubscriptionChange(req)
  122. await RecurlyClient.promises.applySubscriptionChangeRequest(changeRequest)
  123. await SubscriptionHandler.promises.syncSubscription(
  124. { uuid: recurlySubscription.id },
  125. userId
  126. )
  127. return { adding: req.body.adding }
  128. }
  129. async function _getUpgradeTargetPlanCodeMaybeThrow(subscription) {
  130. if (!Object.keys(PLAN_UPGRADE_MAP).includes(subscription.planCode)) {
  131. throw new Error('Not eligible for group plan upgrade')
  132. }
  133. return PLAN_UPGRADE_MAP[subscription.planCode]
  134. }
  135. async function _getGroupPlanUpgradeChangeRequest(ownerId) {
  136. const olSubscription =
  137. await SubscriptionLocator.promises.getUsersSubscription(ownerId)
  138. const newPlanCode = await _getUpgradeTargetPlanCodeMaybeThrow(olSubscription)
  139. const recurlySubscription = await RecurlyClient.promises.getSubscription(
  140. olSubscription.recurlySubscription_id
  141. )
  142. return recurlySubscription.getRequestForFlexibleLicensingGroupPlanUpgrade(
  143. newPlanCode
  144. )
  145. }
  146. async function getGroupPlanUpgradePreview(ownerId) {
  147. const changeRequest = await _getGroupPlanUpgradeChangeRequest(ownerId)
  148. const subscriptionChange =
  149. await RecurlyClient.promises.previewSubscriptionChange(changeRequest)
  150. const paymentMethod = await RecurlyClient.promises.getPaymentMethod(ownerId)
  151. return SubscriptionController.makeChangePreview(
  152. {
  153. type: 'group-plan-upgrade',
  154. prevPlan: {
  155. name: subscriptionChange.subscription.planName,
  156. },
  157. },
  158. subscriptionChange,
  159. paymentMethod
  160. )
  161. }
  162. async function upgradeGroupPlan(ownerId) {
  163. const changeRequest = await _getGroupPlanUpgradeChangeRequest(ownerId)
  164. await RecurlyClient.promises.applySubscriptionChangeRequest(changeRequest)
  165. await SubscriptionHandler.promises.syncSubscription(
  166. { uuid: changeRequest.subscription.id },
  167. ownerId
  168. )
  169. }
  170. module.exports = {
  171. removeUserFromGroup: callbackify(removeUserFromGroup),
  172. replaceUserReferencesInGroups: callbackify(replaceUserReferencesInGroups),
  173. ensureFlexibleLicensingEnabled: callbackify(ensureFlexibleLicensingEnabled),
  174. getTotalConfirmedUsersInGroup: callbackify(getTotalConfirmedUsersInGroup),
  175. isUserPartOfGroup: callbackify(isUserPartOfGroup),
  176. getGroupPlanUpgradePreview: callbackify(getGroupPlanUpgradePreview),
  177. upgradeGroupPlan: callbackify(upgradeGroupPlan),
  178. promises: {
  179. removeUserFromGroup,
  180. replaceUserReferencesInGroups,
  181. ensureFlexibleLicensingEnabled,
  182. getTotalConfirmedUsersInGroup,
  183. isUserPartOfGroup,
  184. getUsersGroupSubscriptionDetails,
  185. previewAddSeatsSubscriptionChange,
  186. createAddSeatsSubscriptionChange,
  187. getGroupPlanUpgradePreview,
  188. upgradeGroupPlan,
  189. },
  190. }