import { Injectable } from '@nestjs/common' import { isNil } from 'lodash' import { BusinessExceptionError, NotFoundError } from '../../../errors' import { Patient, PaymentProviders, Subscription as SubscriptionModel } from '../../../models' import { PatientRepository, PlanRepository, SubscriptionTransactionRepository } from '../../../repositories' import { CardData, ForeignIntegrationStatus, OriginRequest, SmartfitApi, SubscriptionService, TransactionError, } from '../../../services' import { CountryCode } from '../../../utils/countries' import { InvoiceInstallment, N2BPaymentNotification, N2BPaymentRefundNotification } from './types' @Injectable() export class Subscription { constructor( private readonly subscriptionService: SubscriptionService, private readonly planRepository: PlanRepository, private readonly patientRepository: PatientRepository, private readonly smartfitAPI: SmartfitApi, private readonly subscriptionTransactionRepository: SubscriptionTransactionRepository, ) {} async updateSubscriberStatus(payment: N2BPaymentNotification): Promise { const patient = await this.getPatient(payment.invoice_installment.invoice.customer.external_id) const isPaid = payment.amount > 0 && !!payment.paid_at let subscription = patient.subscription if (!payment.transaction?.card) payment.transaction = { ...payment.transaction, card: await this.getCardData(patient.document_number), } if (payment.invoice_installment.installment_number === 1) subscription = await this.addSubscriptionAndLogTransaction(patient, payment) if (payment.invoice_installment.installment_number > 1) await this.updateSubscriptionAndLogTransaction(patient, payment) await this.createIntegrationOnSmartFit( patient, payment.invoice_installment.invoice.subscription.data.origin, subscription?.id || payment.id.toString(), isPaid ? 'active' : 'failed', payment.invoice_installment.installment_number > 1, ) } async expiresSubscription(refund: N2BPaymentRefundNotification): Promise { const patient = await this.getPatient(refund.invoice_pay.invoice_installment.invoice.customer.external_id) const isRefunded = refund.amount > 0 && !!refund.refunded_at if (!patient) throw new NotFoundError('Patient not found') const installment: InvoiceInstallment = { ...refund.invoice_pay?.invoice_installment, invoice: refund.invoice_pay?.invoice_installment?.invoice, amount: -refund.amount, due_date: new Date(), card_installments: refund.invoice_pay?.card_installments, } const subscriptionId = patient.subscription.id.toString() const cardData = refund.transaction?.card ?? (await this.getCardDataByLastTransaction(subscriptionId)) await this.addTransaction( refund.id, installment, cardData, patient, patient.subscription?.paymentProvider === PaymentProviders.N2B && patient.subscription, refund.refunded_at, refund.transaction?.error, ) if (patient.subscription?.paymentProvider !== PaymentProviders.N2B) return if (!patient.subscription?.isCancelled) { if (isRefunded) await this.subscriptionService.updateCancellationDate(patient.subscription, new Date()) else await this.subscriptionService.uncancel(patient.subscription) } await this.createIntegrationOnSmartFit( patient, refund.invoice_pay.invoice_installment.invoice.subscription.data.origin, subscriptionId, isRefunded ? 'cancelled' : 'failed', ) } private async getCardDataByLastTransaction(subscriptionId: string): Promise { const lastTransaction = await this.subscriptionTransactionRepository.findLastBySubscription(subscriptionId) return lastTransaction?.cardData } private async getCardData(documentNumber: string): Promise { const person = await this.smartfitAPI.getPersonByLogin(documentNumber) if (!person) throw new NotFoundError('Person not found') const wallet = await this.smartfitAPI.getWalletToken(person.authToken) return { last4: person.lastDigitsCardNumber, kind: wallet.kind, } } private async getPatient(id: string): Promise { const patient = await this.patientRepository.get(id) if (!patient) throw new NotFoundError('Patient not found') return patient } private async addSubscriptionAndLogTransaction( patient: Patient, payment: N2BPaymentNotification, ): Promise { if (patient.subscriber) throw new BusinessExceptionError('Patient already subscribed') const isPaid = payment.amount > 0 && !!payment.paid_at let subscription: SubscriptionModel if (isPaid) { const plan = await this.planRepository.getWithFeatures( payment.invoice_installment.invoice.subscription.external_reference_id, patient.country_code as CountryCode, PaymentProviders.N2B, ) if (!plan) throw new NotFoundError('Plan not found') const options: Partial = { cardNumber: payment.transaction.card?.last4, subscribedAt: new Date(), originalTransaction: payment.invoice_installment?.invoice?.subscription_id?.toString(), expiresAt: payment.invoice_installment.invoice.subscription.expires_at || null, cancellationAt: payment.invoice_installment.invoice.subscription.cancelled_at || null, } subscription = await this.subscriptionService.addSubscription({ patient, plan, paymentProvider: { provider: PaymentProviders.N2B }, options, data: { ...payment.invoice_installment?.invoice?.subscription?.data }, }) const installments = payment.invoice_installment.invoice.installments await this.subscriptionService.addInstallments(installments, subscription) } await this.addTransaction( payment.id, payment.invoice_installment, payment.transaction?.card, patient, subscription, payment.paid_at, payment.transaction?.error, ) return subscription } private async updateSubscriptionAndLogTransaction(patient: Patient, payment: N2BPaymentNotification): Promise { await this.updateSubscriptionData(patient.subscription, payment) await this.addTransaction( payment.id, payment.invoice_installment, payment.transaction?.card, patient, patient.subscription, payment.paid_at, payment.transaction?.error, ) } private async addTransaction( id: number, installment: InvoiceInstallment, card: CardData, patient: Patient, subscription: SubscriptionModel, paidAt: Date, error: TransactionError = null, ): Promise { await this.subscriptionService.addTransaction({ amount: installment.amount, due_at: installment.due_date, installments: installment.card_installments, data: { ...subscription?.data, ...installment.invoice.subscription.data }, installment_number: installment.installment_number, paid_at: paidAt, card_number: card.last4, card_type: card.kind, payment_id: id, payment_provider: PaymentProviders.N2B, subscription, patient, error, }) } private async createIntegrationOnSmartFit( patient: Patient, origin: OriginRequest, transactionId: string, foreignIntegrationStatus: ForeignIntegrationStatus = 'active', update?: boolean, paymentProvider: PaymentProviders = PaymentProviders.N2B, ): Promise { if (paymentProvider !== PaymentProviders.N2B || origin !== 'smartfit') return try { await this.smartfitAPI.createIntegration( transactionId, patient.document_number, patient.email, foreignIntegrationStatus, !isNil(update) ? update : foreignIntegrationStatus === 'cancelled', ) } catch (error) { console.error(error) } } private async updateSubscriptionData( subscription: SubscriptionModel, payment: N2BPaymentNotification, ): Promise { const isPaid = payment.amount > 0 && !!payment.paid_at if (isPaid) { if (!isNil(subscription.suspendedAt)) await this.subscriptionService.updateSuspendedDate(subscription, null) await this.subscriptionService.updateInstallmentPayment( subscription, payment.invoice_installment.installment_number, ) } else if (!isPaid && isNil(subscription.suspendedAt)) await this.subscriptionService.updateSuspendedDate(subscription, new Date()) if (!subscription.cardNumber && payment.transaction?.card?.last4) await this.subscriptionService.updateCardNumber(subscription, payment.transaction.card.last4) } }