mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2026-03-10 17:11:44 +00:00
Compare commits
3 Commits
7c9e0f0b51
...
f5c3b03264
| Author | SHA1 | Date | |
|---|---|---|---|
| f5c3b03264 | |||
| a09b84e475 | |||
| 4305c4b75f |
@ -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
|
||||||
await Promise.all([
|
if (isSharedAccount) {
|
||||||
this.neoleapService.updateCardControl(card.cardReference, finalAmountNumber),
|
// Shared account: Only update card limit and reserved balance
|
||||||
this.updateCardLimit(card.id, finalAmountNumber),
|
// Money is already in the shared account, just allocate it to the child
|
||||||
this.accountService.increaseReservedBalance(fundingAccount, amount),
|
this.logger.debug(`Shared account detected - only updating card limit and reserved balance`);
|
||||||
// Increase child account balance
|
await Promise.all([
|
||||||
this.accountService.creditAccountBalance(card.account.accountReference, amount),
|
this.neoleapService.updateCardControl(card.cardReference, finalAmountNumber),
|
||||||
// Decrease parent account balance (only if parent is funding)
|
this.updateCardLimit(card.id, finalAmountNumber),
|
||||||
card.parentId ? this.accountService.decreaseAccountBalance(fundingAccount.accountReference, amount) : Promise.resolve(),
|
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
|
// Only create transaction and emit event after all operations succeed
|
||||||
await this.transactionService.createInternalChildTransaction(card.id, amount);
|
await this.transactionService.createInternalChildTransaction(card.id, amount);
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -139,14 +139,39 @@ export class MoneyRequestNotificationListener {
|
|||||||
const currency = getCurrency(accountCurrency, null, 'SAR');
|
const currency = getCurrency(accountCurrency, null, 'SAR');
|
||||||
const formattedAmount = formatCurrencyAmount(amount, currency);
|
const formattedAmount = formatCurrencyAmount(amount, currency);
|
||||||
|
|
||||||
|
const locale = this.getUserLocale(parentUser);
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Notifying parent (user ${parentUser.id}): ${childName} requested ${formattedAmount} ${currency} - ${reason}`
|
`Notifying parent (user ${parentUser.id}): ${childName} requested ${formattedAmount} ${currency} for ${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({
|
await this.notificationFactory.send({
|
||||||
userId: parentUser.id,
|
userId: parentUser.id,
|
||||||
title: 'Money Request',
|
title,
|
||||||
message: `${childName} requested ${formattedAmount} ${currency}. Reason: ${reason}`,
|
message,
|
||||||
scope: NotificationScope.MONEY_REQUEST_CREATED,
|
scope: NotificationScope.MONEY_REQUEST_CREATED,
|
||||||
preferences: this.getUserPreferences(parentUser),
|
preferences: this.getUserPreferences(parentUser),
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@ -97,36 +97,86 @@ 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 appropriate balance based on account structure
|
||||||
// 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 && isChildSpending) {
|
||||||
// For top-up: show card limit (newAmount from transfer response)
|
// Internal transfer: For shared accounts, show card limit (child's spending power)
|
||||||
|
// For separate accounts, show child's account balance
|
||||||
try {
|
try {
|
||||||
// Reload card to get updated limit
|
// Reload card to get updated data
|
||||||
const cardWithUpdatedLimit = await this.cardService.getCardById(card.id);
|
const cardWithUpdatedBalance = await this.cardService.getCardById(card.id);
|
||||||
balance = cardWithUpdatedLimit.limit || card.limit || 0;
|
|
||||||
accountCurrency = cardWithUpdatedLimit.account?.currency || card.account?.currency;
|
// Check if child has parent (shared account scenario)
|
||||||
this.logger.debug(
|
if (cardWithUpdatedBalance.parentId) {
|
||||||
`[Child Top-Up Notification] Using card limit (newAmount) - limit: ${balance} ${accountCurrency}`
|
// 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) {
|
} 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 Internal Transfer] Could not fetch balance: ${error?.message}. Using card limit.`
|
||||||
);
|
);
|
||||||
balance = card.limit || 0;
|
balance = card.limit || 0;
|
||||||
accountCurrency = card.account?.currency;
|
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 {
|
} else {
|
||||||
// For spending: show account balance
|
// For spending: show account balance
|
||||||
try {
|
try {
|
||||||
@ -176,36 +226,44 @@ 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 {
|
||||||
balance: formattedBalance,
|
// 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);
|
||||||
// Fallback to English without i18n
|
// Fallback to English without i18n
|
||||||
title = isTopUp ? 'Card Topped Up' : 'Purchase Successful';
|
title = isTopUp ? 'Funds Credited' : 'Purchase Successful';
|
||||||
message = isTopUp
|
message = isTopUp
|
||||||
? `You received ${formattedAmount} ${currency}. Total balance: ${formattedBalance} ${currency}`
|
? `${formattedAmount} ${currency} has been added to your card. Total balance: ${formattedBalance} ${currency}`
|
||||||
: `You spent ${formattedAmount} ${currency} at ${merchant}. Balance: ${formattedBalance} ${currency}`;
|
: `You spent ${formattedAmount} ${currency} at ${merchant}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
@ -263,29 +321,80 @@ export class TransactionNotificationListener {
|
|||||||
const amount = transaction.transactionAmount;
|
const amount = transaction.transactionAmount;
|
||||||
const merchant = transaction.merchantName || 'a merchant';
|
const merchant = transaction.merchantName || 'a merchant';
|
||||||
|
|
||||||
// Get parent's account balance (not child's balance) - reload to get fresh balance
|
// Get parent's available balance (balance - reserved_balance) - reload to get fresh balance
|
||||||
let parentAccountBalance = 0;
|
let parentAccountBalance = 0;
|
||||||
|
let parentAccountReservedBalance = 0;
|
||||||
let parentAccountCurrency: string | undefined;
|
let parentAccountCurrency: string | undefined;
|
||||||
|
let availableBalance = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (card.parentId) {
|
if (card.parentId) {
|
||||||
// Always reload parent account to get fresh balance after transaction
|
// Get parent's card to access their account reference
|
||||||
const parentAccount = await this.accountService.getAccountByCustomerId(card.parentId);
|
const parentCard = await this.cardService.getCardByCustomerId(card.parentId);
|
||||||
parentAccountBalance = parentAccount.balance;
|
if (parentCard?.account?.accountReference) {
|
||||||
parentAccountCurrency = parentAccount.currency;
|
// Fetch by reference number to get fresh balance from database
|
||||||
this.logger.debug(`Fetched parent account balance: ${parentAccountBalance}, currency: ${parentAccountCurrency}`);
|
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}`
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const parentCustomer = customer?.junior?.guardian?.customer;
|
const parentCustomer = customer?.junior?.guardian?.customer;
|
||||||
if (parentCustomer?.cards?.[0]?.account) {
|
if (parentCustomer?.id) {
|
||||||
// Reload to get fresh balance
|
try {
|
||||||
const parentAccount = await this.accountService.getAccountByCustomerId(parentCustomer.id);
|
const parentCard = await this.cardService.getCardByCustomerId(parentCustomer.id);
|
||||||
parentAccountBalance = parentAccount.balance;
|
if (parentCard?.account?.accountReference) {
|
||||||
parentAccountCurrency = parentAccount.currency;
|
const parentAccount = await this.accountService.getAccountByReferenceNumber(
|
||||||
this.logger.debug(`Fetched parent account balance via customer: ${parentAccountBalance}, currency: ${parentAccountCurrency}`);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.logger.warn(`Could not fetch parent account for parent notification: ${error?.message}, using child account balance as fallback`);
|
this.logger.warn(`[Parent Spending] Could not fetch parent account: ${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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,11 +406,12 @@ export class TransactionNotificationListener {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`[Parent Spending Notification] Parent account currency: ${parentAccountCurrency}, Account currency: ${accountCurrency}, Transaction currency: ${transaction.transactionCurrency}, Final currency: ${currency}, Parent balance: ${parentAccountBalance}, Amount: ${amount}`
|
`[Parent Spending Notification] Parent account currency: ${parentAccountCurrency}, Account currency: ${accountCurrency}, Transaction currency: ${transaction.transactionCurrency}, Final currency: ${currency}, Parent available balance: ${availableBalance}, Amount: ${amount}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const formattedAmount = formatCurrencyAmount(amount, currency);
|
const formattedAmount = formatCurrencyAmount(amount, currency);
|
||||||
const formattedBalance = formatCurrencyAmount(parentAccountBalance, currency);
|
// Use available balance for parent spending notification
|
||||||
|
const formattedBalance = formatCurrencyAmount(availableBalance, currency);
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Notifying parent (user ${parentUser.id}): ${childName} spent ${formattedAmount} ${currency} at ${merchant}`
|
`Notifying parent (user ${parentUser.id}): ${childName} spent ${formattedAmount} ${currency} at ${merchant}`
|
||||||
@ -325,8 +435,8 @@ export class TransactionNotificationListener {
|
|||||||
} catch (i18nError: any) {
|
} catch (i18nError: any) {
|
||||||
console.error(`[TransactionNotificationListener] i18n error in parent spending:`, i18nError);
|
console.error(`[TransactionNotificationListener] i18n error in parent spending:`, i18nError);
|
||||||
this.logger.error(`i18n translation failed: ${i18nError?.message}`, i18nError?.stack);
|
this.logger.error(`i18n translation failed: ${i18nError?.message}`, i18nError?.stack);
|
||||||
title = 'Child Spending Alert';
|
title = 'Spending Alert';
|
||||||
message = `${childName} spent ${formattedAmount} ${currency} at ${merchant}. Balance: ${formattedBalance} ${currency}`;
|
message = `${childName} spent ${formattedAmount} ${currency} at ${merchant}. Remaining balance: ${formattedBalance} ${currency}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.notificationFactory.send({
|
await this.notificationFactory.send({
|
||||||
@ -360,7 +470,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> {
|
||||||
@ -479,15 +589,15 @@ export class TransactionNotificationListener {
|
|||||||
const formattedBalance = formatCurrencyAmount(balance, currency);
|
const formattedBalance = formatCurrencyAmount(balance, currency);
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Notifying parent (user ${parentUser.id}): Transferred ${formattedAmount} ${currency} to ${childName}, parent balance: ${formattedBalance} ${currency}`
|
`Notifying parent (user ${parentUser.id}): Transferred ${formattedAmount} ${currency} to ${childName}, child balance: ${formattedBalance} ${currency}`
|
||||||
);
|
);
|
||||||
|
|
||||||
let title: string;
|
let title: string;
|
||||||
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,
|
||||||
@ -497,17 +607,17 @@ 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 = 'Top-Up Confirmation';
|
title = 'Internal Transfer Completed';
|
||||||
message = `You transferred ${formattedAmount} ${currency} to ${childName}. Balance: ${formattedBalance} ${currency}`;
|
message = `${formattedAmount} ${currency} has been transferred to ${childName}'s card. ${childName}'s balance is ${formattedBalance} ${currency}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.notificationFactory.send({
|
await this.notificationFactory.send({
|
||||||
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}. الرصيد: {balance} {currency}",
|
"CHILD_INTERNAL_TRANSFER_MESSAGE": "تمت إضافة {amount} {currency} إلى بطاقتك. إجمالي الرصيد: {balance} {currency}",
|
||||||
"PARENT_TOP_UP_TITLE": "تأكيد الشحن",
|
"PARENT_INTERNAL_TRANSFER_TITLE": "اكتمل التحويل",
|
||||||
"PARENT_TOP_UP_MESSAGE": "لقد قمت بتحويل {amount} {currency} إلى {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": "تم رفض طلب المال",
|
||||||
|
|||||||
@ -111,17 +111,19 @@
|
|||||||
"NOT_FOUND": "The card was not found."
|
"NOT_FOUND": "The card was not found."
|
||||||
},
|
},
|
||||||
"NOTIFICATION": {
|
"NOTIFICATION": {
|
||||||
"CHILD_TOP_UP_TITLE": "Card Topped Up",
|
"CHILD_TOP_UP_TITLE": "Funds Credited",
|
||||||
"CHILD_TOP_UP_MESSAGE": "You received {amount} {currency}. 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}. Balance: {balance} {currency}",
|
"CHILD_SPENDING_MESSAGE": "You spent {amount} {currency} at {merchant}",
|
||||||
"PARENT_TOP_UP_TITLE": "Top-Up Confirmation",
|
"PARENT_SPENDING_TITLE": "Spending Alert",
|
||||||
"PARENT_TOP_UP_MESSAGE": "You transferred {amount} {currency} to {childName}. Balance: {balance} {currency}",
|
"PARENT_SPENDING_MESSAGE": "{childName} spent {amount} {currency} at {merchant}. Remaining 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",
|
"YOUR_CHILD": "Your child",
|
||||||
"MONEY_REQUEST_CREATED_TITLE": "Money Request",
|
"MONEY_REQUEST_CREATED_TITLE": "Money Request",
|
||||||
"MONEY_REQUEST_CREATED_MESSAGE": "{childName} requested {amount} {currency}. Reason: {reason}",
|
"MONEY_REQUEST_CREATED_MESSAGE": "{childName} has requested {amount} {currency} for {reason}.",
|
||||||
"MONEY_REQUEST_APPROVED_TITLE": "Money Request Approved",
|
"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_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",
|
"MONEY_REQUEST_DECLINED_TITLE": "Money Request Declined",
|
||||||
|
|||||||
Reference in New Issue
Block a user