RecurlyEventHandler.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. const SplitTestHandler = require('../SplitTests/SplitTestHandler')
  2. const AnalyticsManager = require('../Analytics/AnalyticsManager')
  3. const SubscriptionEmailHandler = require('./SubscriptionEmailHandler')
  4. const { AI_ADD_ON_CODE } = require('./PaymentProviderEntities')
  5. const { ObjectId } = require('mongodb-legacy')
  6. const INVOICE_SUBSCRIPTION_LIMIT = 10
  7. async function sendRecurlyAnalyticsEvent(event, eventData) {
  8. const userId = _getUserId(eventData)
  9. if (!ObjectId.isValid(userId)) {
  10. return
  11. }
  12. const customerIoEnabled =
  13. await SplitTestHandler.promises.hasUserBeenAssignedToVariant(
  14. {},
  15. userId,
  16. 'customer-io-trial-conversion',
  17. 'enabled',
  18. true
  19. )
  20. eventData['customerio-integration'] = customerIoEnabled || false
  21. switch (event) {
  22. case 'new_subscription_notification':
  23. await _sendSubscriptionStartedEvent(userId, eventData)
  24. break
  25. case 'updated_subscription_notification':
  26. await _sendSubscriptionUpdatedEvent(userId, eventData)
  27. break
  28. case 'canceled_subscription_notification':
  29. await _sendSubscriptionCancelledEvent(userId, eventData)
  30. break
  31. case 'expired_subscription_notification':
  32. await _sendSubscriptionExpiredEvent(userId, eventData)
  33. break
  34. case 'renewed_subscription_notification':
  35. await _sendSubscriptionRenewedEvent(userId, eventData)
  36. break
  37. case 'reactivated_account_notification':
  38. await _sendSubscriptionReactivatedEvent(userId, eventData)
  39. break
  40. case 'subscription_paused_notification':
  41. await _sendSubscriptionPausedEvent(userId, eventData)
  42. break
  43. case 'subscription_resumed_notification':
  44. // 'resumed' here means resumed from pause
  45. await _sendSubscriptionResumedEvent(userId, eventData)
  46. break
  47. case 'paid_charge_invoice_notification':
  48. if (
  49. eventData.invoice.state === 'paid' &&
  50. eventData.invoice.total_in_cents > 0
  51. ) {
  52. await _sendInvoicePaidEvent(userId, eventData)
  53. }
  54. break
  55. case 'closed_invoice_notification':
  56. if (
  57. eventData.invoice.state === 'collected' &&
  58. eventData.invoice.total_in_cents > 0
  59. ) {
  60. await _sendInvoicePaidEvent(userId, eventData)
  61. }
  62. break
  63. }
  64. }
  65. async function _sendSubscriptionResumedEvent(userId, eventData) {
  66. const { planCode, state, subscriptionId } = _getSubscriptionData(eventData)
  67. AnalyticsManager.recordEventForUserInBackground(
  68. userId,
  69. 'subscription-resumed',
  70. {
  71. plan_code: planCode,
  72. subscriptionId,
  73. payment_provider: 'recurly',
  74. 'customerio-integration': eventData['customerio-integration'],
  75. }
  76. )
  77. AnalyticsManager.setUserPropertyForUserInBackground(
  78. userId,
  79. 'subscription-state',
  80. state
  81. )
  82. }
  83. async function _sendSubscriptionPausedEvent(userId, eventData) {
  84. const { planCode, state, subscriptionId } = _getSubscriptionData(eventData)
  85. const pauseLength = eventData.subscription.remaining_pause_cycles
  86. AnalyticsManager.recordEventForUserInBackground(
  87. userId,
  88. 'subscription-paused',
  89. {
  90. pause_length: pauseLength,
  91. plan_code: planCode,
  92. subscriptionId,
  93. payment_provider: 'recurly',
  94. 'customerio-integration': eventData['customerio-integration'],
  95. }
  96. )
  97. AnalyticsManager.setUserPropertyForUserInBackground(
  98. userId,
  99. 'subscription-state',
  100. state
  101. )
  102. }
  103. async function _sendSubscriptionStartedEvent(userId, eventData) {
  104. const { planCode, quantity, state, isTrial, hasAiAddOn, subscriptionId } =
  105. _getSubscriptionData(eventData)
  106. AnalyticsManager.recordEventForUserInBackground(
  107. userId,
  108. 'subscription-started',
  109. {
  110. plan_code: planCode,
  111. quantity,
  112. is_trial: isTrial,
  113. has_ai_add_on: hasAiAddOn,
  114. subscriptionId,
  115. payment_provider: 'recurly',
  116. 'customerio-integration': eventData['customerio-integration'],
  117. }
  118. )
  119. AnalyticsManager.setUserPropertyForUserInBackground(
  120. userId,
  121. 'subscription-plan-code',
  122. planCode
  123. )
  124. AnalyticsManager.setUserPropertyForUserInBackground(
  125. userId,
  126. 'subscription-state',
  127. state
  128. )
  129. AnalyticsManager.setUserPropertyForUserInBackground(
  130. userId,
  131. 'subscription-is-trial',
  132. isTrial
  133. )
  134. if (isTrial) {
  135. await SubscriptionEmailHandler.sendTrialOnboardingEmail(userId, planCode)
  136. const cioAssignment = await SplitTestHandler.promises.getAssignmentForUser(
  137. userId,
  138. 'customer-io-trial-conversion'
  139. )
  140. if (cioAssignment.variant === 'enabled') {
  141. AnalyticsManager.setUserPropertyForUserInBackground(
  142. userId,
  143. 'customer-io-integration',
  144. true
  145. )
  146. }
  147. }
  148. }
  149. async function _sendSubscriptionUpdatedEvent(userId, eventData) {
  150. const { planCode, quantity, state, isTrial, hasAiAddOn, subscriptionId } =
  151. _getSubscriptionData(eventData)
  152. AnalyticsManager.recordEventForUserInBackground(
  153. userId,
  154. 'subscription-updated',
  155. {
  156. plan_code: planCode,
  157. quantity,
  158. is_trial: isTrial,
  159. has_ai_add_on: hasAiAddOn,
  160. subscriptionId,
  161. payment_provider: 'recurly',
  162. 'customerio-integration': eventData['customerio-integration'],
  163. }
  164. )
  165. AnalyticsManager.setUserPropertyForUserInBackground(
  166. userId,
  167. 'subscription-plan-code',
  168. planCode
  169. )
  170. AnalyticsManager.setUserPropertyForUserInBackground(
  171. userId,
  172. 'subscription-state',
  173. state
  174. )
  175. AnalyticsManager.setUserPropertyForUserInBackground(
  176. userId,
  177. 'subscription-is-trial',
  178. isTrial
  179. )
  180. }
  181. async function _sendSubscriptionCancelledEvent(userId, eventData) {
  182. const { planCode, quantity, state, isTrial, hasAiAddOn, subscriptionId } =
  183. _getSubscriptionData(eventData)
  184. AnalyticsManager.recordEventForUserInBackground(
  185. userId,
  186. 'subscription-cancelled',
  187. {
  188. plan_code: planCode,
  189. quantity,
  190. is_trial: isTrial,
  191. has_ai_add_on: hasAiAddOn,
  192. subscriptionId,
  193. payment_provider: 'recurly',
  194. 'customerio-integration': eventData['customerio-integration'],
  195. }
  196. )
  197. AnalyticsManager.setUserPropertyForUserInBackground(
  198. userId,
  199. 'subscription-state',
  200. state
  201. )
  202. AnalyticsManager.setUserPropertyForUserInBackground(
  203. userId,
  204. 'subscription-is-trial',
  205. isTrial
  206. )
  207. }
  208. async function _sendSubscriptionExpiredEvent(userId, eventData) {
  209. const { planCode, quantity, state, isTrial, hasAiAddOn, subscriptionId } =
  210. _getSubscriptionData(eventData)
  211. AnalyticsManager.recordEventForUserInBackground(
  212. userId,
  213. 'subscription-expired',
  214. {
  215. plan_code: planCode,
  216. quantity,
  217. is_trial: isTrial,
  218. has_ai_add_on: hasAiAddOn,
  219. subscriptionId,
  220. payment_provider: 'recurly',
  221. 'customerio-integration': eventData['customerio-integration'],
  222. }
  223. )
  224. AnalyticsManager.setUserPropertyForUserInBackground(
  225. userId,
  226. 'subscription-plan-code',
  227. planCode
  228. )
  229. AnalyticsManager.setUserPropertyForUserInBackground(
  230. userId,
  231. 'subscription-state',
  232. state
  233. )
  234. AnalyticsManager.setUserPropertyForUserInBackground(
  235. userId,
  236. 'subscription-is-trial',
  237. isTrial
  238. )
  239. }
  240. async function _sendSubscriptionRenewedEvent(userId, eventData) {
  241. const { planCode, quantity, state, isTrial, hasAiAddOn, subscriptionId } =
  242. _getSubscriptionData(eventData)
  243. AnalyticsManager.recordEventForUserInBackground(
  244. userId,
  245. 'subscription-renewed',
  246. {
  247. plan_code: planCode,
  248. quantity,
  249. is_trial: isTrial,
  250. has_ai_add_on: hasAiAddOn,
  251. subscriptionId,
  252. payment_provider: 'recurly',
  253. 'customerio-integration': eventData['customerio-integration'],
  254. }
  255. )
  256. AnalyticsManager.setUserPropertyForUserInBackground(
  257. userId,
  258. 'subscription-plan-code',
  259. planCode
  260. )
  261. AnalyticsManager.setUserPropertyForUserInBackground(
  262. userId,
  263. 'subscription-state',
  264. state
  265. )
  266. AnalyticsManager.setUserPropertyForUserInBackground(
  267. userId,
  268. 'subscription-is-trial',
  269. isTrial
  270. )
  271. }
  272. async function _sendSubscriptionReactivatedEvent(userId, eventData) {
  273. const { planCode, quantity, state, isTrial, hasAiAddOn, subscriptionId } =
  274. _getSubscriptionData(eventData)
  275. AnalyticsManager.recordEventForUserInBackground(
  276. userId,
  277. 'subscription-reactivated',
  278. {
  279. plan_code: planCode,
  280. quantity,
  281. has_ai_add_on: hasAiAddOn,
  282. subscriptionId,
  283. payment_provider: 'recurly',
  284. 'customerio-integration': eventData['customerio-integration'],
  285. }
  286. )
  287. AnalyticsManager.setUserPropertyForUserInBackground(
  288. userId,
  289. 'subscription-plan-code',
  290. planCode
  291. )
  292. AnalyticsManager.setUserPropertyForUserInBackground(
  293. userId,
  294. 'subscription-state',
  295. state
  296. )
  297. AnalyticsManager.setUserPropertyForUserInBackground(
  298. userId,
  299. 'subscription-is-trial',
  300. isTrial
  301. )
  302. }
  303. async function _sendInvoicePaidEvent(userId, eventData) {
  304. const invoice = eventData.invoice
  305. if (!invoice) {
  306. return
  307. }
  308. const invoiceNumber = invoice.invoice_number
  309. const currency = invoice.currency
  310. const totalInCents = invoice.total_in_cents
  311. const taxInCents = invoice.tax_in_cents
  312. const country = invoice.address?.country
  313. const collectionMethod = invoice.collection_method
  314. const subscriptionIds = {}
  315. invoice.subscription_ids?.forEach((e, idx) => {
  316. if (idx < INVOICE_SUBSCRIPTION_LIMIT) {
  317. subscriptionIds[`subscriptionId${idx + 1}`] = e
  318. }
  319. })
  320. AnalyticsManager.recordEventForUserInBackground(
  321. userId,
  322. 'subscription-invoice-collected',
  323. {
  324. invoiceNumber,
  325. currency,
  326. totalInCents,
  327. taxInCents,
  328. country,
  329. collectionMethod,
  330. payment_provider: 'recurly',
  331. ...subscriptionIds,
  332. }
  333. )
  334. AnalyticsManager.setUserPropertyForUserInBackground(
  335. userId,
  336. 'subscription-is-trial',
  337. false
  338. )
  339. }
  340. function _getUserId(eventData) {
  341. let userId
  342. if (eventData && eventData.account && eventData.account.account_code) {
  343. userId = eventData.account.account_code
  344. } else {
  345. throw new Error(
  346. 'account.account_code missing in event data to identity user ID'
  347. )
  348. }
  349. return userId
  350. }
  351. function _getSubscriptionData(eventData) {
  352. const isTrial =
  353. eventData.subscription.trial_started_at &&
  354. eventData.subscription.current_period_started_at &&
  355. eventData.subscription.trial_started_at.getTime() ===
  356. eventData.subscription.current_period_started_at.getTime()
  357. const hasAiAddOn =
  358. eventData.subscription.subscription_add_ons?.some(
  359. addOn => addOn.add_on_code === AI_ADD_ON_CODE
  360. ) ?? false
  361. return {
  362. planCode: eventData.subscription.plan.plan_code,
  363. quantity: eventData.subscription.quantity,
  364. state: eventData.subscription.state,
  365. subscriptionId: eventData.subscription.uuid,
  366. isTrial,
  367. hasAiAddOn,
  368. }
  369. }
  370. module.exports = {
  371. sendRecurlyAnalyticsEvent,
  372. }