Ver Fonte

feat(auth): add script for fixing subscriptions with missing id state (#1030)

* fix(auth): add subscription id safe guards on handlers

* feat(domain-events): add subscription state events

* feat(domain-events): add subscription state events

* feat(auth): add handling of subscription state fetched events

* feat(auth): add script for fixing subscriptions state
Karol Sójko há 1 ano atrás
pai
commit
86b050865f
22 ficheiros alterados com 363 adições e 0 exclusões
  1. 74 0
      packages/auth/bin/fix_subscriptions.ts
  2. 11 0
      packages/auth/docker/entrypoint-fix-subscriptions.js
  3. 4 0
      packages/auth/docker/entrypoint.sh
  4. 12 0
      packages/auth/src/Bootstrap/Container.ts
  5. 1 0
      packages/auth/src/Bootstrap/Types.ts
  6. 16 0
      packages/auth/src/Domain/Event/DomainEventFactory.ts
  7. 2 0
      packages/auth/src/Domain/Event/DomainEventFactoryInterface.ts
  8. 12 0
      packages/auth/src/Domain/Handler/SubscriptionCancelledEventHandler.ts
  9. 10 0
      packages/auth/src/Domain/Handler/SubscriptionExpiredEventHandler.ts
  10. 10 0
      packages/auth/src/Domain/Handler/SubscriptionPurchasedEventHandler.ts
  11. 10 0
      packages/auth/src/Domain/Handler/SubscriptionReassignedEventHandler.ts
  12. 10 0
      packages/auth/src/Domain/Handler/SubscriptionRefundedEventHandler.ts
  13. 10 0
      packages/auth/src/Domain/Handler/SubscriptionRenewedEventHandler.ts
  14. 123 0
      packages/auth/src/Domain/Handler/SubscriptionStateFetchedEventHandler.ts
  15. 10 0
      packages/auth/src/Domain/Handler/SubscriptionSyncRequestedEventHandler.ts
  16. 1 0
      packages/auth/src/Domain/Subscription/OfflineUserSubscriptionRepositoryInterface.ts
  17. 13 0
      packages/auth/src/Infra/TypeORM/TypeORMOfflineUserSubscriptionRepository.ts
  18. 8 0
      packages/domain-events/src/Domain/Event/SubscriptionStateFetchedEvent.ts
  19. 11 0
      packages/domain-events/src/Domain/Event/SubscriptionStateFetchedEventPayload.ts
  20. 8 0
      packages/domain-events/src/Domain/Event/SubscriptionStateRequestedEvent.ts
  21. 3 0
      packages/domain-events/src/Domain/Event/SubscriptionStateRequestedEventPayload.ts
  22. 4 0
      packages/domain-events/src/Domain/index.ts

+ 74 - 0
packages/auth/bin/fix_subscriptions.ts

@@ -0,0 +1,74 @@
+import 'reflect-metadata'
+
+import { Logger } from 'winston'
+
+import { ContainerConfigLoader } from '../src/Bootstrap/Container'
+import TYPES from '../src/Bootstrap/Types'
+import { Env } from '../src/Bootstrap/Env'
+import { UserSubscriptionRepositoryInterface } from '../src/Domain/Subscription/UserSubscriptionRepositoryInterface'
+import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
+import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
+import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
+import { Uuid } from '@standardnotes/domain-core'
+
+const fixSubscriptions = async (
+  userRepository: UserRepositoryInterface,
+  userSubscriptionRepository: UserSubscriptionRepositoryInterface,
+  domainEventFactory: DomainEventFactoryInterface,
+  domainEventPublisher: DomainEventPublisherInterface,
+): Promise<void> => {
+  const subscriptions = await userSubscriptionRepository.findBySubscriptionId(0)
+
+  for (const subscription of subscriptions) {
+    const userUuidOrError = Uuid.create(subscription.userUuid)
+    if (userUuidOrError.isFailed()) {
+      continue
+    }
+    const userUuid = userUuidOrError.getValue()
+
+    const user = await userRepository.findOneByUuid(userUuid)
+    if (!user) {
+      continue
+    }
+
+    await domainEventPublisher.publish(
+      domainEventFactory.createSubscriptionStateRequestedEvent({
+        userEmail: user.email,
+      }),
+    )
+  }
+}
+
+const container = new ContainerConfigLoader('worker')
+void container.load().then((container) => {
+  const env: Env = new Env()
+  env.load()
+
+  const logger: Logger = container.get(TYPES.Auth_Logger)
+
+  logger.info('Starting to fix subscriptions with missing subscriptionId ...')
+
+  const userRepository = container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository)
+  const userSubscriptionRepository = container.get<UserSubscriptionRepositoryInterface>(
+    TYPES.Auth_UserSubscriptionRepository,
+  )
+  const domainEventFactory = container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory)
+  const domainEventPublisher = container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher)
+
+  Promise.resolve(
+    fixSubscriptions(userRepository, userSubscriptionRepository, domainEventFactory, domainEventPublisher),
+  )
+    .then(() => {
+      logger.info('Finished fixing subscriptions with missing subscriptionId.')
+
+      process.exit(0)
+    })
+    .catch((error) => {
+      logger.error('Failed to fix subscriptions with missing subscriptionId.', {
+        error: error.message,
+        stack: error.stack,
+      })
+
+      process.exit(1)
+    })
+})

