feat: Bienenpatenschaft mit Urkunde, Abo, Medaillen und 3D-Konto
Setzt Teilprojekte 1-5 der Spec 2026-10-16 um: - Urkunden-Engine (lib/certificate): ein Layout fuer PDF, Vorschau und 3D-Textur, Vorlage classic@1, eingebettete OFL-Schriften, Testmatrix - Einmal-Patenschaft ueber Traegerprodukte (Kategorie PATENSCHAFT), Paket wird im Checkout in Patenschaft (Regelsatz) und Honig aufgeteilt, Kontopflicht, rein digitale Bestellungen (FulfillmentMethod DIGITAL) - Ausstellung nach Zahlung idempotent, Wiederholung im App-Prozess (instrumentation + background-jobs), Ablage in CERTIFICATE_DIR - Verkaufsseite /patenschaft mit Konfigurator und Live-Vorschau, Warenkorb mit Schluessel je Position - Kundenkonto: 3D-Ansicht von Urkunde und Medaille, Downloads, ZIP, Uebertragung per befristetem Einmal-Link, Treuestufen bis VIP - Admin: Angebote, Patenschaften, Druck, Korrektur mit Revisionen, Abos, Erntefenster, Deckung, SEPA-Frist, Treuestufen - Abo ueber Stripe Billing: Lieferung erst nach bezahlten Monaten, SEPA-Neukunden-Frist und Rueckbuchungsschutz, Honigreservierung, Kuendigungsbutton, Monatsrechnungen, Stripe-Einrichtungsskript Startangebote werden inaktiv angelegt; Livebetrieb erst nach Freigabe. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
dcb578f062
commit
f227aeb8f9
269 files changed
+28054
-141
No files matched your search
@@ -0,0 +1,54 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { logAdminAction } from "@/lib/audit-log";
|
||||
import { MAX_SEPA_HOLD_DAYS, SEPA_HOLD_DAYS_KEY, getSepaHoldDays } from "@/lib/sponsorship/settings";
|
||||
import { requireAdmin, fail, succeed, str } from "./_shared";
|
||||
import { intOrNull } from "./sponsorship-shared";
|
||||
|
||||
const SUBSCRIPTIONS_PATH = "/admin/patenschaften/abos";
|
||||
|
||||
/**
|
||||
* Hebt die SEPA-Sperre eines Kontos auf (gesetzt nach einer Rueckbuchung).
|
||||
* Danach kann das Konto neue Abos wieder per Lastschrift abschliessen.
|
||||
*/
|
||||
export async function liftSepaBlockAction(formData: FormData): Promise<void> {
|
||||
const admin = await requireAdmin();
|
||||
const customerId = str(formData, "customerId");
|
||||
const customer = await prisma.customer.findUnique({ where: { id: customerId }, select: { email: true, sepaBlockedAt: true } });
|
||||
if (!customer) return fail(SUBSCRIPTIONS_PATH, "Das Konto wurde nicht gefunden.");
|
||||
if (!customer.sepaBlockedAt) return succeed(SUBSCRIPTIONS_PATH, "Für dieses Konto besteht keine SEPA-Sperre.");
|
||||
|
||||
await prisma.customer.update({ where: { id: customerId }, data: { sepaBlockedAt: null } });
|
||||
await logAdminAction(admin, {
|
||||
action: "customer.sepa_unblock",
|
||||
entityType: "Customer",
|
||||
entityId: customerId,
|
||||
detail: `${customer.email} (gesperrt seit ${customer.sepaBlockedAt.toISOString().slice(0, 10)})`,
|
||||
});
|
||||
revalidatePath(SUBSCRIPTIONS_PATH);
|
||||
return succeed(SUBSCRIPTIONS_PATH, `SEPA-Sperre für ${customer.email} aufgehoben.`);
|
||||
}
|
||||
|
||||
export async function updateSepaHoldDaysAction(formData: FormData): Promise<void> {
|
||||
const admin = await requireAdmin();
|
||||
const days = intOrNull(str(formData, "sepaHoldDays"));
|
||||
if (days === null || days > MAX_SEPA_HOLD_DAYS) {
|
||||
return fail(SUBSCRIPTIONS_PATH, `Bitte eine ganze Zahl von 0 bis ${MAX_SEPA_HOLD_DAYS} Tagen angeben.`);
|
||||
}
|
||||
const previous = await getSepaHoldDays();
|
||||
await prisma.siteContent.upsert({
|
||||
where: { key: SEPA_HOLD_DAYS_KEY },
|
||||
create: { key: SEPA_HOLD_DAYS_KEY, valueDe: String(days) },
|
||||
update: { valueDe: String(days) },
|
||||
});
|
||||
await logAdminAction(admin, {
|
||||
action: "sponsorship.sepa_hold_days",
|
||||
entityType: "SiteContent",
|
||||
entityId: SEPA_HOLD_DAYS_KEY,
|
||||
detail: `${previous} → ${days} Tage`,
|
||||
});
|
||||
revalidatePath(SUBSCRIPTIONS_PATH);
|
||||
return succeed(SUBSCRIPTIONS_PATH, days === 0 ? "SEPA-Neukunden-Frist abgeschaltet." : `SEPA-Neukunden-Frist auf ${days} Tage gesetzt.`);
|
||||
}
|
||||
Reference in new issue
Block a user