Files
makemd/server/src/services/MarketingService.ts

97 lines
3.2 KiB
TypeScript
Raw Normal View History

import db from '../config/database';
import { logger } from '../utils/logger';
import { AuditService } from './AuditService';
export interface AbandonedCartInfo {
userId: string;
cartId: string;
productId: string;
abandonedAt: Date;
recoveryEmailSent?: boolean;
}
/**
* MarketingService: 自动营销挽留系统 (BIZ_EXT_12)
*
*/
export class MarketingService {
private static TABLE_CART = 'cf_cart';
/**
* 24
*/
static async processAbandonedCarts(): Promise<void> {
try {
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
const dayBeforeYesterday = new Date(Date.now() - 48 * 60 * 60 * 1000);
// 获取 24-48 小时内未结账且未发送挽留邮件的记录
const abandonedCarts = await db(this.TABLE_CART)
.where({ status: 'ABANDONED', recoverySent: false })
.whereBetween('updated_at', [dayBeforeYesterday, yesterday])
.select('*');
for (const cart of abandonedCarts) {
await this.triggerRecoveryStrategy(cart);
}
} catch (error: any) {
logger.error(`[AbandonedRecovery] Failed to process carts: ${error.message}`);
}
}
/**
*
*/
private static async triggerRecoveryStrategy(cart: any): Promise<void> {
try {
// 1. 获取用户历史价值 (LTV) 评分
const customerScore = await this.getCustomerScore(cart.userId);
let discountCode: string;
let emailContent: string;
// 2. 阶梯策略:根据用户价值决定优惠力度
if (customerScore > 80) {
discountCode = 'VIP_RECOVER_20'; // 8折
emailContent = `[VIP Exclusive] Still thinking about it? Here's 20% off just for you!`;
} else if (customerScore > 50) {
discountCode = 'SAVE_10_NOW'; // 9折
emailContent = `We noticed you left something behind. Enjoy 10% off if you order now!`;
} else {
discountCode = 'COME_BACK_5'; // 95折
emailContent = `Complete your purchase and get 5% off!`;
}
// 3. 记录审计并标记已发送 (Mock 邮件发送逻辑)
logger.info(`[AbandonedRecovery] Sending ${discountCode} to user ${cart.userId} for cart ${cart.id}`);
await db(this.TABLE_CART).where({ id: cart.id }).update({
recoverySent: true,
recoveryStrategy: discountCode,
recovery_at: new Date()
});
// 发送审计日志 (BIZ_DEV_04)
await AuditService.log({
module: 'MARKETING',
action: 'RECOVERY_SENT',
userId: cart.userId,
tenantId: 'SYSTEM', // 实际应从用户上下文获取
traceId: `RECOVER-${Date.now()}`,
resourceType: 'cart',
resourceId: String(cart.id),
afterSnapshot: { discountCode, emailContent },
source: 'node',
result: 'success'
});
} catch (error: any) {
logger.error(`[AbandonedRecovery] Strategy failed for user ${cart.userId}: ${error.message}`);
}
}
private static async getCustomerScore(userId: string): Promise<number> {
// 模拟根据历史 LTV 获取的用户得分 (0-100)
return Math.floor(Math.random() * 100);
}
}