From e15c25dae166337b08dd4e24e82c976c5ce2c3e7 Mon Sep 17 00:00:00 2001 From: parsa aghaei Date: Sat, 5 Sep 2026 14:06:23 +0330 Subject: [PATCH] feat(sms): add WALLET_CHARGED sms trigger for online topup, manual admin credit, refund, and cashback --- backend/src/admin/admin.service.ts | 53 +++++++++++++++- .../common/services/sms-events.constants.ts | 23 +++++++ backend/src/payment/payment.service.ts | 63 +++++++++++++++++++ backend/src/users/users.service.ts | 31 ++++++++- .../admin-panel/src/pages/SmsSettingsPage.tsx | 1 + 5 files changed, 168 insertions(+), 3 deletions(-) diff --git a/backend/src/admin/admin.service.ts b/backend/src/admin/admin.service.ts index 6c48a97..aa7737f 100644 --- a/backend/src/admin/admin.service.ts +++ b/backend/src/admin/admin.service.ts @@ -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; } diff --git a/backend/src/common/services/sms-events.constants.ts b/backend/src/common/services/sms-events.constants.ts index 9e0a473..fde587a 100644 --- a/backend/src/common/services/sms-events.constants.ts +++ b/backend/src/common/services/sms-events.constants.ts @@ -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: 'ارسال پیامک افزایش موجودی کیف پول (آنلاین، مدیریت یا کش‌بک)', + }, ]; diff --git a/backend/src/payment/payment.service.ts b/backend/src/payment/payment.service.ts index 5c3643d..365d434 100644 --- a/backend/src/payment/payment.service.ts +++ b/backend/src/payment/payment.service.ts @@ -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: 'تراکنش با موفقیت به صورت دستی تایید و اعمال شد', diff --git a/backend/src/users/users.service.ts b/backend/src/users/users.service.ts index be77356..1e2d06d 100644 --- a/backend/src/users/users.service.ts +++ b/backend/src/users/users.service.ts @@ -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; } diff --git a/frontend/admin-panel/src/pages/SmsSettingsPage.tsx b/frontend/admin-panel/src/pages/SmsSettingsPage.tsx index f6a7ee4..be30a1b 100644 --- a/frontend/admin-panel/src/pages/SmsSettingsPage.tsx +++ b/frontend/admin-panel/src/pages/SmsSettingsPage.tsx @@ -2153,6 +2153,7 @@ export default function SmsSettingsPage() { +