diff --git a/src/card/services/card.service.ts b/src/card/services/card.service.ts index 2638d58..63aa27d 100644 --- a/src/card/services/card.service.ts +++ b/src/card/services/card.service.ts @@ -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 diff --git a/src/card/services/transaction.service.ts b/src/card/services/transaction.service.ts index e94ac64..59edb2f 100644 --- a/src/card/services/transaction.service.ts +++ b/src/card/services/transaction.service.ts @@ -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(), diff --git a/src/common/modules/notification/dtos/response/notifications.response.dto.ts b/src/common/modules/notification/dtos/response/notifications.response.dto.ts index 56d4ef0..8240da7 100644 --- a/src/common/modules/notification/dtos/response/notifications.response.dto.ts +++ b/src/common/modules/notification/dtos/response/notifications.response.dto.ts @@ -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; diff --git a/src/common/modules/notification/listeners/transaction-notification.listener.ts b/src/common/modules/notification/listeners/transaction-notification.listener.ts index ac13712..8537d76 100644 --- a/src/common/modules/notification/listeners/transaction-notification.listener.ts +++ b/src/common/modules/notification/listeners/transaction-notification.listener.ts @@ -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;