Compare commits

..
Author SHA1 Message Date
Abdalhamid Alhamad 64623c7cea fix: correct junior ID to customer ID mapping in transaction queries
Fixed spending history and related transaction queries that were incorrectly
using juniorId as customerId. The queries now properly join through the
Customer -> Junior relationship to filter by junior ID.

Affected methods:
- getTransactionsForCardWithinDateRange (spending history)
- findTransfersToJunior (transfers list)
- countTransfersToJunior (transfers count)
- findTransactionById (transaction details)

This fixes the spending history endpoint which was returning empty results
due to ID mismatch between Junior entity ID and Customer entity ID.

Performance impact: Minimal (~1-2ms overhead from additional joins on
indexed foreign keys). The queries now return correct results instead of
0 results.
2026-01-20 12:39:10 +03:00
Majdalkilany0 4ca8123a67 Merge pull request #85 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: refine transaction notification listener for improved balance c…
2026-01-14 16:57:36 +03:00
Majdalkilany0 e734060c52 Merge pull request #84 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: improve transaction notification listener for accurate balance …
2026-01-14 16:33:05 +03:00
Majdalkilany0 5e6c8d96de Merge pull request #83 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: add timezone support to user and device entities
2026-01-14 16:12:59 +03:00
Majdalkilany0 0f56381703 Merge pull request #82 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: enhance card and transaction services for balance updates
2026-01-14 14:52:57 +03:00
Majdalkilany0 887bd20217 Merge pull request #81 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: enhance card service validation and notification integration
2026-01-14 13:27:56 +03:00
Majdalkilany0 2e21acac7f Merge pull request #80 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: implement KYC and card notification events
2026-01-14 12:57:11 +03:00
Majdalkilany0 652359b1bf Merge pull request #79 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: enhance transaction notification logging and error handling
2026-01-12 16:48:13 +03:00
Majdalkilany0 2d6524be9f Merge pull request #78 from Zod-Alkhair/feature/notification-system-fcm-registration
refactor: standardize notification message formatting
2026-01-12 16:30:32 +03:00
Majdalkilany0 3ab00dfc29 Merge pull request #76 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: implement money request notification system
2026-01-12 16:15:19 +03:00
Majdalkilany0 11b2b25adc Merge pull request #75 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: enhance Redis module exports for pub/sub functionality
2026-01-11 14:40:12 +03:00
Majdalkilany0 ed8cf4b4f8 Merge pull request #74 from Zod-Alkhair/feature/notification-system-fcm-registration
Feature/notification system fcm registration
2026-01-11 14:31:58 +03:00
Majdalkilany0 98f6aaf01f Merge pull request #73 from Zod-Alkhair/feature/notification-system-fcm-registration
add eveint lestiner to the parent
2026-01-06 14:52:52 +03:00
Majdalkilany0 16f8756b74 Merge pull request #72 from Zod-Alkhair/feature/notification-system-fcm-registration
merge conflect
2026-01-06 12:58:49 +03:00
Majdalkilany0 f849003142 Merge pull request #71 from Zod-Alkhair/feature/notification-system-fcm-registration
Feature/notification system fcm registration
2026-01-06 12:54:45 +03:00
8 changed files with 110 additions and 275 deletions
@@ -92,7 +92,9 @@ export class TransactionRepository {
return this.transactionRepository
.createQueryBuilder('transaction')
.innerJoinAndSelect('transaction.card', 'card')
.where('card.customerId = :juniorId', { juniorId })
.innerJoin('card.customer', 'customer')
.innerJoin('customer.junior', 'junior')
.where('junior.id = :juniorId', { juniorId })
.andWhere('transaction.transactionScope = :scope', { scope: TransactionScope.CARD })
.andWhere('transaction.transactionType = :type', { type: TransactionType.EXTERNAL })
.andWhere('transaction.transactionDate BETWEEN :startDate AND :endDate', { startDate, endDate })
@@ -153,7 +155,9 @@ export class TransactionRepository {
.createQueryBuilder('tx')
.innerJoinAndSelect('tx.card', 'card')
.innerJoinAndSelect('card.account', 'account')
.where('card.customerId = :juniorId', { juniorId })
.innerJoin('card.customer', 'customer')
.innerJoin('customer.junior', 'junior')
.where('junior.id = :juniorId', { juniorId })
.andWhere('tx.transactionScope = :scope', { scope: TransactionScope.CARD })
.andWhere('tx.transactionType = :type', { type: TransactionType.INTERNAL })
.orderBy('tx.transactionDate', 'DESC')
@@ -166,7 +170,9 @@ export class TransactionRepository {
return this.transactionRepository
.createQueryBuilder('tx')
.innerJoin('tx.card', 'card')
.where('card.customerId = :juniorId', { juniorId })
.innerJoin('card.customer', 'customer')
.innerJoin('customer.junior', 'junior')
.where('junior.id = :juniorId', { juniorId })
.andWhere('tx.transactionScope = :scope', { scope: TransactionScope.CARD })
.andWhere('tx.transactionType = :type', { type: TransactionType.INTERNAL })
.getCount();
@@ -176,8 +182,10 @@ export class TransactionRepository {
return this.transactionRepository
.createQueryBuilder('tx')
.innerJoinAndSelect('tx.card', 'card')
.innerJoin('card.customer', 'customer')
.innerJoin('customer.junior', 'junior')
.where('tx.id = :transactionId', { transactionId })
.andWhere('card.customerId = :juniorId', { juniorId })
.andWhere('junior.id = :juniorId', { juniorId })
.getOne();
}
}
+9 -30
View File
@@ -240,37 +240,16 @@ 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
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),
]);
}
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(),
]);
// Only create transaction and emit event after all operations succeed
await this.transactionService.createInternalChildTransaction(card.id, amount);
@@ -6,14 +6,10 @@ export enum NotificationScope {
OTP = 'OTP',
USER_INVITED = 'USER_INVITED',
// Transaction notifications - Top-up (external funds)
// Transaction notifications - Top-up
CHILD_TOP_UP = 'CHILD_TOP_UP',
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
CHILD_SPENDING = 'CHILD_SPENDING',
PARENT_SPENDING_ALERT = 'PARENT_SPENDING_ALERT',
@@ -139,39 +139,14 @@ export class MoneyRequestNotificationListener {
const currency = getCurrency(accountCurrency, null, 'SAR');
const formattedAmount = formatCurrencyAmount(amount, currency);
const locale = this.getUserLocale(parentUser);
this.logger.debug(
`Notifying parent (user ${parentUser.id}): ${childName} requested ${formattedAmount} ${currency} for ${reason}`
`Notifying parent (user ${parentUser.id}): ${childName} requested ${formattedAmount} ${currency} - ${reason}`
);
let title: string;
let message: string;
try {
title = this.i18n.t('app.NOTIFICATION.MONEY_REQUEST_CREATED_TITLE', { lang: locale });
message = this.i18n.t('app.NOTIFICATION.MONEY_REQUEST_CREATED_MESSAGE', {
lang: locale,
args: {
childName: childName,
amount: formattedAmount,
currency: currency,
reason: reason,
},
});
} catch (i18nError: any) {
this.logger.error(
`[MoneyRequestNotificationListener] i18n error for parent ${parentUser.id}: ${i18nError?.message || 'Unknown i18n error'}. Falling back to English.`,
i18nError?.stack
);
title = 'Money Request';
message = `${childName} has requested ${formattedAmount} ${currency} for ${reason}.`;
}
await this.notificationFactory.send({
userId: parentUser.id,
title,
message,
title: 'Money Request',
message: `${childName} requested ${formattedAmount} ${currency}. Reason: ${reason}`,
scope: NotificationScope.MONEY_REQUEST_CREATED,
preferences: this.getUserPreferences(parentUser),
data: {
@@ -1,7 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { I18nService } from 'nestjs-i18n';
import { Roles } from '~/auth/enums';
import { NotificationFactory, NotificationPreferences } from '../services/notification-factory.service';
import { UserService } from '~/user/services/user.service';
import { NOTIFICATION_EVENTS } from '../constants/event-names.constant';
@@ -25,14 +24,6 @@ export class ProfileNotificationListener {
try {
const { user, updatedFields } = event;
// Do not notify when a child updates their profile (no notification to child or parent)
if (user?.roles?.includes(Roles.JUNIOR)) {
this.logger.log(
`Skipping profile updated notification for child user ${user.id} - no notification sent`
);
return;
}
this.logger.log(
`Processing profile updated notification for user ${user.id} - Updated fields: ${updatedFields.join(', ')}`
);
@@ -97,86 +97,36 @@ export class TransactionNotificationListener {
return;
}
// Determine scope: internal transfer (parent to child) vs external top-up
let scope: NotificationScope;
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 scope = isTopUp
? NotificationScope.CHILD_TOP_UP
: NotificationScope.CHILD_SPENDING;
const locale = this.getUserLocale(user);
const amount = transaction.transactionAmount;
const merchant = transaction.merchantName || 'merchant';
// For child notifications, show the appropriate balance based on account structure
// For child top-up notifications, show card.limit (newAmount) instead of account balance
// card.limit represents the total spending limit on the card after the transfer
let balance = 0;
let accountCurrency: string | undefined;
if (isTopUp && isChildSpending) {
// Internal transfer: For shared accounts, show card limit (child's spending power)
// For separate accounts, show child's account balance
if (isTopUp) {
// For top-up: show card limit (newAmount from transfer response)
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;
}
}
// 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 Internal Transfer] Could not fetch balance: ${error?.message}. Using card limit.`
`[Child Top-Up Notification] Could not reload card: ${error?.message}. Using card limit from event.`
);
balance = card.limit || 0;
accountCurrency = card.account?.currency;
}
} else if (isTopUp) {
// External top-up: show child's account balance
try {
const cardWithUpdatedBalance = await this.cardService.getCardById(card.id);
if (cardWithUpdatedBalance?.account?.accountReference) {
const account = await this.accountService.getAccountByReferenceNumber(
cardWithUpdatedBalance.account.accountReference
);
balance = account.balance;
accountCurrency = account.currency;
this.logger.debug(
`[Child Top-Up Notification] Fetched account by reference - 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 Top-Up Notification] Could not fetch account: ${error?.message}. Using card balance.`
);
balance = card.account?.balance || 0;
accountCurrency = card.account?.currency;
}
} else {
// For spending: show account balance
try {
@@ -226,44 +176,36 @@ export class TransactionNotificationListener {
let message: string;
try {
if (isTopUp) {
// Internal transfer or external top-up
const titleKey = isChildSpending
? 'app.NOTIFICATION.CHILD_INTERNAL_TRANSFER_TITLE'
: 'app.NOTIFICATION.CHILD_TOP_UP_TITLE';
const messageKey = isChildSpending
? 'app.NOTIFICATION.CHILD_INTERNAL_TRANSFER_MESSAGE'
: 'app.NOTIFICATION.CHILD_TOP_UP_MESSAGE';
title = this.i18n.t(titleKey, { lang: locale });
message = this.i18n.t(messageKey, {
lang: locale,
args: {
amount: formattedAmount,
currency: currency,
balance: formattedBalance,
},
});
} 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,
},
});
}
title = isTopUp
? this.i18n.t('app.NOTIFICATION.CHILD_TOP_UP_TITLE', { lang: locale })
: this.i18n.t('app.NOTIFICATION.CHILD_SPENDING_TITLE', { lang: locale });
message = isTopUp
? this.i18n.t('app.NOTIFICATION.CHILD_TOP_UP_MESSAGE', {
lang: locale,
args: {
amount: formattedAmount,
currency: currency,
balance: formattedBalance,
},
})
: this.i18n.t('app.NOTIFICATION.CHILD_SPENDING_MESSAGE', {
lang: locale,
args: {
amount: formattedAmount,
currency: currency,
merchant: merchant,
balance: formattedBalance,
},
});
} catch (i18nError: any) {
console.error(`[TransactionNotificationListener] i18n error:`, i18nError);
this.logger.error(`i18n translation failed: ${i18nError?.message}`, i18nError?.stack);
// Fallback to English without i18n
title = isTopUp ? 'Funds Credited' : 'Purchase Successful';
message = isTopUp
? `${formattedAmount} ${currency} has been added to your card. Total balance: ${formattedBalance} ${currency}`
: `You spent ${formattedAmount} ${currency} at ${merchant}`;
title = isTopUp ? 'Card Topped Up' : 'Purchase Successful';
message = isTopUp
? `You received ${formattedAmount} ${currency}. Total balance: ${formattedBalance} ${currency}`
: `You spent ${formattedAmount} ${currency} at ${merchant}. Balance: ${formattedBalance} ${currency}`;
}
this.logger.debug(
@@ -321,80 +263,29 @@ export class TransactionNotificationListener {
const amount = transaction.transactionAmount;
const merchant = transaction.merchantName || 'a merchant';
// Get parent's available balance (balance - reserved_balance) - reload to get fresh balance
// Get parent's account balance (not child's balance) - reload to get fresh balance
let parentAccountBalance = 0;
let parentAccountReservedBalance = 0;
let parentAccountCurrency: string | undefined;
let availableBalance = 0;
try {
if (card.parentId) {
// Get parent's card to access their account reference
const parentCard = await this.cardService.getCardByCustomerId(card.parentId);
if (parentCard?.account?.accountReference) {
// Fetch by reference number to get fresh balance from database
const parentAccount = await this.accountService.getAccountByReferenceNumber(
parentCard.account.accountReference
);
parentAccountBalance = parentAccount.balance;
parentAccountReservedBalance = parentAccount.reservedBalance;
availableBalance = parentAccountBalance - parentAccountReservedBalance;
parentAccountCurrency = parentAccount.currency;
this.logger.debug(
`[Parent Spending] Fetched parent account by reference - balance: ${parentAccountBalance}, reserved: ${parentAccountReservedBalance}, available: ${availableBalance} ${parentAccountCurrency}`
);
} else {
// Fallback: try by customer ID
const parentAccount = await this.accountService.getAccountByCustomerId(card.parentId);
parentAccountBalance = parentAccount.balance;
parentAccountReservedBalance = parentAccount.reservedBalance;
availableBalance = parentAccountBalance - parentAccountReservedBalance;
parentAccountCurrency = parentAccount.currency;
this.logger.debug(
`[Parent Spending] Fetched parent account by customer ID - balance: ${parentAccountBalance}, reserved: ${parentAccountReservedBalance}, available: ${availableBalance} ${parentAccountCurrency}`
);
}
// Always reload parent account to get fresh balance after transaction
const parentAccount = await this.accountService.getAccountByCustomerId(card.parentId);
parentAccountBalance = parentAccount.balance;
parentAccountCurrency = parentAccount.currency;
this.logger.debug(`Fetched parent account balance: ${parentAccountBalance}, currency: ${parentAccountCurrency}`);
} else {
const parentCustomer = customer?.junior?.guardian?.customer;
if (parentCustomer?.id) {
try {
const parentCard = await this.cardService.getCardByCustomerId(parentCustomer.id);
if (parentCard?.account?.accountReference) {
const parentAccount = await this.accountService.getAccountByReferenceNumber(
parentCard.account.accountReference
);
parentAccountBalance = parentAccount.balance;
parentAccountReservedBalance = parentAccount.reservedBalance;
availableBalance = parentAccountBalance - parentAccountReservedBalance;
parentAccountCurrency = parentAccount.currency;
this.logger.debug(
`[Parent Spending] Fetched parent account via customer relation (by reference) - balance: ${parentAccountBalance}, reserved: ${parentAccountReservedBalance}, available: ${availableBalance} ${parentAccountCurrency}`
);
} else {
const parentAccount = await this.accountService.getAccountByCustomerId(parentCustomer.id);
parentAccountBalance = parentAccount.balance;
parentAccountReservedBalance = parentAccount.reservedBalance;
availableBalance = parentAccountBalance - parentAccountReservedBalance;
parentAccountCurrency = parentAccount.currency;
this.logger.debug(
`[Parent Spending] Fetched parent account via customer relation - balance: ${parentAccountBalance}, reserved: ${parentAccountReservedBalance}, available: ${availableBalance} ${parentAccountCurrency}`
);
}
} catch (error: any) {
this.logger.warn(
`[Parent Spending] Could not fetch parent account via customer: ${error?.message}. Using child account balance as fallback.`
);
availableBalance = card.account?.balance || 0;
parentAccountCurrency = card.account?.currency;
}
} else {
availableBalance = card.account?.balance || 0;
parentAccountCurrency = card.account?.currency;
if (parentCustomer?.cards?.[0]?.account) {
// Reload to get fresh balance
const parentAccount = await this.accountService.getAccountByCustomerId(parentCustomer.id);
parentAccountBalance = parentAccount.balance;
parentAccountCurrency = parentAccount.currency;
this.logger.debug(`Fetched parent account balance via customer: ${parentAccountBalance}, currency: ${parentAccountCurrency}`);
}
}
} catch (error: any) {
this.logger.warn(`[Parent Spending] Could not fetch parent account: ${error?.message}, using child account balance as fallback`);
availableBalance = card.account?.balance || 0;
this.logger.warn(`Could not fetch parent account for parent notification: ${error?.message}, using child account balance as fallback`);
parentAccountBalance = card.account?.balance || 0;
parentAccountCurrency = card.account?.currency;
}
@@ -406,12 +297,11 @@ export class TransactionNotificationListener {
);
this.logger.debug(
`[Parent Spending Notification] Parent account currency: ${parentAccountCurrency}, Account currency: ${accountCurrency}, Transaction currency: ${transaction.transactionCurrency}, Final currency: ${currency}, Parent available balance: ${availableBalance}, Amount: ${amount}`
`[Parent Spending Notification] Parent account currency: ${parentAccountCurrency}, Account currency: ${accountCurrency}, Transaction currency: ${transaction.transactionCurrency}, Final currency: ${currency}, Parent balance: ${parentAccountBalance}, Amount: ${amount}`
);
const formattedAmount = formatCurrencyAmount(amount, currency);
// Use available balance for parent spending notification
const formattedBalance = formatCurrencyAmount(availableBalance, currency);
const formattedBalance = formatCurrencyAmount(parentAccountBalance, currency);
this.logger.debug(
`Notifying parent (user ${parentUser.id}): ${childName} spent ${formattedAmount} ${currency} at ${merchant}`
@@ -435,8 +325,8 @@ export class TransactionNotificationListener {
} catch (i18nError: any) {
console.error(`[TransactionNotificationListener] i18n error in parent spending:`, i18nError);
this.logger.error(`i18n translation failed: ${i18nError?.message}`, i18nError?.stack);
title = 'Spending Alert';
message = `${childName} spent ${formattedAmount} ${currency} at ${merchant}. Remaining balance: ${formattedBalance} ${currency}`;
title = 'Child Spending Alert';
message = `${childName} spent ${formattedAmount} ${currency} at ${merchant}. Balance: ${formattedBalance} ${currency}`;
}
await this.notificationFactory.send({
@@ -470,7 +360,7 @@ export class TransactionNotificationListener {
}
/**
* Notify parent when they transfer money to their child's card (internal transfer)
* Notify parent when they top up their child's card
* This is a confirmation notification for the parent
*/
private async notifyParentOfTopUp(transaction: Transaction, card: Card): Promise<void> {
@@ -589,15 +479,15 @@ export class TransactionNotificationListener {
const formattedBalance = formatCurrencyAmount(balance, currency);
this.logger.debug(
`Notifying parent (user ${parentUser.id}): Transferred ${formattedAmount} ${currency} to ${childName}, child balance: ${formattedBalance} ${currency}`
`Notifying parent (user ${parentUser.id}): Transferred ${formattedAmount} ${currency} to ${childName}, parent balance: ${formattedBalance} ${currency}`
);
let title: string;
let message: string;
try {
title = this.i18n.t('app.NOTIFICATION.PARENT_INTERNAL_TRANSFER_TITLE', { lang: locale });
message = this.i18n.t('app.NOTIFICATION.PARENT_INTERNAL_TRANSFER_MESSAGE', {
title = this.i18n.t('app.NOTIFICATION.PARENT_TOP_UP_TITLE', { lang: locale });
message = this.i18n.t('app.NOTIFICATION.PARENT_TOP_UP_MESSAGE', {
lang: locale,
args: {
amount: formattedAmount,
@@ -607,17 +497,17 @@ export class TransactionNotificationListener {
},
});
} catch (i18nError: any) {
console.error(`[TransactionNotificationListener] i18n error in parent internal transfer:`, i18nError);
console.error(`[TransactionNotificationListener] i18n error in parent top-up:`, i18nError);
this.logger.error(`i18n translation failed: ${i18nError?.message}`, i18nError?.stack);
title = 'Internal Transfer Completed';
message = `${formattedAmount} ${currency} has been transferred to ${childName}'s card. ${childName}'s balance is ${formattedBalance} ${currency}`;
title = 'Top-Up Confirmation';
message = `You transferred ${formattedAmount} ${currency} to ${childName}. Balance: ${formattedBalance} ${currency}`;
}
await this.notificationFactory.send({
userId: parentUser.id,
title,
message,
scope: NotificationScope.PARENT_INTERNAL_TRANSFER,
scope: NotificationScope.PARENT_TOP_UP_CONFIRMATION,
preferences: this.getUserPreferences(parentUser),
data: {
transactionId: transaction.id,
+10 -12
View File
@@ -112,19 +112,17 @@
"NOT_FOUND": "لم يتم العثور على البطاقة."
},
"NOTIFICATION": {
"CHILD_TOP_UP_TITLE": "تم إضافة رصيد",
"CHILD_TOP_UP_MESSAGE": "تمت إضافة {amount} {currency} إلى بطاقتك. إجمالي الرصيد: {balance} {currency}",
"CHILD_INTERNAL_TRANSFER_TITLE": "تم إضافة رصيد",
"CHILD_INTERNAL_TRANSFER_MESSAGE": "تمت إضافة {amount} {currency} إلى بطاقتك. إجمالي الرصيد: {balance} {currency}",
"PARENT_INTERNAL_TRANSFER_TITLE": "اكتمل التحويل",
"PARENT_INTERNAL_TRANSFER_MESSAGE": "تم تحويل {amount} {currency} إلى بطاقة {childName}. رصيد {childName}: {balance} {currency}",
"CHILD_SPENDING_TITLE": "عملية شراء ناجحة",
"CHILD_SPENDING_MESSAGE": "قمت بإنفاق {amount} {currency} في {merchant}",
"PARENT_SPENDING_TITLE": "تنبيه صرف",
"PARENT_SPENDING_MESSAGE": "قام {childName} بإنفاق {amount} {currency} في {merchant}. الرصيد المتبقي: {balance} {currency}",
"CHILD_TOP_UP_TITLE": "تم شحن البطاقة",
"CHILD_TOP_UP_MESSAGE": "لقد استلمت {amount} {currency}. الرصيد الإجمالي: {balance} {currency}",
"CHILD_SPENDING_TITLE": "تمت العملية بنجاح",
"CHILD_SPENDING_MESSAGE": "لقد أنفقت {amount} {currency} في {merchant}. الرصيد: {balance} {currency}",
"PARENT_TOP_UP_TITLE": "تأكيد الشحن",
"PARENT_TOP_UP_MESSAGE": "لقد قمت بتحويل {amount} {currency} إلى {childName}. الرصيد: {balance} {currency}",
"PARENT_SPENDING_TITLE": "تنبيه إنفاق الطفل",
"PARENT_SPENDING_MESSAGE": "أنفق {childName} {amount} {currency} في {merchant}. الرصيد: {balance} {currency}",
"YOUR_CHILD": "طفلك",
"MONEY_REQUEST_CREATED_TITLE": "طلب مبلغ مالي",
"MONEY_REQUEST_CREATED_MESSAGE": "طلب {childName} مبلغ {amount} {currency} لـ {reason}",
"MONEY_REQUEST_CREATED_TITLE": "طلب مال",
"MONEY_REQUEST_CREATED_MESSAGE": "طلب {childName} مبلغ {amount} {currency}. السبب: {reason}",
"MONEY_REQUEST_APPROVED_TITLE": "تمت الموافقة على طلب المال",
"MONEY_REQUEST_APPROVED_MESSAGE": "تمت الموافقة على طلبك بمبلغ {amount} {currency}. تمت إضافة المال إلى حسابك.",
"MONEY_REQUEST_DECLINED_TITLE": "تم رفض طلب المال",
+8 -10
View File
@@ -111,19 +111,17 @@
"NOT_FOUND": "The card was not found."
},
"NOTIFICATION": {
"CHILD_TOP_UP_TITLE": "Funds Credited",
"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_TOP_UP_TITLE": "Card Topped Up",
"CHILD_TOP_UP_MESSAGE": "You received {amount} {currency}. Total balance: {balance} {currency}",
"CHILD_SPENDING_TITLE": "Purchase Successful",
"CHILD_SPENDING_MESSAGE": "You spent {amount} {currency} at {merchant}",
"PARENT_SPENDING_TITLE": "Spending Alert",
"PARENT_SPENDING_MESSAGE": "{childName} spent {amount} {currency} at {merchant}. Remaining balance: {balance} {currency}",
"CHILD_SPENDING_MESSAGE": "You spent {amount} {currency} at {merchant}. Balance: {balance} {currency}",
"PARENT_TOP_UP_TITLE": "Top-Up Confirmation",
"PARENT_TOP_UP_MESSAGE": "You transferred {amount} {currency} to {childName}. Balance: {balance} {currency}",
"PARENT_SPENDING_TITLE": "Child Spending Alert",
"PARENT_SPENDING_MESSAGE": "{childName} spent {amount} {currency} at {merchant}. Balance: {balance} {currency}",
"YOUR_CHILD": "Your child",
"MONEY_REQUEST_CREATED_TITLE": "Money Request",
"MONEY_REQUEST_CREATED_MESSAGE": "{childName} has requested {amount} {currency} for {reason}.",
"MONEY_REQUEST_CREATED_MESSAGE": "{childName} requested {amount} {currency}. Reason: {reason}",
"MONEY_REQUEST_APPROVED_TITLE": "Money Request Approved",
"MONEY_REQUEST_APPROVED_MESSAGE": "Your request for {amount} {currency} has been approved. The money has been added to your account.",
"MONEY_REQUEST_DECLINED_TITLE": "Money Request Declined",