SubscriptionHandler.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. const async = require('async')
  2. const RecurlyWrapper = require('./RecurlyWrapper')
  3. const { User } = require('../../models/User')
  4. const { promisifyAll } = require('../../util/promises')
  5. const logger = require('logger-sharelatex')
  6. const SubscriptionUpdater = require('./SubscriptionUpdater')
  7. const LimitationsManager = require('./LimitationsManager')
  8. const EmailHandler = require('../Email/EmailHandler')
  9. const Events = require('../../infrastructure/Events')
  10. const Analytics = require('../Analytics/AnalyticsManager')
  11. const SubscriptionHandler = {
  12. validateNoSubscriptionInRecurly(userId, callback) {
  13. if (callback == null) {
  14. callback = function() {}
  15. }
  16. RecurlyWrapper.listAccountActiveSubscriptions(userId, function(
  17. error,
  18. subscriptions
  19. ) {
  20. if (subscriptions == null) {
  21. subscriptions = []
  22. }
  23. if (error != null) {
  24. return callback(error)
  25. }
  26. if (subscriptions.length > 0) {
  27. SubscriptionUpdater.syncSubscription(subscriptions[0], userId, function(
  28. error
  29. ) {
  30. if (error != null) {
  31. return callback(error)
  32. }
  33. callback(null, false)
  34. })
  35. } else {
  36. callback(null, true)
  37. }
  38. })
  39. },
  40. createSubscription(user, subscriptionDetails, recurlyTokenIds, callback) {
  41. SubscriptionHandler.validateNoSubscriptionInRecurly(user._id, function(
  42. error,
  43. valid
  44. ) {
  45. if (error != null) {
  46. return callback(error)
  47. }
  48. if (!valid) {
  49. return callback(new Error('user already has subscription in recurly'))
  50. }
  51. RecurlyWrapper.createSubscription(
  52. user,
  53. subscriptionDetails,
  54. recurlyTokenIds,
  55. function(error, recurlySubscription) {
  56. if (error != null) {
  57. return callback(error)
  58. }
  59. return SubscriptionUpdater.syncSubscription(
  60. recurlySubscription,
  61. user._id,
  62. function(error) {
  63. if (error != null) {
  64. return callback(error)
  65. }
  66. return callback()
  67. }
  68. )
  69. }
  70. )
  71. })
  72. },
  73. updateSubscription(user, planCode, couponCode, callback) {
  74. LimitationsManager.userHasV2Subscription(user, function(
  75. err,
  76. hasSubscription,
  77. subscription
  78. ) {
  79. if (err) {
  80. logger.warn(
  81. { err, user_id: user._id, hasSubscription },
  82. 'there was an error checking user v2 subscription'
  83. )
  84. }
  85. if (!hasSubscription) {
  86. return callback()
  87. } else {
  88. return async.series(
  89. [
  90. function(cb) {
  91. if (couponCode == null) {
  92. return cb()
  93. }
  94. RecurlyWrapper.getSubscription(
  95. subscription.recurlySubscription_id,
  96. { includeAccount: true },
  97. function(err, usersSubscription) {
  98. if (err != null) {
  99. return callback(err)
  100. }
  101. RecurlyWrapper.redeemCoupon(
  102. usersSubscription.account.account_code,
  103. couponCode,
  104. cb
  105. )
  106. }
  107. )
  108. },
  109. cb =>
  110. RecurlyWrapper.updateSubscription(
  111. subscription.recurlySubscription_id,
  112. { plan_code: planCode, timeframe: 'now' },
  113. function(error, recurlySubscription) {
  114. if (error != null) {
  115. return callback(error)
  116. }
  117. SubscriptionUpdater.syncSubscription(
  118. recurlySubscription,
  119. user._id,
  120. cb
  121. )
  122. }
  123. )
  124. ],
  125. callback
  126. )
  127. }
  128. })
  129. },
  130. cancelSubscription(user, callback) {
  131. LimitationsManager.userHasV2Subscription(user, function(
  132. err,
  133. hasSubscription,
  134. subscription
  135. ) {
  136. if (err) {
  137. logger.warn(
  138. { err, user_id: user._id, hasSubscription },
  139. 'there was an error checking user v2 subscription'
  140. )
  141. }
  142. if (hasSubscription) {
  143. RecurlyWrapper.cancelSubscription(
  144. subscription.recurlySubscription_id,
  145. function(error) {
  146. if (error != null) {
  147. return callback(error)
  148. }
  149. const emailOpts = {
  150. to: user.email,
  151. first_name: user.first_name
  152. }
  153. const ONE_HOUR_IN_MS = 1000 * 60 * 60
  154. setTimeout(
  155. () =>
  156. EmailHandler.sendEmail(
  157. 'canceledSubscription',
  158. emailOpts,
  159. err => {
  160. if (err != null) {
  161. logger.warn(
  162. { err },
  163. 'failed to send confirmation email for subscription cancellation'
  164. )
  165. }
  166. }
  167. ),
  168. ONE_HOUR_IN_MS
  169. )
  170. Events.emit('cancelSubscription', user._id)
  171. Analytics.recordEvent(user._id, 'subscription-canceled')
  172. callback()
  173. }
  174. )
  175. } else {
  176. callback()
  177. }
  178. })
  179. },
  180. reactivateSubscription(user, callback) {
  181. LimitationsManager.userHasV2Subscription(user, function(
  182. err,
  183. hasSubscription,
  184. subscription
  185. ) {
  186. if (err) {
  187. logger.warn(
  188. { err, user_id: user._id, hasSubscription },
  189. 'there was an error checking user v2 subscription'
  190. )
  191. }
  192. if (hasSubscription) {
  193. RecurlyWrapper.reactivateSubscription(
  194. subscription.recurlySubscription_id,
  195. function(error) {
  196. if (error != null) {
  197. return callback(error)
  198. }
  199. EmailHandler.sendEmail(
  200. 'reactivatedSubscription',
  201. { to: user.email },
  202. err => {
  203. if (err != null) {
  204. logger.warn(
  205. { err },
  206. 'failed to send reactivation confirmation email'
  207. )
  208. }
  209. }
  210. )
  211. Analytics.recordEvent(user._id, 'subscription-reactivated')
  212. callback()
  213. }
  214. )
  215. } else {
  216. callback()
  217. }
  218. })
  219. },
  220. syncSubscription(recurlySubscription, requesterData, callback) {
  221. RecurlyWrapper.getSubscription(
  222. recurlySubscription.uuid,
  223. { includeAccount: true },
  224. function(error, recurlySubscription) {
  225. if (error != null) {
  226. return callback(error)
  227. }
  228. User.findById(
  229. recurlySubscription.account.account_code,
  230. { _id: 1 },
  231. function(error, user) {
  232. if (error != null) {
  233. return callback(error)
  234. }
  235. if (user == null) {
  236. return callback(new Error('no user found'))
  237. }
  238. SubscriptionUpdater.syncSubscription(
  239. recurlySubscription,
  240. user != null ? user._id : undefined,
  241. requesterData,
  242. callback
  243. )
  244. }
  245. )
  246. }
  247. )
  248. },
  249. // attempt to collect past due invoice for customer. Only do that when a) the
  250. // customer is using Paypal and b) there is only one past due invoice.
  251. // This is used because Recurly doesn't always attempt collection of paast due
  252. // invoices after Paypal billing info were updated.
  253. attemptPaypalInvoiceCollection(recurlyAccountCode, callback) {
  254. RecurlyWrapper.getBillingInfo(recurlyAccountCode, (error, billingInfo) => {
  255. if (error) {
  256. return callback(error)
  257. }
  258. if (!billingInfo.paypal_billing_agreement_id) {
  259. // this is not a Paypal user
  260. return callback()
  261. }
  262. RecurlyWrapper.getAccountPastDueInvoices(
  263. recurlyAccountCode,
  264. (error, pastDueInvoices) => {
  265. if (error) {
  266. return callback(error)
  267. }
  268. if (pastDueInvoices.length !== 1) {
  269. // no past due invoices, or more than one. Ignore.
  270. return callback()
  271. }
  272. RecurlyWrapper.attemptInvoiceCollection(
  273. pastDueInvoices[0].invoice_number,
  274. callback
  275. )
  276. }
  277. )
  278. })
  279. },
  280. extendTrial(subscription, daysToExend, callback) {
  281. return RecurlyWrapper.extendTrial(
  282. subscription.recurlySubscription_id,
  283. daysToExend,
  284. callback
  285. )
  286. }
  287. }
  288. SubscriptionHandler.promises = promisifyAll(SubscriptionHandler)
  289. module.exports = SubscriptionHandler