feat: enhance card and transaction services for balance updates

- Added functionality to credit child account balance and decrease parent account balance in CardService.
- Updated TransactionService to reload card details for accurate balance after transactions.
- Improved TransactionNotificationListener to fetch updated balances for both child and parent accounts, ensuring accurate notifications.
This commit is contained in:
Abdalhamid Alhamad
2026-01-14 14:51:45 +03:00
parent c963b57904
commit 1b0d6cb284
4 changed files with 51 additions and 7 deletions

View File

@ -245,6 +245,10 @@ export class CardService {
this.neoleapService.updateCardControl(card.cardReference, finalAmountNumber),
this.updateCardLimit(card.id, finalAmountNumber),
this.accountService.increaseReservedBalance(fundingAccount, amount),
// Increase child account balance
this.accountService.creditAccountBalance(card.account.accountReference, amount),
// Decrease parent account balance (only if parent is funding)
card.parentId ? this.accountService.decreaseAccountBalance(fundingAccount.accountReference, amount) : Promise.resolve(),
]);
// Only create transaction and emit event after all operations succeed

View File

@ -110,9 +110,12 @@ export class TransactionService {
const card = await this.cardService.getCardById(cardId);
const transaction = await this.transactionRepository.createInternalChildTransaction(card, amount);
// Reload card to get updated account balance after the transfer
const cardWithUpdatedBalance = await this.cardService.getCardById(cardId);
const event: ITransactionCreatedEvent = {
transaction,
card,
card: cardWithUpdatedBalance, // Use card with updated balance
isTopUp: true,
isChildSpending: true,
timestamp: new Date(),

View File

@ -26,7 +26,10 @@ export class NotificationsResponseDto {
// Use event timestamp from data if available, otherwise use notification creation time
// This ensures notifications show when the event occurred, not when notification was saved
// Note: Timestamps are stored in UTC in the database, and should be converted to local timezone on the client side
if (notification.data?.timestamp) {
// Parse the ISO string timestamp (which is in UTC)
// The client should convert this to the user's local timezone
this.createdAt = new Date(notification.data.timestamp);
} else {
this.createdAt = notification.createdAt;

View File

@ -4,6 +4,7 @@ import { I18nService } from 'nestjs-i18n';
import { NotificationFactory, NotificationPreferences } from '../services/notification-factory.service';
import { UserService } from '~/user/services/user.service';
import { AccountService } from '~/card/services/account.service';
import { CardService } from '~/card/services/card.service';
import { NOTIFICATION_EVENTS } from '../constants/event-names.constant';
import { ITransactionCreatedEvent } from '../interfaces/notification-events.interface';
import { NotificationScope } from '../enums/notification-scope.enum';
@ -36,6 +37,8 @@ export class TransactionNotificationListener {
private readonly i18n: I18nService,
@Inject(forwardRef(() => AccountService))
private readonly accountService: AccountService,
@Inject(forwardRef(() => CardService))
private readonly cardService: CardService,
) {}
/**
@ -101,9 +104,13 @@ export class TransactionNotificationListener {
const locale = this.getUserLocale(user);
const amount = transaction.transactionAmount;
const merchant = transaction.merchantName || 'merchant';
const balance = card.account?.balance || 0;
// Reload card to get the latest account balance after transaction
const cardWithUpdatedBalance = await this.cardService.getCardById(card.id);
const balance = cardWithUpdatedBalance.account?.balance || card.account?.balance || 0;
const currency = getCurrency(
card.account?.currency,
cardWithUpdatedBalance.account?.currency || card.account?.currency,
transaction.transactionCurrency,
'SAR'
);
@ -200,14 +207,33 @@ export class TransactionNotificationListener {
const childName = childUser?.firstName || defaultChildName;
const amount = transaction.transactionAmount;
const merchant = transaction.merchantName || 'a merchant';
const balance = card.account?.balance || 0;
// Get parent's account balance (not child's balance)
let parentAccountBalance = 0;
let parentAccountCurrency: string | undefined;
try {
const parentCustomer = customer?.junior?.guardian?.customer;
if (parentCustomer?.cards?.[0]?.account) {
parentAccountBalance = parentCustomer.cards[0].account.balance;
parentAccountCurrency = parentCustomer.cards[0].account.currency;
} else if (card.parentId) {
const parentAccount = await this.accountService.getAccountByCustomerId(card.parentId);
parentAccountBalance = parentAccount.balance;
parentAccountCurrency = parentAccount.currency;
}
} catch (error) {
this.logger.warn(`Could not fetch parent account for parent notification, using child account balance as fallback`);
parentAccountBalance = card.account?.balance || 0;
parentAccountCurrency = card.account?.currency;
}
const currency = getCurrency(
card.account?.currency,
parentAccountCurrency || card.account?.currency,
transaction.transactionCurrency,
'SAR'
);
const formattedAmount = formatCurrencyAmount(amount, currency);
const formattedBalance = formatCurrencyAmount(balance, currency);
const formattedBalance = formatCurrencyAmount(parentAccountBalance, currency);
this.logger.debug(
`Notifying parent (user ${parentUser.id}): ${childName} spent ${formattedAmount} ${currency} at ${merchant}`
@ -249,7 +275,7 @@ export class TransactionNotificationListener {
currency: currency,
merchant: merchant,
merchantCategory: transaction.merchantCategoryCode || 'OTHER',
balance: balance.toString(),
balance: formattedBalance,
timestamp: transaction.transactionDate.toISOString(),
type: 'CHILD_SPENDING',
action: 'OPEN_TRANSACTION',
@ -290,12 +316,20 @@ export class TransactionNotificationListener {
const parentCustomer = customer?.junior?.guardian?.customer;
let parentAccount = parentCustomer?.cards?.[0]?.account;
// Reload parent account to get updated balance after transfer
if (!parentAccount && card.parentId) {
try {
parentAccount = await this.accountService.getAccountByCustomerId(card.parentId);
} catch (error) {
this.logger.warn(`Could not fetch parent account for customer ${card.parentId}, using child account balance`);
}
} else if (parentAccount && card.parentId) {
// Reload to get fresh balance
try {
parentAccount = await this.accountService.getAccountByCustomerId(card.parentId);
} catch (error) {
this.logger.warn(`Could not reload parent account, using cached balance`);
}
}
const balance = parentAccount?.balance || card.account?.balance || 0;