import { randomInt } from "crypto"; import { Prisma } from "@/src/generated/prisma/client"; import { prisma } from "@/src/lib/prisma"; import { berlinDayEnd } from "@/src/modules/courses/lib/course-days"; import type { Reward, TriggerKind } from "./node-config"; // Ohne 0/O und 1/I, damit niemand den Code beim Abtippen verwechselt. const ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; const PREFIX: Record = { birthday: "GEBURTSTAG", milestone: "DANKE", winback: "COMEBACK", }; export function generateCodeString(kind: TriggerKind): string { let suffix = ""; for (let i = 0; i < 6; i++) suffix += ALPHABET[randomInt(ALPHABET.length)]; return `${PREFIX[kind]}-${suffix}`; } /** * Legt den persönlichen Rabattcode für eine Marketing-Mail an: einmal * einlösbar, nur für dieses Konto, gültig bis einschließlich `validUntilDay` * (Berliner Tag). Er erscheint nicht auf der Rabattcode-Seite (origin * "marketing"), sondern im Verlauf des Marketings. */ export async function createPersonalDiscountCode(params: { businessId: string; userId: string; triggerKind: TriggerKind; reward: Reward; validUntilDay: string; }): Promise<{ id: string; code: string; expiresAt: Date }> { const expiresAt = berlinDayEnd(params.validUntilDay); // Kollisionen sind bei 32^6 Möglichkeiten pro Präfix selten - ein paar // Versuche reichen. for (let attempt = 1; ; attempt++) { try { return await prisma.discountCode .create({ data: { businessId: params.businessId, code: generateCodeString(params.triggerKind), type: params.reward.type, value: params.reward.value, appliesTo: params.reward.appliesTo, scope: "all_courses", courseIds: [], maxRedemptions: 1, expiresAt, restrictedToUserId: params.userId, origin: "marketing", }, select: { id: true, code: true, expiresAt: true }, }) .then((code) => ({ ...code, expiresAt: code.expiresAt ?? expiresAt })); } catch (error) { const isCollision = error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002"; if (!isCollision || attempt >= 5) throw error; } } }