import { Injectable } from '@nestjs/common' import { ceil } from 'lodash' import { BusinessExceptionError, NotFoundError } from '../../../errors' import { CancellationsReasons, CardType, Patient, Plan, Subscription, SubscriptionTransaction } from '../../../models' import { PlanRepository, SubscriptionRepository, SubscriptionTransactionRepository } from '../../../repositories' import { AccountService, SalesChannel, SubscriptionService, TokenType } from '../../../services' import { PAYMENT_PROVIDER_DEFAULT } from './consts' import { planToPlanResult } from './converters' import { PersonResult, SubscriptionResult, TransactionResult } from './types' type ObjectCached = { [planId: string]: Value } @Injectable() export class Person { private planCached: ObjectCached = {} constructor( private readonly planRepository: PlanRepository, private readonly subscriptionRepository: SubscriptionRepository, private readonly subscriptionService: SubscriptionService, private readonly subscriptionTransactionRepository: SubscriptionTransactionRepository, private readonly accountService: AccountService, ) {} async get(login: string): Promise { const patient = await this.getPatient(login) if (!patient) throw new NotFoundError('Person not found') const patientData = { id: patient.id, name: patient.name, documentNumber: patient.document_number, email: patient.email, countryCode: patient.country_code, } return { ...patientData, subscription: patient.subscription ? await this.getSubscriptionResult(patient, patient.subscription, patient.smartfit_unit_acronym) : null, } } async getSubscriptions(login: string): Promise> { const patient = await this.getPatient(login) if (!patient) throw new NotFoundError('Person not found') const subscriptions = await this.subscriptionRepository.getByPatient(patient.id) return Promise.all( subscriptions.map(subscription => this.getSubscriptionResult(patient, subscription, patient.smartfit_unit_acronym), ), ) } async subscribe( login: string, token: string, kind: CardType, source: string, planId: string, unitId: number, discount: number, salesChannel?: SalesChannel, ): Promise { const patient = await this.getPatient(login) if (!patient) throw new NotFoundError('Person not found') if (patient.collaborator) throw new BusinessExceptionError('Person is a collaborator') const plan = await this.getPlan(planId, patient) if (!plan) throw new NotFoundError('Plan not found') await this.subscriptionService.subscribe({ patient, plan, data: { sales_channel: salesChannel, origin: 'smartfit', smartfit_unit_id: unitId }, paymentProvider: { provider: PAYMENT_PROVIDER_DEFAULT, data: { card: { token, kind, tokenType: TokenType.PAYMENT_METHOD }, discount, unitId, source }, }, }) } async unsubscribe(login: string): Promise { const patient = await this.getPatient(login) if (!patient) throw new NotFoundError('Person not found') return await this.subscriptionService.cancelSubscription({ patient, cancellationReason: { id: CancellationsReasons.BY_PROVIDER }, cancellationBy: { id: patient.id }, amount: await this.getAmountToRefund(patient.subscription), }) } private async getPatient(login: string): Promise { return this.accountService.createOrUpdatePatient(login) } private async getAmountToRefund(subscription: Subscription): Promise { if (!subscription?.isTrial) return 0 return this.subscriptionService.getSubscriptionAmountToRefund(subscription) } private async getPlan(planId: string, patient: Patient): Promise { if (this.planCached[planId]) return this.planCached[planId] const plan = await this.planRepository.get(planId, { language: patient.country_code }) return (this.planCached[planId] = plan) } private async getSubscriptionResult( patient: Patient, subscription: Subscription, unitAcronym: string, ): Promise { const transactions = await this.subscriptionTransactionRepository.findBySubscription(subscription.id) const plan = await this.getPlan(subscription.planId, patient) const totalPaid = transactions.reduce( (total, transaction) => (total += transaction.amount > 0 && transaction.status === 'success' ? transaction.amount : 0), 0, ) const totalRefund = transactions.reduce( (total, transaction) => (total += transaction.amount < 0 && transaction.status === 'success' ? -transaction.amount : 0), 0, ) if (!plan) throw new NotFoundError(`Subscription's plan not found`) const planData = planToPlanResult(plan, subscription.paymentProvider, unitAcronym) const subscriptionData: SubscriptionResult = { id: subscription.id, paymentProvider: subscription.paymentProvider, contractURL: subscription.contractURL, subscribedAt: subscription.subscribedAt || subscription.createdAt, discount: 0, totalPaid: ceil(totalPaid - totalRefund, 2), notPaidYet: ceil(plan.getPaymentProvider(subscription.paymentProvider).amount - totalPaid, 2), plan: planData, transactions: transactions.map(transaction => this.bindTransaction(transaction)), } if (subscription.cancellationRequestAt) subscriptionData.cancelledRequestedAt = subscription.cancellationRequestAt if (subscription.expiresAt) subscriptionData[subscription.expiresAt.getTime() <= new Date().getTime() ? 'expiredAt' : 'expiresAt'] = subscription.expiresAt if (subscription.cancellationAt) { subscriptionData.expiredAt = subscription.cancellationAt delete subscriptionData.expiresAt } return subscriptionData } private bindTransaction = (subscriptionTransaction: SubscriptionTransaction): TransactionResult => { const plain = subscriptionTransaction.toPlain() plain.dueAt = plain.due_at plain.paidAt = plain.paid_at delete plain.due_at delete plain.paid_at delete plain.patient_id delete plain.subscription_id return plain } }