import { Injectable } from '@nestjs/common' import { isNil } from 'lodash' import { PlanNotFoundError } from '../../../../errors/plan-not-found.error' import { CancellationsReasons, Patient, PaymentProviders, Plan } from '../../../../models' import { PlanRepository } from '../../../../repositories/plan.repository' import { AppleInAppSubscriptionService, AppleInAppSubscription, SubscriptionService } from '../../../../services' import { CountryCode } from '../../../../utils' import { CheckSubscription } from './check-subscription' @Injectable() export class AppleCheckSubscription implements CheckSubscription { private plansCached: { [key: string]: Plan } = {} constructor( private readonly subscriptionService: SubscriptionService, private readonly planRepository: PlanRepository, private readonly appleInAppSubscription: AppleInAppSubscriptionService, ) {} async check(patient: Patient): Promise { if (!isNil(patient.subscription)) { const token = await this.subscriptionService.getToken(patient.subscription) patient.subscription.token = token } const appleInfo = await this.appleInAppSubscription.getInfo(patient.subscription) await this.checkIfNeedsCancel(patient, appleInfo) await this.checkIfNeedsCreate(patient, appleInfo) return patient.subscriber } private async checkIfNeedsCancel(patient: Patient, appleInfo: AppleInAppSubscription): Promise { if (!patient.subscriber) return const { subscription } = patient const planCode = await this.getPlanCode(subscription.planId, patient.country_code as CountryCode) if (appleInfo?.externalPlan === planCode) return const cancellationReasonId = appleInfo?.externalPlan ? CancellationsReasons.CHANGE_PLAN_BY_PROVIDER : CancellationsReasons.BY_PROVIDER subscription.cancellationAt = new Date() await this.subscriptionService.cancelSubscription({ patient, cancellationReason: { id: cancellationReasonId }, cancellationBy: { id: patient.id }, expiresAt: subscription.expiresAt || appleInfo?.expiration || new Date(), }) } private async checkIfNeedsCreate(patient: Patient, appleInfo: AppleInAppSubscription): Promise { if (patient.subscriber) return if (isNil(appleInfo)) return const plan = await this.getPlan({ code: appleInfo.externalPlan, countryCode: patient.country_code as CountryCode }) patient.subscription = await this.subscriptionService.addSubscription({ patient, plan, paymentProvider: { provider: PaymentProviders.APPLE }, options: { subscribedAt: appleInfo.purchasedAt, originalTransaction: appleInfo.originalTransaction, token: appleInfo.receipt, }, data: {}, }) } private async getPlanCode(id: string, countryCode: CountryCode): Promise { return (await this.getPlan({ id, countryCode })).getPaymentProvider(PaymentProviders.APPLE)?.code } private async getPlan({ id, code, countryCode, }: { id?: string code?: string countryCode: CountryCode }): Promise { let plan: Plan if (!isNil(id)) plan = this.plansCached[id] = this.plansCached[id] ?? (await this.planRepository.getWithFeatures(id, countryCode, PaymentProviders.APPLE)) else if (!isNil(code)) plan = this.plansCached[code] = this.plansCached[code] ?? (await this.planRepository.findByAddonCode(code, PaymentProviders.APPLE)) if (isNil(plan)) throw new PlanNotFoundError(`id: ${id}, code: ${code}`) return plan } }