Zum Inhalt

Gutschein-Verwaltung (Vouchers)

Seite: app/vouchers/page.tsx (245 Zeilen) API: app/api/vouchers/route.ts (112 Zeilen) Stats-API: app/api/vouchers/stats/route.ts (25 Zeilen) Pfad: /vouchers

Übersicht

Die Voucher-Seite ermöglicht die Verwaltung und Vergabe von Bonus-Gutscheinen.

┌──────────────────────────────────────────────┐
│ Statistiken pro Typ                          │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐      │
│ │marketing │ │affiliate │ │leaderboard│     │
│ │ 45 total │ │ 12 total │ │  3 total  │     │
│ │ 20 aktiv │ │  8 aktiv │ │  3 aktiv  │     │
│ └──────────┘ └──────────┘ └──────────┘      │
├──────────────────────────────────────────────┤
│ Bonus vergeben                               │
│ [Wallet] [Amount: 1-100] [Type] [Reason]     │
│                              [Grant Bonus]    │
├──────────────────────────────────────────────┤
│ Filter: [Type ▾] [Status ▾]                 │
├──────────────────────────────────────────────┤
│ Voucher-Liste (paginiert)                    │
│ ID | Buyer | Amount | Type | Status | Dates  │
└──────────────────────────────────────────────┘

Statistiken

SELECT created_by,
       COUNT(*) as total_count,
       SUM(amount) as total_amount,
       COUNT(*) FILTER (WHERE used = false) as unused_count,
       SUM(amount) FILTER (WHERE used = false) as unused_amount,
       COUNT(*) FILTER (WHERE used = true) as used_count
FROM extra_bonus_vouchers
GROUP BY created_by
ORDER BY total_count DESC

Bonus-Vergabe (POST)

Validierung

  1. Pflichtfelder: Buyer (Wallet), Amount, Type
  2. Amount: 1-100 Tipps
  3. Marketing-Bonus-Limit: Max. 3 aktive Marketing-Boni pro Spieler
// vouchers/route.ts:76-97
// Marketing-Bonus Limit prüfen
if (type === "marketing_bonus") {
  const maxActive = await getBoConfig("reengagement_max_active_bonuses") ?? 3;

  const existing = await db.query(
    `SELECT COUNT(*) as cnt FROM extra_bonus_vouchers
     WHERE LOWER(buyer) = $1
       AND created_by = 'marketing_bonus'
       AND used = false
       AND (expires_at IS NULL OR expires_at > NOW())`,
    [buyer.toLowerCase()]
  );

  if (existing.rows[0].cnt >= maxActive) {
    return Response.json(
      { error: `Max ${maxActive} aktive Marketing-Boni erlaubt` },
      { status: 409 }
    );
  }
}

Insert

INSERT INTO extra_bonus_vouchers (buyer, amount, reason, created_by, expires_at)
VALUES ($1, $2, $3, $4,
  CASE WHEN $4 = 'marketing_bonus'
    THEN NOW() + INTERVAL '30 days'
    ELSE NULL
  END
)
RETURNING *

Voucher-Typen

created_by Quelle Ablauf
marketing_bonus Re-Engagement / Admin 30 Tage
affiliate_bonus Affiliate-Worker Kein Ablauf
leaderboard_bonus Leaderboard-Worker Kein Ablauf
compensation Admin (manuell) Kein Ablauf
instant_bonus Admin (manuell) Kein Ablauf
profile_bonus Profil-Vervollständigung Kein Ablauf

Filter

Filter Optionen
Type Alle / marketing_bonus / affiliate_bonus / leaderboard_bonus / compensation
Status Alle / Aktiv (unused) / Verwendet (used)

Tabellen-Spalten

Spalte Beschreibung
ID Voucher-ID
Buyer Wallet-Adresse (Link zum User)
Amount Anzahl Bonus-Tipps (fett)
Type created_by-Wert
Status ACTIVE/USED Badge
Created Erstellungsdatum
Used At Einlöse-Datum (falls verwendet)