feat(analytics): add saving revenue modifications upon subscription canceled

This commit is contained in:
Karol Sójko 2022-11-09 11:25:21 +01:00
parent 7480fb089b
commit 52a257abb1
No known key found for this signature in database
GPG key ID: A50543BF560BDEB0
4 changed files with 38 additions and 2 deletions

View file

@ -9,14 +9,19 @@ import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure' import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface' import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
import { Period } from '../Time/Period' import { Period } from '../Time/Period'
import { Result } from '../Core/Result'
import { RevenueModification } from '../Revenue/RevenueModification'
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
describe('SubscriptionCancelledEventHandler', () => { describe('SubscriptionCancelledEventHandler', () => {
let event: SubscriptionCancelledEvent let event: SubscriptionCancelledEvent
let getUserAnalyticsId: GetUserAnalyticsId let getUserAnalyticsId: GetUserAnalyticsId
let analyticsStore: AnalyticsStoreInterface let analyticsStore: AnalyticsStoreInterface
let statisticsStore: StatisticsStoreInterface let statisticsStore: StatisticsStoreInterface
let saveRevenueModification: SaveRevenueModification
const createHandler = () => new SubscriptionCancelledEventHandler(getUserAnalyticsId, analyticsStore, statisticsStore) const createHandler = () =>
new SubscriptionCancelledEventHandler(getUserAnalyticsId, analyticsStore, statisticsStore, saveRevenueModification)
beforeEach(() => { beforeEach(() => {
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId> getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
@ -30,6 +35,7 @@ describe('SubscriptionCancelledEventHandler', () => {
event = {} as jest.Mocked<SubscriptionCancelledEvent> event = {} as jest.Mocked<SubscriptionCancelledEvent>
event.createdAt = new Date(1) event.createdAt = new Date(1)
event.type = 'SUBSCRIPTION_CANCELLED'
event.payload = { event.payload = {
subscriptionId: 1, subscriptionId: 1,
userEmail: 'test@test.com', userEmail: 'test@test.com',
@ -41,7 +47,13 @@ describe('SubscriptionCancelledEventHandler', () => {
timestamp: 1, timestamp: 1,
offline: false, offline: false,
replaced: false, replaced: false,
userExistingSubscriptionsCount: 1,
billingFrequency: 1,
payAmount: 12.99,
} }
saveRevenueModification = {} as jest.Mocked<SaveRevenueModification>
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.ok<RevenueModification>())
}) })
it('should track subscription cancelled statistics', async () => { it('should track subscription cancelled statistics', async () => {
@ -55,6 +67,7 @@ describe('SubscriptionCancelledEventHandler', () => {
Period.ThisWeek, Period.ThisWeek,
Period.ThisMonth, Period.ThisMonth,
]) ])
expect(saveRevenueModification.execute).toHaveBeenCalled()
}) })
it('should not track statistics for subscriptions that are in a legacy 5 year plan', async () => { it('should not track statistics for subscriptions that are in a legacy 5 year plan', async () => {
@ -65,5 +78,6 @@ describe('SubscriptionCancelledEventHandler', () => {
await createHandler().handle(event) await createHandler().handle(event)
expect(statisticsStore.incrementMeasure).not.toHaveBeenCalled() expect(statisticsStore.incrementMeasure).not.toHaveBeenCalled()
expect(saveRevenueModification.execute).toHaveBeenCalled()
}) })
}) })

View file

@ -4,10 +4,14 @@ import { inject, injectable } from 'inversify'
import TYPES from '../../Bootstrap/Types' import TYPES from '../../Bootstrap/Types'
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity' import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface' import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
import { Email } from '../Common/Email'
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure' import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface' import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
import { Period } from '../Time/Period' import { Period } from '../Time/Period'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId' import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
@injectable() @injectable()
export class SubscriptionCancelledEventHandler implements DomainEventHandlerInterface { export class SubscriptionCancelledEventHandler implements DomainEventHandlerInterface {
@ -15,10 +19,11 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId, @inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface, @inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface, @inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
) {} ) {}
async handle(event: SubscriptionCancelledEvent): Promise<void> { async handle(event: SubscriptionCancelledEvent): Promise<void> {
const { analyticsId } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail }) const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionCancelled], analyticsId, [ await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionCancelled], analyticsId, [
Period.Today, Period.Today,
Period.ThisWeek, Period.ThisWeek,
@ -26,6 +31,17 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
]) ])
await this.trackSubscriptionStatistics(event) await this.trackSubscriptionStatistics(event)
await this.saveRevenueModification.execute({
billingFrequency: event.payload.billingFrequency,
eventType: SubscriptionEventType.create(event.type).getValue(),
newSubscriber: event.payload.userExistingSubscriptionsCount === 1,
payedAmount: event.payload.payAmount,
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
subscriptionId: event.payload.subscriptionId,
userEmail: Email.create(event.payload.userEmail).getValue(),
userUuid,
})
} }
private async trackSubscriptionStatistics(event: SubscriptionCancelledEvent) { private async trackSubscriptionStatistics(event: SubscriptionCancelledEvent) {

View file

@ -40,6 +40,9 @@ describe('SubscriptionCancelledEventHandler', () => {
subscriptionEndsAt: 2, subscriptionEndsAt: 2,
subscriptionUpdatedAt: 2, subscriptionUpdatedAt: 2,
lastPayedAt: 1, lastPayedAt: 1,
userExistingSubscriptionsCount: 1,
billingFrequency: 1,
payAmount: 12.99,
} }
}) })

View file

@ -11,4 +11,7 @@ export interface SubscriptionCancelledEventPayload {
timestamp: number timestamp: number
offline: boolean offline: boolean
replaced: boolean replaced: boolean
userExistingSubscriptionsCount: number
billingFrequency: number
payAmount: number
} }