mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2026-03-10 17:11:44 +00:00
feat: enhance transaction notification listener for internal transfer support
- Updated TransactionNotificationListener to differentiate between internal transfers and external top-ups for child accounts. - Added new notification scopes and messages for internal transfers from parent to child. - Improved balance retrieval logic to ensure accurate account balances are displayed in notifications. - Enhanced localization support by adding relevant keys for internal transfer notifications in both English and Arabic.
This commit is contained in:
@ -6,10 +6,14 @@ export enum NotificationScope {
|
|||||||
OTP = 'OTP',
|
OTP = 'OTP',
|
||||||
USER_INVITED = 'USER_INVITED',
|
USER_INVITED = 'USER_INVITED',
|
||||||
|
|
||||||
// Transaction notifications - Top-up
|
// Transaction notifications - Top-up (external funds)
|
||||||
CHILD_TOP_UP = 'CHILD_TOP_UP',
|
CHILD_TOP_UP = 'CHILD_TOP_UP',
|
||||||
PARENT_TOP_UP_CONFIRMATION = 'PARENT_TOP_UP_CONFIRMATION',
|
PARENT_TOP_UP_CONFIRMATION = 'PARENT_TOP_UP_CONFIRMATION',
|
||||||
|
|
||||||
|
// Transaction notifications - Internal Transfer (parent to child)
|
||||||
|
CHILD_INTERNAL_TRANSFER = 'CHILD_INTERNAL_TRANSFER',
|
||||||
|
PARENT_INTERNAL_TRANSFER = 'PARENT_INTERNAL_TRANSFER',
|
||||||
|
|
||||||
// Transaction notifications - Spending
|
// Transaction notifications - Spending
|
||||||
CHILD_SPENDING = 'CHILD_SPENDING',
|
CHILD_SPENDING = 'CHILD_SPENDING',
|
||||||
PARENT_SPENDING_ALERT = 'PARENT_SPENDING_ALERT',
|
PARENT_SPENDING_ALERT = 'PARENT_SPENDING_ALERT',
|
||||||
|
|||||||
@ -97,34 +97,52 @@ export class TransactionNotificationListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scope = isTopUp
|
// Determine scope: internal transfer (parent to child) vs external top-up
|
||||||
? NotificationScope.CHILD_TOP_UP
|
let scope: NotificationScope;
|
||||||
: NotificationScope.CHILD_SPENDING;
|
if (isTopUp) {
|
||||||
|
scope = isChildSpending
|
||||||
|
? NotificationScope.CHILD_INTERNAL_TRANSFER // Parent transferring to child
|
||||||
|
: NotificationScope.CHILD_TOP_UP; // External top-up
|
||||||
|
} else {
|
||||||
|
scope = NotificationScope.CHILD_SPENDING;
|
||||||
|
}
|
||||||
|
|
||||||
const locale = this.getUserLocale(user);
|
const locale = this.getUserLocale(user);
|
||||||
const amount = transaction.transactionAmount;
|
const amount = transaction.transactionAmount;
|
||||||
const merchant = transaction.merchantName || 'merchant';
|
const merchant = transaction.merchantName || 'merchant';
|
||||||
|
|
||||||
// For child top-up notifications, show card.limit (newAmount) instead of account balance
|
// For child notifications, show the child's account balance (available balance)
|
||||||
// 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) {
|
if (isTopUp) {
|
||||||
// For top-up: show card limit (newAmount from transfer response)
|
// For top-up: show child's account balance after the transfer
|
||||||
try {
|
try {
|
||||||
// Reload card to get updated limit
|
// Reload card to get account reference with fresh balance
|
||||||
const cardWithUpdatedLimit = await this.cardService.getCardById(card.id);
|
const cardWithUpdatedBalance = await this.cardService.getCardById(card.id);
|
||||||
balance = cardWithUpdatedLimit.limit || card.limit || 0;
|
if (cardWithUpdatedBalance?.account?.accountReference) {
|
||||||
accountCurrency = cardWithUpdatedLimit.account?.currency || card.account?.currency;
|
// Fetch by reference number to get fresh balance from database
|
||||||
this.logger.debug(
|
const account = await this.accountService.getAccountByReferenceNumber(
|
||||||
`[Child Top-Up Notification] Using card limit (newAmount) - limit: ${balance} ${accountCurrency}`
|
cardWithUpdatedBalance.account.accountReference
|
||||||
);
|
);
|
||||||
|
balance = account.balance;
|
||||||
|
accountCurrency = account.currency;
|
||||||
|
this.logger.debug(
|
||||||
|
`[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) {
|
} catch (error: any) {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`[Child Top-Up Notification] Could not reload card: ${error?.message}. Using card limit from event.`
|
`[Child Top-Up Notification] Could not fetch account by reference: ${error?.message}. Using card account balance.`
|
||||||
);
|
);
|
||||||
balance = card.limit || 0;
|
balance = card.account?.balance || 0;
|
||||||
accountCurrency = card.account?.currency;
|
accountCurrency = card.account?.currency;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -176,27 +194,36 @@ export class TransactionNotificationListener {
|
|||||||
let message: string;
|
let message: string;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
title = isTopUp
|
if (isTopUp) {
|
||||||
? this.i18n.t('app.NOTIFICATION.CHILD_TOP_UP_TITLE', { lang: locale })
|
// Internal transfer or external top-up
|
||||||
: this.i18n.t('app.NOTIFICATION.CHILD_SPENDING_TITLE', { lang: locale });
|
const titleKey = isChildSpending
|
||||||
|
? 'app.NOTIFICATION.CHILD_INTERNAL_TRANSFER_TITLE'
|
||||||
message = isTopUp
|
: 'app.NOTIFICATION.CHILD_TOP_UP_TITLE';
|
||||||
? this.i18n.t('app.NOTIFICATION.CHILD_TOP_UP_MESSAGE', {
|
const messageKey = isChildSpending
|
||||||
lang: locale,
|
? 'app.NOTIFICATION.CHILD_INTERNAL_TRANSFER_MESSAGE'
|
||||||
args: {
|
: 'app.NOTIFICATION.CHILD_TOP_UP_MESSAGE';
|
||||||
amount: formattedAmount,
|
|
||||||
currency: currency,
|
title = this.i18n.t(titleKey, { lang: locale });
|
||||||
balance: formattedBalance,
|
message = this.i18n.t(messageKey, {
|
||||||
},
|
lang: locale,
|
||||||
})
|
args: {
|
||||||
: this.i18n.t('app.NOTIFICATION.CHILD_SPENDING_MESSAGE', {
|
amount: formattedAmount,
|
||||||
lang: locale,
|
currency: currency,
|
||||||
args: {
|
balance: formattedBalance,
|
||||||
amount: formattedAmount,
|
},
|
||||||
currency: currency,
|
});
|
||||||
merchant: merchant,
|
} else {
|
||||||
},
|
// Spending
|
||||||
});
|
title = this.i18n.t('app.NOTIFICATION.CHILD_SPENDING_TITLE', { lang: locale });
|
||||||
|
message = this.i18n.t('app.NOTIFICATION.CHILD_SPENDING_MESSAGE', {
|
||||||
|
lang: locale,
|
||||||
|
args: {
|
||||||
|
amount: formattedAmount,
|
||||||
|
currency: currency,
|
||||||
|
merchant: merchant,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (i18nError: any) {
|
} catch (i18nError: any) {
|
||||||
console.error(`[TransactionNotificationListener] i18n error:`, i18nError);
|
console.error(`[TransactionNotificationListener] i18n error:`, i18nError);
|
||||||
this.logger.error(`i18n translation failed: ${i18nError?.message}`, i18nError?.stack);
|
this.logger.error(`i18n translation failed: ${i18nError?.message}`, i18nError?.stack);
|
||||||
@ -411,7 +438,7 @@ export class TransactionNotificationListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify parent when they top up their child's card
|
* Notify parent when they transfer money to their child's card (internal transfer)
|
||||||
* This is a confirmation notification for the parent
|
* This is a confirmation notification for the parent
|
||||||
*/
|
*/
|
||||||
private async notifyParentOfTopUp(transaction: Transaction, card: Card): Promise<void> {
|
private async notifyParentOfTopUp(transaction: Transaction, card: Card): Promise<void> {
|
||||||
@ -537,8 +564,8 @@ export class TransactionNotificationListener {
|
|||||||
let message: string;
|
let message: string;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
title = this.i18n.t('app.NOTIFICATION.PARENT_TOP_UP_TITLE', { lang: locale });
|
title = this.i18n.t('app.NOTIFICATION.PARENT_INTERNAL_TRANSFER_TITLE', { lang: locale });
|
||||||
message = this.i18n.t('app.NOTIFICATION.PARENT_TOP_UP_MESSAGE', {
|
message = this.i18n.t('app.NOTIFICATION.PARENT_INTERNAL_TRANSFER_MESSAGE', {
|
||||||
lang: locale,
|
lang: locale,
|
||||||
args: {
|
args: {
|
||||||
amount: formattedAmount,
|
amount: formattedAmount,
|
||||||
@ -548,9 +575,9 @@ export class TransactionNotificationListener {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (i18nError: any) {
|
} catch (i18nError: any) {
|
||||||
console.error(`[TransactionNotificationListener] i18n error in parent top-up:`, i18nError);
|
console.error(`[TransactionNotificationListener] i18n error in parent internal transfer:`, i18nError);
|
||||||
this.logger.error(`i18n translation failed: ${i18nError?.message}`, i18nError?.stack);
|
this.logger.error(`i18n translation failed: ${i18nError?.message}`, i18nError?.stack);
|
||||||
title = 'Transfer Completed';
|
title = 'Internal Transfer Completed';
|
||||||
message = `${formattedAmount} ${currency} has been transferred to ${childName}'s card. ${childName}'s balance is ${formattedBalance} ${currency}`;
|
message = `${formattedAmount} ${currency} has been transferred to ${childName}'s card. ${childName}'s balance is ${formattedBalance} ${currency}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -558,7 +585,7 @@ export class TransactionNotificationListener {
|
|||||||
userId: parentUser.id,
|
userId: parentUser.id,
|
||||||
title,
|
title,
|
||||||
message,
|
message,
|
||||||
scope: NotificationScope.PARENT_TOP_UP_CONFIRMATION,
|
scope: NotificationScope.PARENT_INTERNAL_TRANSFER,
|
||||||
preferences: this.getUserPreferences(parentUser),
|
preferences: this.getUserPreferences(parentUser),
|
||||||
data: {
|
data: {
|
||||||
transactionId: transaction.id,
|
transactionId: transaction.id,
|
||||||
|
|||||||
@ -112,17 +112,19 @@
|
|||||||
"NOT_FOUND": "لم يتم العثور على البطاقة."
|
"NOT_FOUND": "لم يتم العثور على البطاقة."
|
||||||
},
|
},
|
||||||
"NOTIFICATION": {
|
"NOTIFICATION": {
|
||||||
"CHILD_TOP_UP_TITLE": "تم إضافة الأموال",
|
"CHILD_TOP_UP_TITLE": "تم إضافة رصيد",
|
||||||
"CHILD_TOP_UP_MESSAGE": "تم إضافة {amount} {currency} إلى بطاقتك. الرصيد الإجمالي: {balance} {currency}",
|
"CHILD_TOP_UP_MESSAGE": "تمت إضافة {amount} {currency} إلى بطاقتك. إجمالي الرصيد: {balance} {currency}",
|
||||||
"CHILD_SPENDING_TITLE": "تمت العملية بنجاح",
|
"CHILD_INTERNAL_TRANSFER_TITLE": "تم إضافة رصيد",
|
||||||
"CHILD_SPENDING_MESSAGE": "لقد أنفقت {amount} {currency} في {merchant}",
|
"CHILD_INTERNAL_TRANSFER_MESSAGE": "تمت إضافة {amount} {currency} إلى بطاقتك. إجمالي الرصيد: {balance} {currency}",
|
||||||
"PARENT_TOP_UP_TITLE": "اكتمل التحويل",
|
"PARENT_INTERNAL_TRANSFER_TITLE": "اكتمل التحويل",
|
||||||
"PARENT_TOP_UP_MESSAGE": "تم تحويل {amount} {currency} إلى بطاقة {childName}. رصيد {childName} هو {balance} {currency}",
|
"PARENT_INTERNAL_TRANSFER_MESSAGE": "تم تحويل {amount} {currency} إلى بطاقة {childName}. رصيد {childName}: {balance} {currency}",
|
||||||
"PARENT_SPENDING_TITLE": "تنبيه الإنفاق",
|
"CHILD_SPENDING_TITLE": "عملية شراء ناجحة",
|
||||||
"PARENT_SPENDING_MESSAGE": "أنفق {childName} {amount} {currency} في {merchant}. الرصيد المتبقي: {balance} {currency}",
|
"CHILD_SPENDING_MESSAGE": "قمت بإنفاق {amount} {currency} في {merchant}",
|
||||||
|
"PARENT_SPENDING_TITLE": "تنبيه صرف",
|
||||||
|
"PARENT_SPENDING_MESSAGE": "قام {childName} بإنفاق {amount} {currency} في {merchant}. الرصيد المتبقي: {balance} {currency}",
|
||||||
"YOUR_CHILD": "طفلك",
|
"YOUR_CHILD": "طفلك",
|
||||||
"MONEY_REQUEST_CREATED_TITLE": "طلب مال",
|
"MONEY_REQUEST_CREATED_TITLE": "طلب مبلغ مالي",
|
||||||
"MONEY_REQUEST_CREATED_MESSAGE": "طلب {childName} مبلغ {amount} {currency} لـ {reason}.",
|
"MONEY_REQUEST_CREATED_MESSAGE": "طلب {childName} مبلغ {amount} {currency} لـ {reason}",
|
||||||
"MONEY_REQUEST_APPROVED_TITLE": "تمت الموافقة على طلب المال",
|
"MONEY_REQUEST_APPROVED_TITLE": "تمت الموافقة على طلب المال",
|
||||||
"MONEY_REQUEST_APPROVED_MESSAGE": "تمت الموافقة على طلبك بمبلغ {amount} {currency}. تمت إضافة المال إلى حسابك.",
|
"MONEY_REQUEST_APPROVED_MESSAGE": "تمت الموافقة على طلبك بمبلغ {amount} {currency}. تمت إضافة المال إلى حسابك.",
|
||||||
"MONEY_REQUEST_DECLINED_TITLE": "تم رفض طلب المال",
|
"MONEY_REQUEST_DECLINED_TITLE": "تم رفض طلب المال",
|
||||||
|
|||||||
@ -113,10 +113,12 @@
|
|||||||
"NOTIFICATION": {
|
"NOTIFICATION": {
|
||||||
"CHILD_TOP_UP_TITLE": "Funds Credited",
|
"CHILD_TOP_UP_TITLE": "Funds Credited",
|
||||||
"CHILD_TOP_UP_MESSAGE": "{amount} {currency} has been added to your card. Total balance: {balance} {currency}",
|
"CHILD_TOP_UP_MESSAGE": "{amount} {currency} has been added to your card. Total balance: {balance} {currency}",
|
||||||
|
"CHILD_INTERNAL_TRANSFER_TITLE": "Funds Credited",
|
||||||
|
"CHILD_INTERNAL_TRANSFER_MESSAGE": "{amount} {currency} has been added to your card. Total balance: {balance} {currency}",
|
||||||
|
"PARENT_INTERNAL_TRANSFER_TITLE": "Internal Transfer Completed",
|
||||||
|
"PARENT_INTERNAL_TRANSFER_MESSAGE": "{amount} {currency} has been transferred to {childName}'s card. {childName}'s balance is {balance} {currency}",
|
||||||
"CHILD_SPENDING_TITLE": "Purchase Successful",
|
"CHILD_SPENDING_TITLE": "Purchase Successful",
|
||||||
"CHILD_SPENDING_MESSAGE": "You spent {amount} {currency} at {merchant}",
|
"CHILD_SPENDING_MESSAGE": "You spent {amount} {currency} at {merchant}",
|
||||||
"PARENT_TOP_UP_TITLE": "Transfer Completed",
|
|
||||||
"PARENT_TOP_UP_MESSAGE": "{amount} {currency} has been transferred to {childName}'s card. {childName}'s balance is {balance} {currency}",
|
|
||||||
"PARENT_SPENDING_TITLE": "Spending Alert",
|
"PARENT_SPENDING_TITLE": "Spending Alert",
|
||||||
"PARENT_SPENDING_MESSAGE": "{childName} spent {amount} {currency} at {merchant}. Remaining balance: {balance} {currency}",
|
"PARENT_SPENDING_MESSAGE": "{childName} spent {amount} {currency} at {merchant}. Remaining balance: {balance} {currency}",
|
||||||
"YOUR_CHILD": "Your child",
|
"YOUR_CHILD": "Your child",
|
||||||
|
|||||||
Reference in New Issue
Block a user