feat(sms): add WALLET_CHARGED sms trigger for online topup, manual admin credit, refund, and cashback
Some checks failed
Deploy Canina / deploy (push) Successful in 43s
E2E Playwright Tests / Run Full E2E & Security Suites (push) Failing after 0s

This commit is contained in:
parsa aghaei 2026-09-05 14:06:23 +03:30
parent 7093978e27
commit e15c25dae1
5 changed files with 168 additions and 3 deletions

View File

@ -984,6 +984,32 @@ export class AdminService {
});
});
// Send SMS notification for wallet refund
if (this.smsService && order.user?.mobile) {
try {
const user = await this.prisma.user.findUnique({
where: { id: order.userId },
});
const customerFullName = (
order.user.firstName
? `${order.user.firstName} ${order.user.lastName || ''}`
: 'کاربر گرامی'
).trim();
const refundData = {
customerName: customerFullName,
customerPhone: order.user.mobile,
amount: Number(refundAmount || 0).toLocaleString('fa-IR'),
balance: Number(user?.walletBalance || 0).toLocaleString('fa-IR'),
reason: reason || `استرداد وجه سفارش #${order.trackingNumber || order.id.slice(0, 8)}`,
date: new Date().toLocaleDateString('fa-IR'),
};
this.smsService.triggerEvent('WALLET_CHARGED', refundData).catch(() => {});
} catch (err) {
// Silently catch SMS errors
}
}
return {
success: true,
message: `مبلغ ${refundAmount.toLocaleString('fa-IR')} تومان با موفقیت به کیف پول کاربر بازگردانده شد.`,
@ -1243,7 +1269,7 @@ export class AdminService {
});
const isIncrement = type === 'deposit' || type === 'refund';
await this.prisma.user.update({
const updatedUser = await this.prisma.user.update({
where: { id: userId },
data: {
walletBalance: isIncrement
@ -1252,6 +1278,31 @@ export class AdminService {
},
});
// Send SMS notification if wallet is topped up or refunded
if (isIncrement && this.smsService && updatedUser.mobile) {
try {
const customerFullName = (
updatedUser.firstName
? `${updatedUser.firstName} ${updatedUser.lastName || ''}`
: 'کاربر گرامی'
).trim();
const topupData = {
customerName: customerFullName,
customerPhone: updatedUser.mobile,
amount: Number(amount || 0).toLocaleString('fa-IR'),
balance: Number(updatedUser.walletBalance || 0).toLocaleString('fa-IR'),
reason: description || (type === 'refund' ? 'استرداد وجه به کیف پول' : 'شارژ کیف پول توسط مدیریت'),
date: new Date().toLocaleDateString('fa-IR'),
};
this.smsService
.triggerEvent('WALLET_CHARGED', topupData)
.catch(() => {});
} catch (err) {
// Silently catch SMS errors
}
}
return transaction;
}

View File

@ -104,6 +104,19 @@ export const SMS_EVENT_DEFINITIONS: SmsEventDefinition[] = [
{ key: 'date', label: 'تاریخ ارسال', sample: '۱۴۰۳/۰۶/۱۵' },
],
},
{
event: 'WALLET_CHARGED',
label: 'شارژ و افزایش موجودی کیف پول',
description: 'هنگام افزایش موجودی کیف پول کاربر (درگاه آنلاین، پنل مدیریت، کش‌بک یا استرداد)',
variables: [
{ key: 'customerName', label: 'نام مشتری', sample: 'علی محمدی' },
{ key: 'amount', label: 'مبلغ شارژ (تومان)', sample: '۵۰,۰۰۰' },
{ key: 'balance', label: 'موجودی جدید کیف پول (تومان)', sample: '۱۵۰,۰۰۰' },
{ key: 'reason', label: 'علت / نوع شارژ', sample: 'شارژ آنلاین / پاداش خرید (Cashback)' },
{ key: 'customerPhone', label: 'شماره همراه مشتری', sample: '09123456789' },
{ key: 'date', label: 'تاریخ شارژ', sample: '۱۴۰۳/۰۶/۱۵' },
],
},
];
export const DEFAULT_SMS_RULES: SmsRule[] = [
@ -147,4 +160,14 @@ export const DEFAULT_SMS_RULES: SmsRule[] = [
variables: ['applicantName'],
description: 'پیامک ثبت درخواست متقاضیان عمده‌فروشی',
},
{
id: 'default-wallet-charged-customer',
title: 'اطلاع‌رسانی شارژ کیف پول به کاربر',
event: 'WALLET_CHARGED',
enabled: true,
recipientType: 'customer',
patternId: 0,
variables: ['customerName', 'amount', 'balance'],
description: 'ارسال پیامک افزایش موجودی کیف پول (آنلاین، مدیریت یا کش‌بک)',
},
];

View File

