feat: refine transaction notification listener for improved balance calculations

- Updated TransactionNotificationListener to differentiate between child top-up and spending notifications, ensuring accurate balance retrieval.
- Enhanced error handling and fallback mechanisms for both child and parent account balance fetching.
- Improved logging to provide detailed insights into balance calculations, including available balance after accounting for reserved amounts.
This commit is contained in:
Abdalhamid Alhamad
2026-01-14 16:56:22 +03:00
parent 1086166e04
commit 7c9e0f0b51

View File

@ -105,11 +105,30 @@ export class TransactionNotificationListener {
const amount = transaction.transactionAmount; const amount = transaction.transactionAmount;
const merchant = transaction.merchantName || 'merchant'; const merchant = transaction.merchantName || 'merchant';
// Fetch account by reference number to get fresh balance (bypasses entity cache) // For child top-up notifications, show card.limit (newAmount) instead of account balance
// This ensures we get the updated balance after the transaction // card.limit represents the total spending limit on the card after the transfer
let balance = 0; let balance = 0;
let accountCurrency: string | undefined; let accountCurrency: string | undefined;
if (isTopUp) {
// For top-up: show card limit (newAmount from transfer response)
try {
// Reload card to get updated limit
const cardWithUpdatedLimit = await this.cardService.getCardById(card.id);
balance = cardWithUpdatedLimit.limit || card.limit || 0;
accountCurrency = cardWithUpdatedLimit.account?.currency || card.account?.currency;
this.logger.debug(
`[Child Top-Up Notification] Using card limit (newAmount) - limit: ${balance} ${accountCurrency}`
);
} catch (error: any) {
this.logger.warn(
`[Child Top-Up Notification] Could not reload card: ${error?.message}. Using card limit from event.`
);
balance = card.limit || 0;
accountCurrency = card.account?.currency;
}
} else {
// For spending: show account balance
try { try {
// Reload card to get account reference // Reload card to get account reference
const cardWithUpdatedBalance = await this.cardService.getCardById(card.id); const cardWithUpdatedBalance = await this.cardService.getCardById(card.id);
@ -121,24 +140,25 @@ export class TransactionNotificationListener {
balance = account.balance; balance = account.balance;
accountCurrency = account.currency; accountCurrency = account.currency;
this.logger.debug( this.logger.debug(
`[Child Notification] Fetched account by reference - balance: ${balance} ${accountCurrency}` `[Child Spending Notification] Fetched account by reference - balance: ${balance} ${accountCurrency}`
); );
} else { } else {
// Fallback: use card's account balance // Fallback: use card's account balance
balance = cardWithUpdatedBalance.account?.balance || card.account?.balance || 0; balance = cardWithUpdatedBalance.account?.balance || card.account?.balance || 0;
accountCurrency = cardWithUpdatedBalance.account?.currency || card.account?.currency; accountCurrency = cardWithUpdatedBalance.account?.currency || card.account?.currency;
this.logger.debug( this.logger.debug(
`[Child Notification] Using card account balance - balance: ${balance} ${accountCurrency}` `[Child Spending Notification] Using card account balance - balance: ${balance} ${accountCurrency}`
); );
} }
} catch (error: any) { } catch (error: any) {
this.logger.warn( this.logger.warn(
`[Child Notification] Could not fetch account by reference: ${error?.message}. Using card account balance.` `[Child Spending Notification] Could not fetch account by reference: ${error?.message}. Using card account balance.`
); );
// Fallback: use card's account balance // Fallback: use card's account balance
balance = card.account?.balance || 0; balance = card.account?.balance || 0;
accountCurrency = card.account?.currency; accountCurrency = card.account?.currency;
} }
}
const currency = getCurrency( const currency = getCurrency(
accountCurrency, accountCurrency,
transaction.transactionCurrency, transaction.transactionCurrency,
@ -362,13 +382,16 @@ export class TransactionNotificationListener {
const amount = transaction.transactionAmount; const amount = transaction.transactionAmount;
// Fetch parent account by reference number to get fresh balance (bypasses entity cache) // Fetch parent account by reference number to get fresh balance (bypasses entity cache)
// This ensures we get the updated balance after the transfer // For parent notification, show available_balance = balance - reserved_balance
let parentAccountBalance = 0; let parentAccountBalance = 0;
let parentAccountReservedBalance = 0;
let parentAccountCurrency: string | undefined; let parentAccountCurrency: string | undefined;
let availableBalance = 0;
if (card.parentId) { if (card.parentId) {
try { try {
// Get parent's card to access their account reference // Get parent's card to access their account reference
// card.parentId is the parent's CUSTOMER ID
const parentCard = await this.cardService.getCardByCustomerId(card.parentId); const parentCard = await this.cardService.getCardByCustomerId(card.parentId);
if (parentCard?.account?.accountReference) { if (parentCard?.account?.accountReference) {
// Fetch by reference number to get fresh balance from database // Fetch by reference number to get fresh balance from database
@ -376,24 +399,28 @@ export class TransactionNotificationListener {
parentCard.account.accountReference parentCard.account.accountReference
); );
parentAccountBalance = parentAccount.balance; parentAccountBalance = parentAccount.balance;
parentAccountReservedBalance = parentAccount.reservedBalance;
availableBalance = parentAccountBalance - parentAccountReservedBalance;
parentAccountCurrency = parentAccount.currency; parentAccountCurrency = parentAccount.currency;
this.logger.debug( this.logger.debug(
`[Parent Top-Up] Fetched parent account by reference - balance: ${parentAccountBalance} ${parentAccountCurrency}` `[Parent Top-Up] Fetched parent account by reference - balance: ${parentAccountBalance}, reserved: ${parentAccountReservedBalance}, available: ${availableBalance} ${parentAccountCurrency}`
); );
} else { } else {
// Fallback: try by customer ID // Fallback: try by customer ID
const parentAccount = await this.accountService.getAccountByCustomerId(card.parentId); const parentAccount = await this.accountService.getAccountByCustomerId(card.parentId);
parentAccountBalance = parentAccount.balance; parentAccountBalance = parentAccount.balance;
parentAccountReservedBalance = parentAccount.reservedBalance;
availableBalance = parentAccountBalance - parentAccountReservedBalance;
parentAccountCurrency = parentAccount.currency; parentAccountCurrency = parentAccount.currency;
this.logger.debug( this.logger.debug(
`[Parent Top-Up] Fetched parent account by customer ID - balance: ${parentAccountBalance} ${parentAccountCurrency}` `[Parent Top-Up] Fetched parent account by customer ID - balance: ${parentAccountBalance}, reserved: ${parentAccountReservedBalance}, available: ${availableBalance} ${parentAccountCurrency}`
); );
} }
} catch (error: any) { } catch (error: any) {
this.logger.warn( this.logger.warn(
`[Parent Top-Up] Could not fetch parent account for customer ${card.parentId}: ${error?.message}. Using child account balance as fallback.` `[Parent Top-Up] Could not fetch parent account for customer ${card.parentId}: ${error?.message}. Using child account balance as fallback.`
); );
parentAccountBalance = card.account?.balance || 0; availableBalance = card.account?.balance || 0;
parentAccountCurrency = card.account?.currency; parentAccountCurrency = card.account?.currency;
} }
} else { } else {
@ -407,32 +434,37 @@ export class TransactionNotificationListener {
parentCard.account.accountReference parentCard.account.accountReference
); );
parentAccountBalance = parentAccount.balance; parentAccountBalance = parentAccount.balance;
parentAccountReservedBalance = parentAccount.reservedBalance;
availableBalance = parentAccountBalance - parentAccountReservedBalance;
parentAccountCurrency = parentAccount.currency; parentAccountCurrency = parentAccount.currency;
this.logger.debug( this.logger.debug(
`[Parent Top-Up] Fetched parent account via customer relation (by reference) - balance: ${parentAccountBalance} ${parentAccountCurrency}` `[Parent Top-Up] Fetched parent account via customer relation (by reference) - balance: ${parentAccountBalance}, reserved: ${parentAccountReservedBalance}, available: ${availableBalance} ${parentAccountCurrency}`
); );
} else { } else {
const parentAccount = await this.accountService.getAccountByCustomerId(parentCustomer.id); const parentAccount = await this.accountService.getAccountByCustomerId(parentCustomer.id);
parentAccountBalance = parentAccount.balance; parentAccountBalance = parentAccount.balance;
parentAccountReservedBalance = parentAccount.reservedBalance;
availableBalance = parentAccountBalance - parentAccountReservedBalance;
parentAccountCurrency = parentAccount.currency; parentAccountCurrency = parentAccount.currency;
this.logger.debug( this.logger.debug(
`[Parent Top-Up] Fetched parent account via customer relation - balance: ${parentAccountBalance} ${parentAccountCurrency}` `[Parent Top-Up] Fetched parent account via customer relation - balance: ${parentAccountBalance}, reserved: ${parentAccountReservedBalance}, available: ${availableBalance} ${parentAccountCurrency}`
); );
} }
} catch (error: any) { } catch (error: any) {
this.logger.warn( this.logger.warn(
`[Parent Top-Up] Could not fetch parent account via customer: ${error?.message}. Using child account balance as fallback.` `[Parent Top-Up] Could not fetch parent account via customer: ${error?.message}. Using child account balance as fallback.`
); );
parentAccountBalance = card.account?.balance || 0; availableBalance = card.account?.balance || 0;
parentAccountCurrency = card.account?.currency; parentAccountCurrency = card.account?.currency;
} }
} else { } else {
parentAccountBalance = card.account?.balance || 0; availableBalance = card.account?.balance || 0;
parentAccountCurrency = card.account?.currency; parentAccountCurrency = card.account?.currency;
} }
} }
const balance = parentAccountBalance; // Use available_balance for parent notification (balance - reserved_balance)
const balance = availableBalance;
const accountCurrency = parentAccountCurrency; const accountCurrency = parentAccountCurrency;
const currency = getCurrency( const currency = getCurrency(
accountCurrency, accountCurrency,