Signup and admin-created accounts notify platform managers, and an ended plan sends one SMS instead of repeating for old expirations. Co-authored-by: Cursor <cursoragent@cursor.com>
152 lines
4.7 KiB
TypeScript
152 lines
4.7 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
|
import { isPermanentBillingPlan } from '@mento/shared';
|
|
import { SmsGatewayService } from '../sms-billing/sms-gateway.service';
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
import {
|
|
newAccountSms,
|
|
subscriptionEndedSms,
|
|
type NewAccountSource,
|
|
} from './platform-admin-alert';
|
|
|
|
@Injectable()
|
|
export class PlatformAdminAlertService {
|
|
private readonly logger = new Logger(PlatformAdminAlertService.name);
|
|
|
|
constructor(
|
|
private readonly prisma: PrismaService,
|
|
private readonly sms: SmsGatewayService,
|
|
) {}
|
|
|
|
/** ساخت حساب جدید؛ خطای پیامک جلوی ثبتنام یا ساخت محیط را نمیگیرد. */
|
|
async notifyNewAccount(input: {
|
|
source: NewAccountSource;
|
|
organizationName: string;
|
|
ownerName: string;
|
|
mobile: string | null;
|
|
}): Promise<void> {
|
|
try {
|
|
const delivered = await this.sendToAdmins(newAccountSms(input));
|
|
if (!delivered) {
|
|
this.logger.warn(`new account SMS was not delivered (${input.source})`);
|
|
}
|
|
} catch (error) {
|
|
this.logger.warn(
|
|
`new account SMS failed: ${error instanceof Error ? error.message : 'unknown'}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* یکبار هنگام تمام شدن اشتراک. اگر ارسال نشود پرچم خالی میماند تا بعداً دوباره تلاش شود.
|
|
*/
|
|
async notifySubscriptionEnded(billingAccountId: string): Promise<void> {
|
|
const row = await this.prisma.billingAccount.findUnique({
|
|
where: { id: billingAccountId },
|
|
select: {
|
|
id: true,
|
|
planCode: true,
|
|
status: true,
|
|
expiresAt: true,
|
|
expiryNotifiedAt: true,
|
|
owner: {
|
|
select: {
|
|
fullName: true,
|
|
mobile: true,
|
|
memberships: {
|
|
where: { role: 'owner', isActive: true },
|
|
select: { organization: { select: { name: true } } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
if (!row || row.expiryNotifiedAt) return;
|
|
if (row.status === 'suspended') return;
|
|
if (isPermanentBillingPlan(row.planCode)) {
|
|
await this.prisma.billingAccount.update({
|
|
where: { id: row.id },
|
|
data: { expiryNotifiedAt: new Date() },
|
|
});
|
|
return;
|
|
}
|
|
if (row.expiresAt.getTime() > Date.now() && row.status !== 'expired') return;
|
|
|
|
const claimed = await this.prisma.billingAccount.updateMany({
|
|
where: { id: row.id, expiryNotifiedAt: null },
|
|
data: { status: 'expired', expiryNotifiedAt: new Date() },
|
|
});
|
|
if (claimed.count !== 1) return;
|
|
|
|
try {
|
|
const delivered = await this.sendToAdmins(
|
|
subscriptionEndedSms({
|
|
organizationNames: row.owner.memberships.map((item) => item.organization.name),
|
|
ownerName: row.owner.fullName,
|
|
mobile: row.owner.mobile,
|
|
planCode: row.planCode,
|
|
expiresAt: row.expiresAt,
|
|
}),
|
|
);
|
|
if (!delivered) {
|
|
await this.prisma.billingAccount.update({
|
|
where: { id: row.id },
|
|
data: { expiryNotifiedAt: null },
|
|
});
|
|
}
|
|
} catch (error) {
|
|
this.logger.warn(
|
|
`subscription SMS failed ${row.id}: ${error instanceof Error ? error.message : 'unknown'}`,
|
|
);
|
|
await this.prisma.billingAccount
|
|
.update({
|
|
where: { id: row.id },
|
|
data: { expiryNotifiedAt: null },
|
|
})
|
|
.catch(() => undefined);
|
|
}
|
|
}
|
|
|
|
/** true یعنی حداقل یک پیامک رفت، یا مدیری با موبایل فعال وجود نداشت. */
|
|
private async sendToAdmins(text: string): Promise<boolean> {
|
|
const admins = await this.prisma.platformAdmin.findMany({
|
|
where: { isActive: true, mobile: { not: null } },
|
|
select: { mobile: true },
|
|
});
|
|
const mobiles = [
|
|
...new Set(
|
|
admins
|
|
.map((admin) => admin.mobile?.trim() ?? '')
|
|
.filter((mobile) => mobile.length > 0),
|
|
),
|
|
];
|
|
if (!mobiles.length) {
|
|
this.logger.warn('platform admin SMS skipped: no active admin mobile');
|
|
return true;
|
|
}
|
|
|
|
let delivered = false;
|
|
for (const mobile of mobiles) {
|
|
try {
|
|
const result = await this.sms.send({
|
|
phone: mobile,
|
|
text,
|
|
source: 'platform-admin',
|
|
});
|
|
if (result.ok) delivered = true;
|
|
else {
|
|
this.logger.warn(
|
|
`platform admin SMS failed to=…${mobile.slice(-4)} reason=${result.reason ?? 'unknown'}`,
|
|
);
|
|
}
|
|
} catch (error) {
|
|
this.logger.warn(
|
|
`platform admin SMS failed to=…${mobile.slice(-4)}: ${
|
|
error instanceof Error ? error.message : 'unknown'
|
|
}`,
|
|
);
|
|
}
|
|
}
|
|
return delivered;
|
|
}
|
|
}
|