Files
overleaf-cep/services/web/app/src/Features/Subscription/RecurlyEventHandler.js
Domagoj Kriskovic a9e47f043a Move AI related functions from PaymentProviderEntities to AiHelper (#26956)
* Move AI related functions from PaymentProviderEntities to AiHelper

* added @ts-check

GitOrigin-RevId: 8c8eec334b40a7f8f8533f6d5194f428112f68f9
2025-07-10 08:07:51 +00:00

393 lines
10 KiB
JavaScript

const SplitTestHandler = require('../SplitTests/SplitTestHandler')
const AnalyticsManager = require('../Analytics/AnalyticsManager')
const SubscriptionEmailHandler = require('./SubscriptionEmailHandler')
const { AI_ADD_ON_CODE } = require('./AiHelper')
const { ObjectId } = require('mongodb-legacy')
const INVOICE_SUBSCRIPTION_LIMIT = 10
async function sendRecurlyAnalyticsEvent(event, eventData) {
const userId = _getUserId(eventData)
if (!ObjectId.isValid(userId)) {
return
}
const customerIoEnabled =
await SplitTestHandler.promises.hasUserBeenAssignedToVariant(
{},
userId,
'customer-io-trial-conversion',
'enabled',
true
)
eventData['customerio-integration'] = customerIoEnabled || false
switch (event) {
case 'new_subscription_notification':
await _sendSubscriptionStartedEvent(userId, eventData)
break
case 'updated_subscription_notification':
await _sendSubscriptionUpdatedEvent(userId, eventData)
break
case 'canceled_subscription_notification':
await _sendSubscriptionCancelledEvent(userId, eventData)
break
case 'expired_subscription_notification':
await _sendSubscriptionExpiredEvent(userId, eventData)
break
case 'renewed_subscription_notification':
await _sendSubscriptionRenewedEvent(userId, eventData)
break
case 'reactivated_account_notification':
await _sendSubscriptionReactivatedEvent(userId, eventData)
break
case 'subscription_paused_notification':
await _sendSubscriptionPausedEvent(userId, eventData)
break
case 'subscription_resumed_notification':
// 'resumed' here means resumed from pause
await _sendSubscriptionResumedEvent(userId, eventData)
break
case 'paid_charge_invoice_notification':
if (
eventData.invoice.state === 'paid' &&
eventData.invoice.total_in_cents > 0
) {
await _sendInvoicePaidEvent(userId, eventData)
}
break
case 'closed_invoice_notification':
if (
eventData.invoice.state === 'collected' &&
eventData.invoice.total_in_cents > 0
) {
await _sendInvoicePaidEvent(userId, eventData)
}
break
}
}
async function _sendSubscriptionResumedEvent(userId, eventData) {
const { planCode, state, subscriptionId } = _getSubscriptionData(eventData)
AnalyticsManager.recordEventForUserInBackground(
userId,
'subscription-resumed',
{
plan_code: planCode,
subscriptionId,
payment_provider: 'recurly',
'customerio-integration': eventData['customerio-integration'],
}
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-state',
state
)
}
async function _sendSubscriptionPausedEvent(userId, eventData) {
const { planCode, state, subscriptionId } = _getSubscriptionData(eventData)
const pauseLength = eventData.subscription.remaining_pause_cycles
AnalyticsManager.recordEventForUserInBackground(
userId,
'subscription-paused',
{
pause_length: pauseLength,
plan_code: planCode,
subscriptionId,
payment_provider: 'recurly',
'customerio-integration': eventData['customerio-integration'],
}
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-state',
state
)
}
async function _sendSubscriptionStartedEvent(userId, eventData) {
const { planCode, quantity, state, isTrial, hasAiAddOn, subscriptionId } =
_getSubscriptionData(eventData)
AnalyticsManager.recordEventForUserInBackground(
userId,
'subscription-started',
{
plan_code: planCode,
quantity,
is_trial: isTrial,
has_ai_add_on: hasAiAddOn,
subscriptionId,
payment_provider: 'recurly',
'customerio-integration': eventData['customerio-integration'],
}
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-plan-code',
planCode
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-state',
state
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-is-trial',
isTrial
)
if (isTrial) {
await SubscriptionEmailHandler.sendTrialOnboardingEmail(userId, planCode)
const cioAssignment = await SplitTestHandler.promises.getAssignmentForUser(
userId,
'customer-io-trial-conversion'
)
if (cioAssignment.variant === 'enabled') {
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'customer-io-integration',
true
)
}
}
}
async function _sendSubscriptionUpdatedEvent(userId, eventData) {
const { planCode, quantity, state, isTrial, hasAiAddOn, subscriptionId } =
_getSubscriptionData(eventData)
AnalyticsManager.recordEventForUserInBackground(
userId,
'subscription-updated',
{
plan_code: planCode,
quantity,
is_trial: isTrial,
has_ai_add_on: hasAiAddOn,
subscriptionId,
payment_provider: 'recurly',
'customerio-integration': eventData['customerio-integration'],
}
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-plan-code',
planCode
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-state',
state
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-is-trial',
isTrial
)
}
async function _sendSubscriptionCancelledEvent(userId, eventData) {
const { planCode, quantity, state, isTrial, hasAiAddOn, subscriptionId } =
_getSubscriptionData(eventData)
AnalyticsManager.recordEventForUserInBackground(
userId,
'subscription-cancelled',
{
plan_code: planCode,
quantity,
is_trial: isTrial,
has_ai_add_on: hasAiAddOn,
subscriptionId,
payment_provider: 'recurly',
'customerio-integration': eventData['customerio-integration'],
}
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-state',
state
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-is-trial',
isTrial
)
}
async function _sendSubscriptionExpiredEvent(userId, eventData) {
const { planCode, quantity, state, isTrial, hasAiAddOn, subscriptionId } =
_getSubscriptionData(eventData)
AnalyticsManager.recordEventForUserInBackground(
userId,
'subscription-expired',
{
plan_code: planCode,
quantity,
is_trial: isTrial,
has_ai_add_on: hasAiAddOn,
subscriptionId,
payment_provider: 'recurly',
'customerio-integration': eventData['customerio-integration'],
}
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-plan-code',
planCode
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-state',
state
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-is-trial',
isTrial
)
}
async function _sendSubscriptionRenewedEvent(userId, eventData) {
const { planCode, quantity, state, isTrial, hasAiAddOn, subscriptionId } =
_getSubscriptionData(eventData)
AnalyticsManager.recordEventForUserInBackground(
userId,
'subscription-renewed',
{
plan_code: planCode,
quantity,
is_trial: isTrial,
has_ai_add_on: hasAiAddOn,
subscriptionId,
payment_provider: 'recurly',
'customerio-integration': eventData['customerio-integration'],
}
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-plan-code',
planCode
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-state',
state
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-is-trial',
isTrial
)
}
async function _sendSubscriptionReactivatedEvent(userId, eventData) {
const { planCode, quantity, state, isTrial, hasAiAddOn, subscriptionId } =
_getSubscriptionData(eventData)
AnalyticsManager.recordEventForUserInBackground(
userId,
'subscription-reactivated',
{
plan_code: planCode,
quantity,
has_ai_add_on: hasAiAddOn,
subscriptionId,
payment_provider: 'recurly',
'customerio-integration': eventData['customerio-integration'],
}
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-plan-code',
planCode
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-state',
state
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-is-trial',
isTrial
)
}
async function _sendInvoicePaidEvent(userId, eventData) {
const invoice = eventData.invoice
if (!invoice) {
return
}
const invoiceNumber = invoice.invoice_number
const currency = invoice.currency
const totalInCents = invoice.total_in_cents
const taxInCents = invoice.tax_in_cents
const country = invoice.address?.country
const collectionMethod = invoice.collection_method
const subscriptionIds = {}
invoice.subscription_ids?.forEach((e, idx) => {
if (idx < INVOICE_SUBSCRIPTION_LIMIT) {
subscriptionIds[`subscriptionId${idx + 1}`] = e
}
})
AnalyticsManager.recordEventForUserInBackground(
userId,
'subscription-invoice-collected',
{
invoiceNumber,
currency,
totalInCents,
taxInCents,
country,
collectionMethod,
payment_provider: 'recurly',
...subscriptionIds,
}
)
AnalyticsManager.setUserPropertyForUserInBackground(
userId,
'subscription-is-trial',
false
)
}
function _getUserId(eventData) {
let userId
if (eventData && eventData.account && eventData.account.account_code) {
userId = eventData.account.account_code
} else {
throw new Error(
'account.account_code missing in event data to identity user ID'
)
}
return userId
}
function _getSubscriptionData(eventData) {
const isTrial =
eventData.subscription.trial_started_at &&
eventData.subscription.current_period_started_at &&
eventData.subscription.trial_started_at.getTime() ===
eventData.subscription.current_period_started_at.getTime()
const hasAiAddOn =
eventData.subscription.subscription_add_ons?.some(
addOn => addOn.add_on_code === AI_ADD_ON_CODE
) ?? false
return {
planCode: eventData.subscription.plan.plan_code,
quantity: eventData.subscription.quantity,
state: eventData.subscription.state,
subscriptionId: eventData.subscription.uuid,
isTrial,
hasAiAddOn,
}
}
module.exports = {
sendRecurlyAnalyticsEvent,
}