+ 11 - 0
packages/auth/docker/entrypoint-fix-subscriptions.js

@@ -0,0 +1,11 @@
+'use strict'
+
+const path = require('path')
+
+const pnp = require(path.normalize(path.resolve(__dirname, '../../..', '.pnp.cjs'))).setup()
+
+const index = require(path.normalize(path.resolve(__dirname, '../dist/bin/fix_subscriptions.js')))
+
+Object.defineProperty(exports, '__esModule', { value: true })
+
+exports.default = index

+ 4 - 0
packages/auth/docker/entrypoint.sh

@@ -42,6 +42,10 @@ case "$COMMAND" in
     exec node docker/entrypoint-fix-roles.js
     ;;
 
+  'fix-subscriptions' )
+    exec node docker/entrypoint-fix-subscriptions.js
+    ;;
+
   'delete-accounts' )
     FILE_NAME=$1 && shift 1
     MODE=$1 && shift 1

+ 12 - 0
packages/auth/src/Bootstrap/Container.ts

@@ -285,6 +285,7 @@ import { RenewSharedSubscriptions } from '../Domain/UseCase/RenewSharedSubscript
 import { FixStorageQuotaForUser } from '../Domain/UseCase/FixStorageQuotaForUser/FixStorageQuotaForUser'
 import { FileQuotaRecalculatedEventHandler } from '../Domain/Handler/FileQuotaRecalculatedEventHandler'
 import { SessionServiceInterface } from '../Domain/Session/SessionServiceInterface'
+import { SubscriptionStateFetchedEventHandler } from '../Domain/Handler/SubscriptionStateFetchedEventHandler'
 
 export class ContainerConfigLoader {
   constructor(private mode: 'server' | 'worker' = 'server') {}
@@ -1579,6 +1580,16 @@ export class ContainerConfigLoader {
           container.get<winston.Logger>(TYPES.Auth_Logger),
         ),
       )
