import { Injectable } from '@nestjs/common' import { isAfter, startOfMinute } from 'date-fns' import { isEmpty, isNil } from 'lodash' import { NotFoundError } from '../../../errors' import { CancellationsReasons, Patient, PaymentProviders, Subscription } from '../../../models' import { SubscriptionRepository } from '../../../repositories' import { SubscriptionService } from '../../../services' import { AppleEventType, ReceiptInfo } from './types' export const CANCELLATION_NOTIFICATION_TYPES: Array = [ 'CANCEL', 'DID_CHANGE_RENEWAL_STATUS', 'DID_FAIL_TO_RENEW', ] export const RENEW_NOTIFICATION_TYPES: Array = ['DID_RECOVER', 'INTERACTIVE_RENEWAL'] @Injectable() export class AppleInApp { constructor( private readonly subscriptionRepository: SubscriptionRepository, private readonly subscriptionService: SubscriptionService, ) {} async handle(notificationType: AppleEventType, receiptInfo: ReceiptInfo): Promise { if (![...CANCELLATION_NOTIFICATION_TYPES, ...RENEW_NOTIFICATION_TYPES].includes(notificationType)) return const subscription = await this.subscriptionRepository.getByOriginalTransactionAndPaymentProvider( receiptInfo.original_transaction_id, PaymentProviders.APPLE, ) if (!subscription) throw new NotFoundError('There is no subscription with that receipt and payment provider') if (CANCELLATION_NOTIFICATION_TYPES.includes(notificationType)) return this.handleCancellation(subscription, receiptInfo) if (RENEW_NOTIFICATION_TYPES.includes(notificationType)) return this.handleRenew(subscription, receiptInfo) } private async handleRenew(subscription: Subscription, receiptInfo: ReceiptInfo): Promise { subscription.expiresAt = new Date(+receiptInfo.expires_date_ms) return this.subscriptionService.uncancel(subscription) } private async handleCancellation(subscription: Subscription, receiptInfo: ReceiptInfo): Promise { const expirationDate = new Date(+receiptInfo.expires_date_ms) if (isAfter(new Date(), startOfMinute(expirationDate))) subscription.cancellationAt = expirationDate if (isNil(subscription.cancellationRequestAt) || isEmpty(subscription.cancellationRequestAt)) subscription.cancellationRequestAt = new Date(+receiptInfo.cancellation_date_ms) return this.subscriptionService.cancelSubscription({ patient: new Patient({ id: subscription.patientId, subscription }), cancellationReason: { id: CancellationsReasons.IN_APP_CANCELLED, }, cancellationBy: { id: subscription.patientId }, expiresAt: expirationDate, }) } }