import { usePrisma } from "../utils/prisma"; /** * Versucht einen DiGA-Code für den User einzulösen. * * Validierung: * - Code existiert * - Code wurde noch nicht eingelöst (used_at IS NULL) * - Code ist nicht abgelaufen (expires_at IS NULL OR expires_at > NOW) * * Bei Erfolg (atomar in einer Transaktion): * - diga_codes: used_at = NOW(), used_by_profile_id = userId * - profiles: plan = code.grants_plan, onboarding_step = 'done', * diga_code_redeemed_at = NOW() * * Returns `null` wenn der Code ungültig ist (für saubere 4xx-Errors im Endpoint). */ export type RedeemResult = | { ok: true; plan: string; codeId: string } | { ok: false; reason: "not_found" | "already_used" | "expired" }; export async function redeemDigaCode( userId: string, rawCode: string, ): Promise { const code = rawCode.trim().toUpperCase(); const db = usePrisma(); return db.$transaction(async (tx) => { const found = await tx.digaCode.findUnique({ where: { code }, select: { id: true, usedAt: true, expiresAt: true, grantsPlan: true }, }); if (!found) { return { ok: false as const, reason: "not_found" as const }; } if (found.usedAt) { return { ok: false as const, reason: "already_used" as const }; } if (found.expiresAt && found.expiresAt.getTime() < Date.now()) { return { ok: false as const, reason: "expired" as const }; } const now = new Date(); await tx.digaCode.update({ where: { id: found.id }, data: { usedAt: now, usedByProfileId: userId }, }); await tx.profile.update({ where: { id: userId }, data: { plan: found.grantsPlan, onboardingStep: "done", digaCodeRedeemedAt: now, }, }); return { ok: true as const, plan: found.grantsPlan, codeId: found.id }; }); }