import { BadRequestException, forwardRef, Inject, Injectable, Logger } from '@nestjs/common'; import Decimal from 'decimal.js'; import { Transactional } from 'typeorm-transactional'; import { AccountCardStatusChangedWebhookRequest } from '~/common/modules/neoleap/dtos/requests'; import { NeoLeapService } from '~/common/modules/neoleap/services'; import { Customer } from '~/customer/entities'; import { KycStatus } from '~/customer/enums'; import { CustomerService } from '~/customer/services'; import { OciService } from '~/document/services'; import { Card } from '../entities'; import { CardColors } from '../enums'; import { CardStatusMapper } from '../mappers/card-status.mapper'; import { CardRepository } from '../repositories'; import { AccountService } from './account.service'; import { TransactionService } from './transaction.service'; @Injectable() export class CardService { private readonly logger = new Logger(CardService.name); constructor( private readonly cardRepository: CardRepository, private readonly accountService: AccountService, private readonly ociService: OciService, @Inject(forwardRef(() => TransactionService)) private readonly transactionService: TransactionService, @Inject(forwardRef(() => NeoLeapService)) private readonly neoleapService: NeoLeapService, @Inject(forwardRef(() => CustomerService)) private readonly customerService: CustomerService, ) {} @Transactional() async createCard(customerId: string): Promise { const customer = await this.customerService.findCustomerById(customerId); if (customer.kycStatus !== KycStatus.APPROVED) { throw new BadRequestException('CUSTOMER.KYC_NOT_APPROVED'); } if (customer.cards.length > 0) { throw new BadRequestException('CUSTOMER.ALREADY_HAS_CARD'); } const data = await this.neoleapService.createApplication(customer); const account = await this.accountService.createAccount(data); const createdCard = await this.cardRepository.createCard(customerId, account.id, data); return this.getCardById(createdCard.id); } async getChildCards(guardianId: string): Promise { const cards = await this.cardRepository.findChildCardsForGuardian(guardianId); await this.prepareJuniorImages(cards); return cards; } async createCardForChild(parentCustomer: Customer, childCustomer: Customer, cardColor: CardColors, cardPin: string) { const data = await this.neoleapService.createChildCard(parentCustomer, childCustomer, cardPin); const createdCard = await this.cardRepository.createCard( childCustomer.id, parentCustomer.cards[0].account.id, data, cardColor, parentCustomer.id, ); return this.getCardById(createdCard.id); } async getCardByChildId(guardianId: string, childId: string): Promise { const card = await this.cardRepository.findCardByChildId(guardianId, childId); if (!card) { throw new BadRequestException('CARD.NOT_FOUND'); } await this.prepareJuniorImages([card]); return card; } async getCardById(id: string): Promise { const card = await this.cardRepository.getCardById(id); if (!card) { throw new BadRequestException('CARD.NOT_FOUND'); } return card; } async getCardByReferenceNumber(referenceNumber: string): Promise { const card = await this.cardRepository.getCardByReferenceNumber(referenceNumber); if (!card) { throw new BadRequestException('CARD.NOT_FOUND'); } return card; } async getCardByVpan(vpan: string): Promise { const card = await this.cardRepository.getCardByVpan(vpan); if (!card) { throw new BadRequestException('CARD.NOT_FOUND'); } return card; } async getCardByCustomerId(customerId: string): Promise { const card = await this.cardRepository.getCardByCustomerId(customerId); if (!card) { throw new BadRequestException('CARD.NOT_FOUND'); } return card; } async updateCardStatus(body: AccountCardStatusChangedWebhookRequest) { const card = await this.getCardByVpan(body.cardId); const { description, status } = CardStatusMapper[body.newStatus] || CardStatusMapper['99']; return this.cardRepository.updateCardStatus(card.id, status, description); } async getEmbossingInformation(customerId: string) { const card = await this.getCardByCustomerId(customerId); return this.neoleapService.getEmbossingInformation(card); } async getChildCardEmbossingInformation(cardId: string, guardianId: string) { const card = await this.getCardById(cardId); if (card.parentId !== guardianId) { throw new BadRequestException('CARD.DOES_NOT_BELONG_TO_GUARDIAN'); } return this.neoleapService.getEmbossingInformation(card); } async updateCardLimit(cardId: string, newLimit: number) { const { affected } = await this.cardRepository.updateCardLimit(cardId, newLimit); if (affected === 0) { throw new BadRequestException('CARD.NOT_FOUND'); } } async getIbanInformation(customerId: string) { const account = await this.accountService.getAccountByCustomerId(customerId); return account.iban; } @Transactional() async transferToChild(juniorId: string, amount: number) { const card = await this.getCardByCustomerId(juniorId); if (amount > card.account.balance - card.account.reservedBalance) { throw new BadRequestException('CARD.INSUFFICIENT_BALANCE'); } const finalAmount = Decimal(amount).plus(card.limit); await Promise.all([ this.neoleapService.updateCardControl(card.cardReference, finalAmount.toNumber()), this.updateCardLimit(card.id, finalAmount.toNumber()), this.accountService.increaseReservedBalance(card.account, amount), this.transactionService.createInternalChildTransaction(card.id, amount), ]); return finalAmount.toNumber(); } getWeeklySummary(juniorId: string, startDate?: Date, endDate?: Date) { return this.transactionService.getWeeklySummary(juniorId, startDate, endDate); } fundIban(iban: string, amount: number) { return this.accountService.fundIban(iban, amount); } private async prepareJuniorImages(cards: Card[]) { this.logger.log(`Preparing junior images`); await Promise.all( cards.map(async (card) => { const profilePicture = card.customer?.user?.profilePicture; if (profilePicture) { profilePicture.url = await this.ociService.generatePreSignedUrl(profilePicture); } }), ); } }