mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2026-03-10 20:41:46 +00:00
Merge pull request #82 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: enhance card and transaction services for balance updates
This commit is contained in:
@ -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
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
Reference in New Issue
Block a user