DiGA-Pfad-Foundation: User mit Rezept-Code löst im Onboarding ein, wird auf plan='legend' hochgestuft (Default), Onboarding-Step springt auf 'done', diga_code_redeemed_at als Audit-Trail. Trial-Modell wird übersprungen. - Prisma model DigaCode (code unique, expires_at, used_at, used_by_profile_id, grants_plan, notes, label) - Profile.digaCodeRedeemedAt für Reverse-Audit - Migration 20260517_add_diga_codes mit Table + FK + Index - Seed: REBREAK-TEST-001..010 (single-use, reset via SQL für erneutes Testen) - POST /api/onboarding/redeem-diga-code — atomare Transaction, klare 400-Errors (not_found | already_used | expired | invalid_input) Frontend (Duo-Onboarding) dockt später an — diese Backend-Foundation steht. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
49 lines
1.4 KiB
TypeScript
49 lines
1.4 KiB
TypeScript
import { redeemDigaCode } from "../../db/diga";
|
|
|
|
/**
|
|
* POST /api/onboarding/redeem-diga-code
|
|
*
|
|
* Löst einen DiGA-Rezept-Code ein und stuft den User auf Vollzugang hoch
|
|
* (`grants_plan`, Default 'legend'). Skipped Trial + Onboarding (step='done').
|
|
*
|
|
* Body: { code: string }
|
|
* Response 200: { success: true, plan: 'legend' }
|
|
* Response 400: { error: 'not_found' | 'already_used' | 'expired' | 'invalid_input' }
|
|
*
|
|
* Test-Codes (siehe Migration `20260517_add_diga_codes`): REBREAK-TEST-001..010
|
|
*/
|
|
export default defineEventHandler(async (event) => {
|
|
const user = await requireUser(event);
|
|
const body = await readBody(event).catch(() => ({}));
|
|
const code = body?.code;
|
|
|
|
if (typeof code !== "string" || code.trim().length === 0) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
data: { error: "invalid_input", message: "Code fehlt." },
|
|
});
|
|
}
|
|
|
|
const res = await redeemDigaCode(user.id, code);
|
|
|
|
if (!res.ok) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
data: { error: res.reason, message: errorMessage(res.reason) },
|
|
});
|
|
}
|
|
|
|
return { success: true, plan: res.plan };
|
|
});
|
|
|
|
function errorMessage(reason: "not_found" | "already_used" | "expired"): string {
|
|
switch (reason) {
|
|
case "not_found":
|
|
return "Dieser Code existiert nicht. Bitte prüfe die Schreibweise.";
|
|
case "already_used":
|
|
return "Dieser Code wurde bereits eingelöst.";
|
|
case "expired":
|
|
return "Dieser Code ist abgelaufen.";
|
|
}
|
|
}
|