+    container
+      .bind<SubscriptionStateFetchedEventHandler>(TYPES.Auth_SubscriptionStateFetchedEventHandler)
+      .toConstantValue(
+        new SubscriptionStateFetchedEventHandler(
+          container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
+          container.get<UserSubscriptionRepositoryInterface>(TYPES.Auth_UserSubscriptionRepository),
+          container.get<OfflineUserSubscriptionRepositoryInterface>(TYPES.Auth_OfflineUserSubscriptionRepository),
+          container.get<winston.Logger>(TYPES.Auth_Logger),
+        ),
+      )
 
     const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
       ['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Auth_AccountDeletionRequestedEventHandler)],
@@ -1620,6 +1631,7 @@ export class ContainerConfigLoader {
         'FILE_QUOTA_RECALCULATED',
         container.get<FileQuotaRecalculatedEventHandler>(TYPES.Auth_FileQuotaRecalculatedEventHandler),
       ],
+      ['SUBSCRIPTION_STATE_FETCHED', container.get(TYPES.Auth_SubscriptionStateFetchedEventHandler)],
     ])
 
     if (isConfiguredForHomeServer) {

+ 1 - 0
packages/auth/src/Bootstrap/Types.ts

@@ -205,6 +205,7 @@ const TYPES = {
   ),
   Auth_UserInvitedToSharedVaultEventHandler: Symbol.for('Auth_UserInvitedToSharedVaultEventHandler'),
   Auth_FileQuotaRecalculatedEventHandler: Symbol.for('Auth_FileQuotaRecalculatedEventHandler'),
+  Auth_SubscriptionStateFetchedEventHandler: Symbol.for('Auth_SubscriptionStateFetchedEventHandler'),
   // Services
   Auth_DeviceDetector: Symbol.for('Auth_DeviceDetector'),
   Auth_SessionService: Symbol.for('Auth_SessionService'),

+ 16 - 0
packages/auth/src/Domain/Event/DomainEventFactory.ts

@@ -22,6 +22,7 @@ import {
   SessionRefreshedEvent,
   AccountDeletionVerificationRequestedEvent,
   FileQuotaRecalculationRequestedEvent,
+  SubscriptionStateRequestedEvent,
 } from '@standardnotes/domain-events'
 import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
 import { TimerInterface } from '@standardnotes/time'
@@ -34,6 +35,21 @@ import { KeyParamsData } from '@standardnotes/responses'
 @injectable()
 export class DomainEventFactory implements DomainEventFactoryInterface {
   constructor(@inject(TYPES.Auth_Timer) private timer: TimerInterface) {}
+  createSubscriptionStateRequestedEvent(dto: { userEmail: string }): SubscriptionStateRequestedEvent {
+    return {
+      type: 'SUBSCRIPTION_STATE_REQUESTED',
+      createdAt: this.timer.getUTCDate(),
+      meta: {
+        correlation: {
+          userIdentifier: dto.userEmail,
+          userIdentifierType: 'email',
+        },
+        origin: DomainEventService.Auth,
+      },
+      payload: dto,
+    }
+  }
+
   createFileQuotaRecalculationRequestedEvent(dto: { userUuid: string }): FileQuotaRecalculationRequestedEvent {
     return {
       type: 'FILE_QUOTA_RECALCULATION_REQUESTED',

+ 2 - 0
packages/auth/src/Domain/Event/DomainEventFactoryInterface.ts

@@ -20,11 +20,13 @@ import {
   SessionRefreshedEvent,
   AccountDeletionVerificationRequestedEvent,
   FileQuotaRecalculationRequestedEvent,
+  SubscriptionStateRequestedEvent,
 } from '@standardnotes/domain-events'
 import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType'
 import { KeyParamsData } from '@standardnotes/responses'
 
 export interface DomainEventFactoryInterface {
+  createSubscriptionStateRequestedEvent(dto: { userEmail: string }): SubscriptionStateRequestedEvent
   createFileQuotaRecalculationRequestedEvent(dto: { userUuid: string }): FileQuotaRecalculationRequestedEvent
   createWebSocketMessageRequestedEvent(dto: { userUuid: string; message: JSONString }): WebSocketMessageRequestedEvent
   createEmailRequestedEvent(dto: {

+ 12 - 0
packages/auth/src/Domain/Handler/SubscriptionCancelledEventHandler.ts

@@ -4,6 +4,7 @@ import { inject, injectable } from 'inversify'
 import TYPES from '../../Bootstrap/Types'
 import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
 import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
+import { Logger } from 'winston'
 
 @injectable()
 export class SubscriptionCancelledEventHandler implements DomainEventHandlerInterface {
@@ -12,9 +13,20 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
     private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
     @inject(TYPES.Auth_OfflineUserSubscriptionRepository)
     private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
+    @inject(TYPES.Auth_Logger) private logger: Logger,
   ) {}
 
   async handle(event: SubscriptionCancelledEvent): Promise<void> {
+    if (!event.payload.subscriptionId) {
+      this.logger.error('Subscription ID is missing', {
+        codeTag: 'SubscriptionCancelledEventHandler.handle',
+        subscriptionId: event.payload.subscriptionId,
+        userId: event.payload.userEmail,
+      })
+
+      return
+    }
+
     if (event.payload.offline) {
       await this.updateOfflineSubscriptionCancelled(event.payload.subscriptionId, event.payload.timestamp)
 

+ 10 - 0
packages/auth/src/Domain/Handler/SubscriptionExpiredEventHandler.ts

@@ -22,6 +22,16 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
   ) {}
 
   async handle(event: SubscriptionExpiredEvent): Promise<void> {
+    if (!event.payload.subscriptionId) {
+      this.logger.error('Subscription ID is missing', {
+        codeTag: 'SubscriptionExpiredEventHandler.handle',
+        subscriptionId: event.payload.subscriptionId,
+        userId: event.payload.userEmail,
+      })
+
+      return
+    }
+
     if (event.payload.offline) {
       await this.updateOfflineSubscriptionEndsAt(event.payload.subscriptionId, event.payload.timestamp)
 

+ 10 - 0
packages/auth/src/Domain/Handler/SubscriptionPurchasedEventHandler.ts

@@ -25,6 +25,16 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
   ) {}
 
   async handle(event: SubscriptionPurchasedEvent): Promise<void> {
+    if (!event.payload.subscriptionId) {
+      this.logger.error('Subscription ID is missing', {
+        codeTag: 'SubscriptionPurchasedEventHandler.handle',
+        subscriptionId: event.payload.subscriptionId,
+        userId: event.payload.userEmail,
+      })
+
+      return
+    }
+
     if (event.payload.offline) {
       const offlineUserSubscription = await this.createOfflineSubscription(
         event.payload.subscriptionId,

+ 10 - 0
packages/auth/src/Domain/Handler/SubscriptionReassignedEventHandler.ts

@@ -22,6 +22,16 @@ export class SubscriptionReassignedEventHandler implements DomainEventHandlerInt
   ) {}
 
   async handle(event: SubscriptionReassignedEvent): Promise<void> {
+    if (!event.payload.subscriptionId) {
+      this.logger.error('Subscription ID is missing', {
+        codeTag: 'SubscriptionReassignedEventHandler.handle',
+        subscriptionId: event.payload.subscriptionId,
+        userId: event.payload.userEmail,
+      })
+
+      return
+    }
+
     const usernameOrError = Username.create(event.payload.userEmail)
     if (usernameOrError.isFailed()) {
       return

+ 10 - 0
packages/auth/src/Domain/Handler/SubscriptionRefundedEventHandler.ts

@@ -22,6 +22,16 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
   ) {}
 
   async handle(event: SubscriptionRefundedEvent): Promise<void> {
+    if (!event.payload.subscriptionId) {
+      this.logger.error('Subscription ID is missing', {
+        codeTag: 'SubscriptionRefundedEventHandler.handle',
+        subscriptionId: event.payload.subscriptionId,
+        userId: event.payload.userEmail,
+      })
+
+      return
+    }
+
     if (event.payload.offline) {
       await this.updateOfflineSubscriptionEndsAt(event.payload.subscriptionId, event.payload.timestamp)
 

+ 10 - 0
packages/auth/src/Domain/Handler/SubscriptionRenewedEventHandler.ts

@@ -23,6 +23,16 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
   ) {}
 
   async handle(event: SubscriptionRenewedEvent): Promise<void> {
+    if (!event.payload.subscriptionId) {
+      this.logger.error('Subscription ID is missing', {
+        codeTag: 'SubscriptionRenewedEventHandler.handle',
+        subscriptionId: event.payload.subscriptionId,
+        userId: event.payload.userEmail,
+      })
+
+      return
+    }
+
     if (event.payload.offline) {
       const offlineUserSubscription = await this.offlineUserSubscriptionRepository.findOneBySubscriptionId(
         event.payload.subscriptionId,

+ 123 - 0
packages/auth/src/Domain/Handler/SubscriptionStateFetchedEventHandler.ts

@@ -0,0 +1,123 @@
+import { Username } from '@standardnotes/domain-core'
+import { DomainEventHandlerInterface, SubscriptionStateFetchedEvent } from '@standardnotes/domain-events'
+import { Logger } from 'winston'
+
+import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
+import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
+import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
+
+export class SubscriptionStateFetchedEventHandler implements DomainEventHandlerInterface {
+  constructor(
+    private userRepository: UserRepositoryInterface,
+    private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
+    private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
+    private logger: Logger,
+  ) {}
+
+  async handle(event: SubscriptionStateFetchedEvent): Promise<void> {
+    if (!event.payload.subscriptionId) {
+      this.logger.error('Subscription ID is missing', {
+        codeTag: 'SubscriptionStateFetchedEventHandler.handle',
+        subscriptionId: event.payload.subscriptionId,
+        userId: event.payload.userEmail,
+      })
+
+      return
+    }
+
+    this.logger.info('Subscription state update fetched', {
+      subscriptionId: event.payload.subscriptionId,
+    })
+
+    if (event.payload.offline) {
+      this.logger.info('Updating offline subscription', {
+        subscriptionId: event.payload.subscriptionId,
+      })
+
+      const subscription = await this.offlineUserSubscriptionRepository.findOneByEmailAndSubscriptionId(
+        event.payload.userEmail,
+        0,
+      )
+      if (!subscription) {
+        this.logger.error('Offline subscription not found', {
+          subscriptionId: event.payload.subscriptionId,
+        })
+
+        return
+      }
+
+      subscription.planName = event.payload.subscriptionName
+      subscription.email = event.payload.userEmail
+      subscription.endsAt = event.payload.subscriptionExpiresAt
+      subscription.cancelled = event.payload.canceled
+      if (subscription.subscriptionId !== event.payload.subscriptionId) {
+        this.logger.warn('Subscription IDs do not match', {
+          previousSubscriptionId: subscription.subscriptionId,
+          subscriptionId: event.payload.subscriptionId,
+        })
+      }
+      subscription.subscriptionId = event.payload.subscriptionId
+
+      await this.offlineUserSubscriptionRepository.save(subscription)
+
+      this.logger.info('Offline subscription updated', {
+        subscriptionId: event.payload.subscriptionId,
+      })
+
+      return
+    }
+
+    const usernameOrError = Username.create(event.payload.userEmail)
+    if (usernameOrError.isFailed()) {
+      this.logger.warn(`Could not update subscription: ${usernameOrError.getError()}`, {
+        subscriptionId: event.payload.subscriptionId,
+      })
+
+      return
+    }
+    const username = usernameOrError.getValue()
+
+    const user = await this.userRepository.findOneByUsernameOrEmail(username)
+
+    if (user === null) {
+      this.logger.warn(`Could not find user with email: ${username.value}`, {
+        subscriptionId: event.payload.subscriptionId,
+      })
+
+      return
+    }
+
+    this.logger.info('Updating subscription', {
+      userId: user.uuid,
+      subscriptionId: event.payload.subscriptionId,
+    })
+
+    const subscription = await this.userSubscriptionRepository.findOneByUserUuidAndSubscriptionId(user.uuid, 0)
+    if (!subscription) {
+      this.logger.error('Subscription not found', {
+        userId: user.uuid,
+        subscriptionId: event.payload.subscriptionId,
+      })
+
+      return
+    }
+
+    subscription.planName = event.payload.subscriptionName
+    subscription.endsAt = event.payload.subscriptionExpiresAt
+    subscription.cancelled = event.payload.canceled
+    if (subscription.subscriptionId !== event.payload.subscriptionId) {
+      this.logger.warn('Subscription IDs do not match', {
+        previousSubscriptionId: subscription.subscriptionId,
+        subscriptionId: event.payload.subscriptionId,
+      })
+    }
+    subscription.subscriptionId = event.payload.subscriptionId
+
+    await this.userSubscriptionRepository.save(subscription)
+
+    this.logger.info('Subscription updated to current state', {
+      userId: user.uuid,
+      subscriptionId: event.payload.subscriptionId,
+    })
+  }
+}

+ 10 - 0
packages/auth/src/Domain/Handler/SubscriptionSyncRequestedEventHandler.ts

@@ -33,6 +33,16 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
   ) {}
 
   async handle(event: SubscriptionSyncRequestedEvent): Promise<void> {
+    if (!event.payload.subscriptionId) {
+      this.logger.error('Subscription ID is missing', {
+        codeTag: 'SubscriptionSyncRequestedEventHandler.handle',
+        subscriptionId: event.payload.subscriptionId,
+        userId: event.payload.userEmail,
+      })
+
+      return
+    }
+
     this.logger.info('Subscription sync requested', {
       subscriptionId: event.payload.subscriptionId,
     })

+ 1 - 0
packages/auth/src/Domain/Subscription/OfflineUserSubscriptionRepositoryInterface.ts

@@ -2,6 +2,7 @@ import { OfflineUserSubscription } from './OfflineUserSubscription'
 
 export interface OfflineUserSubscriptionRepositoryInterface {
   findOneByEmail(email: string): Promise<OfflineUserSubscription | null>
+  findOneByEmailAndSubscriptionId(email: string, subscriptionId: number): Promise<OfflineUserSubscription | null>
   findOneBySubscriptionId(subscriptionId: number): Promise<OfflineUserSubscription | null>
   findByEmail(email: string, activeAfter: number): Promise<OfflineUserSubscription[]>
   updateEndsAt(subscriptionId: number, endsAt: number, updatedAt: number): Promise<void>

+ 13 - 0
packages/auth/src/Infra/TypeORM/TypeORMOfflineUserSubscriptionRepository.ts

@@ -12,6 +12,19 @@ export class TypeORMOfflineUserSubscriptionRepository implements OfflineUserSubs
     private ormRepository: Repository<OfflineUserSubscription>,
   ) {}
 
+  async findOneByEmailAndSubscriptionId(
+    email: string,
+    subscriptionId: number,
+  ): Promise<OfflineUserSubscription | null> {
+    return await this.ormRepository
+      .createQueryBuilder()
+      .where('email = :email AND subscription_id = :subscriptionId', {
+        email,
+        subscriptionId,
+      })
+      .getOne()
+  }
+
   async save(offlineUserSubscription: OfflineUserSubscription): Promise<OfflineUserSubscription> {
     return this.ormRepository.save(offlineUserSubscription)
   }

+ 8 - 0
packages/domain-events/src/Domain/Event/SubscriptionStateFetchedEvent.ts

@@ -0,0 +1,8 @@
+import { DomainEventInterface } from './DomainEventInterface'
+
+import { SubscriptionStateFetchedEventPayload } from './SubscriptionStateFetchedEventPayload'
+
+export interface SubscriptionStateFetchedEvent extends DomainEventInterface {
+  type: 'SUBSCRIPTION_STATE_FETCHED'
+  payload: SubscriptionStateFetchedEventPayload
+}

+ 11 - 0
packages/domain-events/src/Domain/Event/SubscriptionStateFetchedEventPayload.ts

@@ -0,0 +1,11 @@
+export interface SubscriptionStateFetchedEventPayload {
+  userEmail: string
+  subscriptionId: number
+  subscriptionName: string
+  subscriptionExpiresAt: number
+  timestamp: number
+  offline: boolean
+  canceled: boolean
+  extensionKey: string
+  offlineFeaturesToken: string
+}

+ 8 - 0
packages/domain-events/src/Domain/Event/SubscriptionStateRequestedEvent.ts

@@ -0,0 +1,8 @@
+import { DomainEventInterface } from './DomainEventInterface'
+
+import { SubscriptionStateRequestedEventPayload } from './SubscriptionStateRequestedEventPayload'
+
+export interface SubscriptionStateRequestedEvent extends DomainEventInterface {
+  type: 'SUBSCRIPTION_STATE_REQUESTED'
+  payload: SubscriptionStateRequestedEventPayload
+}

+ 3 - 0
packages/domain-events/src/Domain/Event/SubscriptionStateRequestedEventPayload.ts

@@ -0,0 +1,3 @@
+export interface SubscriptionStateRequestedEventPayload {
+  userEmail: string
+}

+ 4 - 0
packages/domain-events/src/Domain/index.ts

@@ -110,6 +110,10 @@ export * from './Event/SubscriptionExpiredEvent'
 export * from './Event/SubscriptionExpiredEventPayload'
 export * from './Event/SubscriptionRevertRequestedEvent'
 export * from './Event/SubscriptionRevertRequestedEventPayload'
+export * from './Event/SubscriptionStateFetchedEvent'
+export * from './Event/SubscriptionStateFetchedEventPayload'
+export * from './Event/SubscriptionStateRequestedEvent'
+export * from './Event/SubscriptionStateRequestedEventPayload'
 export * from './Event/SubscriptionSyncRequestedEvent'
 export * from './Event/SubscriptionSyncRequestedEventPayload'
 export * from './Event/UserAddedToSharedVaultEvent'