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.
This commit is contained in:
Abdalhamid Alhamad
2026-01-20 16:27:32 +03:00
parent a09b84e475
commit f5c3b03264
2 changed files with 72 additions and 19 deletions

View File

@ -240,16 +240,37 @@ export class CardService {
this.logger.debug(`Updating card control - cardReference: ${card.cardReference}, finalAmount: ${finalAmountNumber}`); 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 // First, ensure all external operations succeed before creating transaction
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([ await Promise.all([
this.neoleapService.updateCardControl(card.cardReference, finalAmountNumber), this.neoleapService.updateCardControl(card.cardReference, finalAmountNumber),
this.updateCardLimit(card.id, finalAmountNumber), this.updateCardLimit(card.id, finalAmountNumber),
this.accountService.increaseReservedBalance(fundingAccount, amount), this.accountService.increaseReservedBalance(fundingAccount, amount),
// Increase child account balance // Increase child account balance
this.accountService.creditAccountBalance(card.account.accountReference, amount), this.accountService.creditAccountBalance(card.account.accountReference, amount),
// Decrease parent account balance (only if parent is funding) // Decrease parent account balance
card.parentId ? this.accountService.decreaseAccountBalance(fundingAccount.accountReference, amount) : Promise.resolve(), this.accountService.decreaseAccountBalance(fundingAccount.accountReference, amount),
]); ]);
}
// Only create transaction and emit event after all operations succeed // Only create transaction and emit event after all operations succeed
await this.transactionService.createInternalChildTransaction(card.id, amount); await this.transactionService.createInternalChildTransaction(card.id, amount);

View File

@ -111,17 +111,53 @@ export class TransactionNotificationListener {
const amount = transaction.transactionAmount; const amount = transaction.transactionAmount;
const merchant = transaction.merchantName || 'merchant'; 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 balance = 0;
let accountCurrency: string | undefined; let accountCurrency: string | undefined;
if (isTopUp) { if (isTopUp && isChildSpending) {
// For top-up: show child's account balance after the transfer // 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 { try {
// Reload card to get account reference with fresh balance
const cardWithUpdatedBalance = await this.cardService.getCardById(card.id); const cardWithUpdatedBalance = await this.cardService.getCardById(card.id);
if (cardWithUpdatedBalance?.account?.accountReference) { if (cardWithUpdatedBalance?.account?.accountReference) {
// Fetch by reference number to get fresh balance from database
const account = await this.accountService.getAccountByReferenceNumber( const account = await this.accountService.getAccountByReferenceNumber(
cardWithUpdatedBalance.account.accountReference cardWithUpdatedBalance.account.accountReference
); );
@ -131,16 +167,12 @@ export class TransactionNotificationListener {
`[Child Top-Up Notification] Fetched account by reference - balance: ${balance} ${accountCurrency}` `[Child Top-Up Notification] Fetched account by reference - balance: ${balance} ${accountCurrency}`
); );
} else { } else {
// 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(
`[Child Top-Up Notification] Using card account balance - balance: ${balance} ${accountCurrency}`
);
} }
} catch (error: any) { } catch (error: any) {
this.logger.warn( 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; balance = card.account?.balance || 0;
accountCurrency = card.account?.currency; accountCurrency = card.account?.currency;