Files
zod-backend/src/card/services/card.service.ts

187 lines
6.4 KiB
TypeScript

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<Card> {
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<Card[]> {
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<Card> {
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<Card> {
const card = await this.cardRepository.getCardById(id);
if (!card) {
throw new BadRequestException('CARD.NOT_FOUND');
}
return card;
}
async getCardByReferenceNumber(referenceNumber: string): Promise<Card> {
const card = await this.cardRepository.getCardByReferenceNumber(referenceNumber);
if (!card) {
throw new BadRequestException('CARD.NOT_FOUND');
}
return card;
}
async getCardByVpan(vpan: string): Promise<Card> {
const card = await this.cardRepository.getCardByVpan(vpan);
if (!card) {
throw new BadRequestException('CARD.NOT_FOUND');
}
return card;
}
async getCardByCustomerId(customerId: string): Promise<Card> {
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);
}
}),
);
}
}