SubscriptionHelper.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. const { formatCurrency } = require('../../util/currency')
  2. const GroupPlansData = require('./GroupPlansData')
  3. const { isStandaloneAiAddOnPlanCode } = require('./AiHelper')
  4. /**
  5. * If the user changes to a less expensive plan, we shouldn't apply the change immediately.
  6. * This is to avoid unintended/artifical credits on users Recurly accounts.
  7. */
  8. function shouldPlanChangeAtTermEnd(oldPlan, newPlan, isInTrial) {
  9. if (isInTrial) {
  10. // we should always upgrade or downgrade immediately if actively in trial
  11. return false
  12. }
  13. if (
  14. oldPlan.annual === newPlan.annual &&
  15. isStandaloneAiAddOnPlanCode(oldPlan.planCode) &&
  16. !isStandaloneAiAddOnPlanCode(newPlan.planCode)
  17. ) {
  18. // changing from an standalone AI add-on plan to a non-AI plan should not be considered a downgrade
  19. return false
  20. }
  21. return oldPlan.price_in_cents > newPlan.price_in_cents
  22. }
  23. /**
  24. * @import { CurrencyCode } from '../../../../types/subscription/currency'
  25. */
  26. /**
  27. * @typedef {Object} PlanToPrice
  28. * @property {string} collaborator
  29. * @property {string} professional
  30. */
  31. /**
  32. * @typedef {Object} LocalizedGroupPrice
  33. * @property {PlanToPrice} price
  34. * @property {PlanToPrice} pricePerUser
  35. */
  36. /**
  37. * @param {CurrencyCode} recommendedCurrency
  38. * @param {string} locale
  39. * @returns {LocalizedGroupPrice}
  40. */
  41. function generateInitialLocalizedGroupPrice(recommendedCurrency, locale) {
  42. const INITIAL_LICENSE_SIZE = 2
  43. // the price is in cents, so divide by 100 to get the value
  44. const collaboratorPrice =
  45. GroupPlansData.enterprise.collaborator[recommendedCurrency][
  46. INITIAL_LICENSE_SIZE
  47. ].price_in_cents / 100
  48. const collaboratorPricePerUser = collaboratorPrice / INITIAL_LICENSE_SIZE
  49. const professionalPrice =
  50. GroupPlansData.enterprise.professional[recommendedCurrency][
  51. INITIAL_LICENSE_SIZE
  52. ].price_in_cents / 100
  53. const professionalPricePerUser = professionalPrice / INITIAL_LICENSE_SIZE
  54. /**
  55. * @param {number} price
  56. * @returns {string}
  57. */
  58. const formatPrice = price =>
  59. formatCurrency(price, recommendedCurrency, locale, true)
  60. return {
  61. price: {
  62. collaborator: formatPrice(collaboratorPrice),
  63. professional: formatPrice(professionalPrice),
  64. },
  65. pricePerUser: {
  66. collaborator: formatPrice(collaboratorPricePerUser),
  67. professional: formatPrice(professionalPricePerUser),
  68. },
  69. }
  70. }
  71. function isPaidSubscription(subscription) {
  72. const hasRecurlySubscription =
  73. subscription?.recurlySubscription_id &&
  74. subscription?.recurlySubscription_id !== ''
  75. const hasStripeSubscription =
  76. subscription?.paymentProvider?.subscriptionId &&
  77. subscription?.paymentProvider?.subscriptionId !== ''
  78. return !!(subscription && (hasRecurlySubscription || hasStripeSubscription))
  79. }
  80. function isIndividualActivePaidSubscription(subscription) {
  81. return (
  82. isPaidSubscription(subscription) &&
  83. subscription?.groupPlan === false &&
  84. subscription?.recurlyStatus?.state !== 'canceled' &&
  85. subscription?.paymentProvider?.state !== 'canceled'
  86. )
  87. }
  88. function getPaymentProviderSubscriptionId(subscription) {
  89. if (subscription?.recurlySubscription_id) {
  90. return subscription.recurlySubscription_id
  91. }
  92. if (subscription?.paymentProvider?.subscriptionId) {
  93. return subscription.paymentProvider.subscriptionId
  94. }
  95. return null
  96. }
  97. function getPaidSubscriptionState(subscription) {
  98. if (subscription?.recurlyStatus?.state) {
  99. return subscription.recurlyStatus.state
  100. }
  101. if (subscription?.paymentProvider?.state) {
  102. return subscription.paymentProvider.state
  103. }
  104. return null
  105. }
  106. function getSubscriptionTrialStartedAt(subscription) {
  107. if (subscription?.recurlyStatus?.trialStartedAt) {
  108. return subscription.recurlyStatus?.trialStartedAt
  109. }
  110. return subscription?.paymentProvider?.trialStartedAt
  111. }
  112. function getSubscriptionTrialEndsAt(subscription) {
  113. if (subscription?.recurlyStatus?.trialEndsAt) {
  114. return subscription.recurlyStatus?.trialEndsAt
  115. }
  116. return subscription?.paymentProvider?.trialEndsAt
  117. }
  118. function isInTrial(trialEndsAt) {
  119. if (!trialEndsAt) {
  120. return false
  121. }
  122. return trialEndsAt.getTime() > Date.now()
  123. }
  124. module.exports = {
  125. shouldPlanChangeAtTermEnd,
  126. generateInitialLocalizedGroupPrice,
  127. isPaidSubscription,
  128. isIndividualActivePaidSubscription,
  129. getPaymentProviderSubscriptionId,
  130. getPaidSubscriptionState,
  131. getSubscriptionTrialStartedAt,
  132. getSubscriptionTrialEndsAt,
  133. isInTrial,
  134. }