@ -608,6 +608,38 @@ export class PaymentService {
});
}
// Send SMS notification if wallet top-up
if (transaction.type === 'WALLET_TOPUP' && transaction.userId) {
try {
const user = await this.prisma.user.findUnique({
where: { id: transaction.userId },
});
if (user?.mobile) {
const customerFullName = (
user.firstName
? `${user.firstName} ${user.lastName || ''}`
: 'کاربر گرامی'
).trim();
const topupData = {
customerName: customerFullName,
customerPhone: user.mobile,
amount: Number(transaction.amount || 0).toLocaleString('fa-IR'),
balance: Number(user.walletBalance || 0).toLocaleString('fa-IR'),
reason: 'شارژ آنلاین از طریق درگاه بانکی',
date: new Date().toLocaleDateString('fa-IR'),
};
this.smsService
.triggerEvent('WALLET_CHARGED', topupData)
.catch((err) => {
this.logger.warn(`Could not trigger SMS event WALLET_CHARGED: ${err}`);
});
}
} catch (err) {
this.logger.warn(`Error dispatching WALLET_CHARGED SMS: ${err}`);
}
}
return {
success: true,
status: 'VERIFIED',
@ -1075,6 +1107,37 @@ export class PaymentService {
}
});
if (transaction.type === 'WALLET_TOPUP' && transaction.userId) {
try {
const user = await this.prisma.user.findUnique({
where: { id: transaction.userId },
});
if (user?.mobile) {
const customerFullName = (
user.firstName
? `${user.firstName} ${user.lastName || ''}`
: 'کاربر گرامی'
).trim();
const topupData = {
customerName: customerFullName,
customerPhone: user.mobile,
amount: Number(transaction.amount || 0).toLocaleString('fa-IR'),
balance: Number(user.walletBalance || 0).toLocaleString('fa-IR'),
reason: adminNote || 'افزایش موجودی (کارت به کارت / فیش بانکی)',
date: new Date().toLocaleDateString('fa-IR'),
};
this.smsService
.triggerEvent('WALLET_CHARGED', topupData)
.catch((err) => {
this.logger.warn(`Could not trigger SMS event WALLET_CHARGED: ${err}`);
});
}
} catch (err) {
this.logger.warn(`Error dispatching WALLET_CHARGED SMS: ${err}`);
}
}
return {
success: true,
message: 'تراکنش با موفقیت به صورت دستی تایید و اعمال شد',

View File

@ -2,9 +2,11 @@ import {
Injectable,
BadRequestException,
NotFoundException,
Optional,
} from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { SmsService } from '../common/services/sms.service';
import * as bcrypt from 'bcryptjs';
import { normalizeMobile } from '../common/utils/phone.utils';
import { UpdateProfileDto } from './dto/update-profile.dto';
@ -22,7 +24,10 @@ export interface UserAddressInput {
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
constructor(
private prisma: PrismaService,
@Optional() private smsService?: SmsService,
) {}
async findById(id: string) {
if (id === '12345678-1234-1234-1234-123456789012') {
@ -277,7 +282,7 @@ export class UsersService {
throw new Error('مبلغ شارژ باید بزرگتر از صفر باشد');
}
const [transaction] = await this.prisma.$transaction([
const [transaction, updatedUser] = await this.prisma.$transaction([
this.prisma.walletTransaction.create({
data: {
userId,
@ -293,6 +298,28 @@ export class UsersService {
}),
]);
if (this.smsService && updatedUser.mobile) {
try {
const customerFullName = (
updatedUser.firstName
? `${updatedUser.firstName} ${updatedUser.lastName || ''}`
: 'کاربر گرامی'
).trim();
const topupData = {
customerName: customerFullName,
customerPhone: updatedUser.mobile,
amount: Number(amount || 0).toLocaleString('fa-IR'),
balance: Number(updatedUser.walletBalance || 0).toLocaleString('fa-IR'),
reason: 'افزایش موجودی کیف پول',
date: new Date().toLocaleDateString('fa-IR'),
};
this.smsService.triggerEvent('WALLET_CHARGED', topupData).catch(() => {});
} catch (err) {
// Silently catch SMS error
}
}
return transaction;
}

View File

@ -2153,6 +2153,7 @@ export default function SmsSettingsPage() {
<option value="ORDER_PAID">پرداخت موفق سفارش (Order Paid)</option>
<option value="ORDER_CREATED">ثبت سفارش کارت به کارت / در انتظار</option>
<option value="ORDER_SHIPPED">ارسال مرسوله و صدور بارکد پستی</option>
<option value="WALLET_CHARGED">شارژ و افزایش موجودی کیف پول</option>
<option value="B2B_SUBMITTED">ثبت درخواست همکاری B2B</option>
<option value="CONTACT_SUBMITTED">ثبت پیام در فرم تماس با ما</option>
</>