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
- Pflichtfelder: Buyer (Wallet), Amount, Type
- Amount: 1-100 Tipps
- 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) |