From f5c3b03264e1247aa4f09cd4766ca3ed5a45f8c6 Mon Sep 17 00:00:00 2001 From: Abdalhamid Alhamad Date: Tue, 20 Jan 2026 16:27:32 +0300 Subject: [PATCH] feat: enhance card service and transaction notification for shared account handling - Updated CardService to differentiate between shared and separate accounts during card control updates, optimizing balance allocation. - Enhanced TransactionNotificationListener to accurately reflect balance based on account structure for internal transfers and top-ups. - Improved logging for better traceability of account operations and balance calculations. --- src/card/services/card.service.ts | 39 ++++++++++---- .../transaction-notification.listener.ts | 52 +++++++++++++++---- 2 files changed, 72 insertions(+), 19 deletions(-) diff --git a/src/card/services/card.service.ts b/src/card/services/card.service.ts index 63aa27d..b38d6f0 100644 --- a/src/card/services/card.service.ts +++ b/src/card/services/card.service.ts @@ -240,16 +240,37 @@ export class CardService { this.logger.debug(`Updating card control - cardReference: ${card.cardReference}, finalAmount: ${finalAmountNumber}`); + // Check if child and parent share the same account + const isSharedAccount = card.parentId && card.account.id === fundingAccount.id; + + this.logger.debug( + `Account structure - Child account: ${card.account.id}, Parent account: ${fundingAccount.id}, ` + + `Shared: ${isSharedAccount ? 'YES' : 'NO'}` + ); + // First, ensure all external operations succeed before creating transaction - await Promise.all([ - 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(), - ]); + if (isSharedAccount) { + // Shared account: Only update card limit and reserved balance + // Money is already in the shared account, just allocate it to the child + this.logger.debug(`Shared account detected - only updating card limit and reserved balance`); + await Promise.all([ + this.neoleapService.updateCardControl(card.cardReference, finalAmountNumber), + this.updateCardLimit(card.id, finalAmountNumber), + this.accountService.increaseReservedBalance(fundingAccount, amount), + ]); + } else { + // Separate accounts: Transfer money from parent to child + this.logger.debug(`Separate accounts - transferring money from parent to child`); + await Promise.all([ + 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 + this.accountService.decreaseAccountBalance(fundingAccount.accountReference, amount), + ]); + } // Only create transaction and emit event after all operations succeed await this.transactionService.createInternalChildTransaction(card.id, amount); diff --git a/src/common/modules/notification/listeners/transaction-notification.listener.ts b/src/common/modules/notification/listeners/transaction-notification.listener.ts index 83e901f..35d5df2 100644 --- a/src/common/modules/notification/listeners/transaction-notification.listener.ts +++ b/src/common/modules/notification/listeners/transaction-notification.listener.ts @@ -111,17 +111,53 @@ export class TransactionNotificationListener { const amount = transaction.transactionAmount; const merchant = transaction.merchantName || 'merchant'; - // For child notifications, show the child's account balance (available balance) + // For child notifications, show the appropriate balance based on account structure let balance = 0; let accountCurrency: string | undefined; - if (isTopUp) { - // For top-up: show child's account balance after the transfer + if (isTopUp && isChildSpending) { + // Internal transfer: For shared accounts, show card limit (child's spending power) + // For separate accounts, show child's account balance + try { + // Reload card to get updated data + const cardWithUpdatedBalance = await this.cardService.getCardById(card.id); + + // Check if child has parent (shared account scenario) + if (cardWithUpdatedBalance.parentId) { + // Likely shared account - use card limit as the child's "balance" + balance = cardWithUpdatedBalance.limit || card.limit || 0; + accountCurrency = cardWithUpdatedBalance.account?.currency || card.account?.currency; + this.logger.debug( + `[Child Internal Transfer] Shared account - using card limit: ${balance} ${accountCurrency}` + ); + } else { + // Separate account - use child's account balance + if (cardWithUpdatedBalance?.account?.accountReference) { + const account = await this.accountService.getAccountByReferenceNumber( + cardWithUpdatedBalance.account.accountReference + ); + balance = account.balance; + accountCurrency = account.currency; + this.logger.debug( + `[Child Internal Transfer] Separate account - using account balance: ${balance} ${accountCurrency}` + ); + } else { + balance = cardWithUpdatedBalance.account?.balance || card.account?.balance || 0; + accountCurrency = cardWithUpdatedBalance.account?.currency || card.account?.currency; + } + } + } catch (error: any) { + this.logger.warn( + `[Child Internal Transfer] Could not fetch balance: ${error?.message}. Using card limit.` + ); + balance = card.limit || 0; + accountCurrency = card.account?.currency; + } + } else if (isTopUp) { + // External top-up: show child's account balance try { - // Reload card to get account reference with fresh balance const cardWithUpdatedBalance = await this.cardService.getCardById(card.id); if (cardWithUpdatedBalance?.account?.accountReference) { - // Fetch by reference number to get fresh balance from database const account = await this.accountService.getAccountByReferenceNumber( cardWithUpdatedBalance.account.accountReference ); @@ -131,16 +167,12 @@ export class TransactionNotificationListener { `[Child Top-Up Notification] Fetched account by reference - balance: ${balance} ${accountCurrency}` ); } else { - // Fallback: use card's account balance balance = cardWithUpdatedBalance.account?.balance || card.account?.balance || 0; accountCurrency = cardWithUpdatedBalance.account?.currency || card.account?.currency; - this.logger.debug( - `[Child Top-Up Notification] Using card account balance - balance: ${balance} ${accountCurrency}` - ); } } catch (error: any) { this.logger.warn( - `[Child Top-Up Notification] Could not fetch account by reference: ${error?.message}. Using card account balance.` + `[Child Top-Up Notification] Could not fetch account: ${error?.message}. Using card balance.` ); balance = card.account?.balance || 0; accountCurrency = card.account?.currency;