Files
Wabenhain/src/app/patenschaft/abo/danke/page.tsx
T
Nick SzaunigandClaude Opus 5.5 f227aeb8f9 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]>
2026-10-16 14:07:30 +02:00

61 lines
2.5 KiB
TypeScript

import type { Metadata } from "next";
import { Container, LinkButton, SectionHeading } from "@/components/ui";
import { noIndexPrivate } from "@/lib/seo";
import { getCurrentCustomer } from "@/lib/auth";
import { resolveSubscriptionReturn } from "@/lib/sponsorship-subscription/checkout-return";
import { getLocale } from "@/i18n/locale";
import { getAboStrings } from "@/i18n/sponsorship/abo";
// Fragt bei jedem Aufruf den aktuellen Stand bei Stripe ab — nie aus einem Cache.
export const dynamic = "force-dynamic";
export async function generateMetadata(): Promise<Metadata> {
return { title: getAboStrings(await getLocale()).thanks.metaTitle, robots: noIndexPrivate };
}
type Props = { searchParams: Promise<Record<string, string | string[] | undefined>> };
/**
* Rueckkehrseite nach dem Abo-Checkout (return_url in checkout.ts). Ueber
* "abgeschlossen" entscheidet die serverseitige Nachfrage bei Stripe, nicht
* die URL; angelegt wird hier nichts — Abo, Zahlung und Urkunde entstehen
* ausschliesslich ueber den Webhook.
*/
export default async function AboThanksPage({ searchParams }: Props) {
const t = getAboStrings(await getLocale()).thanks;
const { session_id: sessionId } = await searchParams;
const customer = await getCurrentCustomer();
const result =
typeof sessionId === "string" && customer ? await resolveSubscriptionReturn(sessionId, customer.id) : { kind: "notCompleted" as const };
const view =
result.kind === "active"
? { title: t.activeTitle, text: t.activeText }
: result.kind === "processing"
? { title: t.processingTitle, text: t.processingText }
: { title: t.notCompletedTitle, text: t.notCompletedText };
return (
<div className="py-24">
<Container className="max-w-xl text-center">
<SectionHeading title={view.title} align="center" level="h1" className="mx-auto" />
<p role="status" className="mt-6 text-ink/70">
{view.text}
</p>
<div className="mt-10 flex flex-wrap items-center justify-center gap-4">
{result.kind === "notCompleted" ? (
<LinkButton href="/patenschaft">{t.backToOffer}</LinkButton>
) : (
<>
<LinkButton href={`/konto/patenschaft/abo/${result.subscriptionId}`}>{t.toSubscription}</LinkButton>
<LinkButton href="/konto/patenschaft" variant="secondary">
{t.toAccount}
</LinkButton>
</>
)}
</div>
</Container>
</div>
);
}