From 4ff55a3736759494666e97be0e2dd2a60197594c Mon Sep 17 00:00:00 2001 From: Git Manager GUI Date: Tue, 28 Apr 2026 22:17:42 +0200 Subject: [PATCH] Upload folder via GUI - src --- .../de/mviper/spigot/IngameShopSpigot.java | 396 ++++++++++++++---- .../src/main/resources/config.yml | 1 + 2 files changed, 312 insertions(+), 85 deletions(-) diff --git a/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java b/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java index 11262e2..2d67aad 100644 --- a/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java +++ b/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java @@ -921,13 +921,11 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { anyCode = true; } else if ("fly_abo".equals(type)) { - // Fly-Abo: Tage-basiertes Abonnement mit tägl. Stunden-Limit - int days = cmdObj.has("days") - ? cmdObj.get("days").getAsInt() : 30; + // Fly-Abo: monatliche Abbuchung, Preis des Shop-Artikels = Monatsbeitrag String label = cmdObj.has("label") - ? cmdObj.get("label").getAsString() - : "Fly-Abo " + days + " Tage"; - flyAboManager.grantAbo(player, days, label); + ? cmdObj.get("label").getAsString() : "Fly-Abo"; + int monthlyPrice = (int) data.price; + flyAboManager.grantAbo(player, label, monthlyPrice); anyCode = true; } else if ("generic".equals(type) && cmdObj.has("cmd")) { @@ -1329,11 +1327,20 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { p.sendMessage(ChatColor.GRAY + " Kein aktives Fly-Abo."); p.sendMessage(ChatColor.GRAY + " Im Shop erhältlich: " + ChatColor.AQUA + "viper-network.de"); } else { - p.sendMessage(ChatColor.AQUA + " Status: " + ChatColor.GREEN + "✔ Aktiv"); - p.sendMessage(ChatColor.AQUA + " Paket: " + ChatColor.WHITE + abo.label); - p.sendMessage(ChatColor.AQUA + " Läuft ab: " + ChatColor.YELLOW + abo.expiresAt); - if (abo.cancelled) - p.sendMessage(ChatColor.RED + " ⚠ Kündigung vorgemerkt – läuft zum o.g. Datum aus"); + p.sendMessage(ChatColor.AQUA + " Paket: " + ChatColor.WHITE + abo.label); + p.sendMessage(ChatColor.AQUA + " Preis: " + ChatColor.WHITE + abo.monthlyPrice + " " + currency + " / Monat"); + if (abo.cancelled) { + String reason = "payment_failed".equals(abo.cancellationReason) + ? " (Zahlung fehlgeschlagen)" : " (durch dich)"; + p.sendMessage(ChatColor.RED + " Status: ⚠ Gekündigt" + reason); + p.sendMessage(ChatColor.AQUA + " Aktiv bis: " + ChatColor.YELLOW + abo.periodEnd); + p.sendMessage(ChatColor.GRAY + " Danach kein Fly-Abo mehr aktiv."); + } else { + p.sendMessage(ChatColor.AQUA + " Status: " + ChatColor.GREEN + "✔ Aktiv"); + p.sendMessage(ChatColor.AQUA + " Aktiv bis: " + ChatColor.YELLOW + abo.periodEnd); + p.sendMessage(ChatColor.AQUA + " Nächste Abbuchung: " + ChatColor.WHITE + abo.nextBillingDate); + p.sendMessage(ChatColor.GRAY + " Kündigen zum Monatsende: " + ChatColor.WHITE + "/flyabocancel"); + } int usedSec = flyAboManager.getUsedTodaySec(p.getName()); int maxSec = flyAboMaxDailySec; int remSec = Math.max(0, maxSec - usedSec); @@ -1353,17 +1360,24 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { } } - /** /flyabocancel – Abo zum Ablauf kündigen */ + /** /flyabocancel – Abo zum Ende des laufenden Monats kündigen */ private class FlyAboCancelCommand implements CommandExecutor { @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (!(sender instanceof Player)) { sender.sendMessage("Nur für Spieler."); return true; } Player p = (Player) sender; + + // Letzten Tag des laufenden Monats berechnen + java.time.LocalDate today = java.time.LocalDate.now(); + java.time.LocalDate monthEnd = today.withDayOfMonth(today.lengthOfMonth()); + String endDateStr = monthEnd.format(java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy")); + // Bestätigung erforderlich if (args.length == 0 || !args[0].equalsIgnoreCase("confirm")) { p.sendMessage(ChatColor.YELLOW + "⚠ Möchtest du dein Fly-Abo wirklich kündigen?"); - p.sendMessage(ChatColor.GRAY + " Das Abo läuft bis zum Ablaufdatum weiter."); + p.sendMessage(ChatColor.GRAY + " Das Abo läuft noch bis zum " + ChatColor.WHITE + endDateStr + ChatColor.GRAY + " weiter."); + p.sendMessage(ChatColor.GRAY + " Ab dem 1. des Folgemonats wird es nicht mehr verlängert."); p.sendMessage(ChatColor.WHITE + " Bestätigen: " + ChatColor.RED + "/flyabocancel confirm"); return true; } @@ -1372,11 +1386,11 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { boolean ok = flyAboManager.cancelAbo(p.getName()); Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> { if (ok) { - p.sendMessage(ChatColor.YELLOW + "✈ Dein Fly-Abo wurde zur Kündigung vorgemerkt."); - p.sendMessage(ChatColor.GRAY + " Es bleibt bis zum Ablaufdatum aktiv."); + p.sendMessage(ChatColor.YELLOW + "✈ Dein Fly-Abo wurde zum " + ChatColor.WHITE + endDateStr + ChatColor.YELLOW + " gekündigt."); + p.sendMessage(ChatColor.GRAY + " Bis dahin kannst du es wie gewohnt nutzen."); p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1.0F, 0.8F); } else { - p.sendMessage(ChatColor.RED + "✗ Kein aktives Fly-Abo gefunden."); + p.sendMessage(ChatColor.RED + "✗ Kein aktives Fly-Abo gefunden oder bereits gekündigt."); } }); } @@ -1385,7 +1399,7 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { } } - /** /flyabogive [Label] – Admin gibt Abo manuell */ + /** /flyabogive [Label] – Admin gibt Abo manuell */ private class FlyAboGiveCommand implements CommandExecutor { @Override public boolean onCommand(CommandSender sender, Command command, @@ -1395,33 +1409,37 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { return true; } if (args.length < 2) { - sender.sendMessage(ChatColor.YELLOW + "Verwendung: /flyabogive [Label]"); + sender.sendMessage(ChatColor.YELLOW + "Verwendung: /flyabogive [Label]"); + sender.sendMessage(ChatColor.GRAY + " Preis = 0 → kostenloses Abo (kein Vault-Abzug)"); return true; } String targetName = args[0]; - int days; - try { days = Integer.parseInt(args[1]); } + int monthlyPrice; + try { monthlyPrice = Integer.parseInt(args[1]); } catch (NumberFormatException e) { - sender.sendMessage(ChatColor.RED + "✗ Tage muss eine Zahl sein."); + sender.sendMessage(ChatColor.RED + "✗ Monatspreis muss eine Zahl sein."); return true; } String aboLabel = args.length >= 3 ? String.join(" ", Arrays.copyOfRange(args, 2, args.length)) - : "Fly-Abo " + days + " Tage"; + : "Fly-Abo"; Player target = Bukkit.getPlayer(targetName); new BukkitRunnable() { @Override public void run() { - flyAboManager.grantAboByName(targetName, days, aboLabel); + flyAboManager.grantAboByName(targetName, aboLabel, monthlyPrice); Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> { - sender.sendMessage(ChatColor.GREEN + "✔ Fly-Abo (" + days + " Tage) an " - + ChatColor.YELLOW + targetName + ChatColor.GREEN + " vergeben."); + sender.sendMessage(ChatColor.GREEN + "✔ Fly-Abo an " + + ChatColor.YELLOW + targetName + ChatColor.GREEN + " vergeben." + + ChatColor.GRAY + " (Monatspreis: " + monthlyPrice + " " + currency + ")"); if (target != null && target.isOnline()) { target.sendMessage(""); target.sendMessage(ChatColor.GOLD + "✈ ════════════════════════════"); target.sendMessage(ChatColor.YELLOW + " Fly-Abo aktiviert!"); - target.sendMessage(ChatColor.GRAY + " Paket: " + ChatColor.WHITE + aboLabel); + target.sendMessage(ChatColor.GRAY + " Paket: " + ChatColor.WHITE + aboLabel); + target.sendMessage(ChatColor.GRAY + " Monatspreis: " + ChatColor.WHITE + monthlyPrice + " " + currency); target.sendMessage(ChatColor.GRAY + " Tägl. Limit: " + ChatColor.WHITE + flyManager.formatTime(flyAboMaxDailySec)); + target.sendMessage(ChatColor.GRAY + " Nächste Abbuchung: " + ChatColor.YELLOW + "1. des Folgemonats"); target.sendMessage(ChatColor.GRAY + " Status: /flyabo"); target.sendMessage(ChatColor.GOLD + " ════════════════════════════"); target.sendMessage(""); @@ -2191,16 +2209,27 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { // ── Fly-Abo CRUD ────────────────────────────────────────────────── void createAboTable() { + // Migration: alte expires_at-Tabelle auf neues Schema upgraden String sqlAbo = "CREATE TABLE IF NOT EXISTS wis_fly_abos (" - + " id INT AUTO_INCREMENT PRIMARY KEY," - + " player_name VARCHAR(64) NOT NULL COLLATE utf8mb4_general_ci," - + " label VARCHAR(128) NOT NULL," - + " cancelled TINYINT(1) NOT NULL DEFAULT 0," - + " expires_at DATETIME NOT NULL," - + " granted_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP," + + " id INT AUTO_INCREMENT PRIMARY KEY," + + " player_name VARCHAR(64) NOT NULL COLLATE utf8mb4_general_ci," + + " label VARCHAR(128) NOT NULL," + + " monthly_price INT NOT NULL DEFAULT 0," + + " cancelled TINYINT(1) NOT NULL DEFAULT 0," + + " cancellation_reason VARCHAR(32) DEFAULT NULL," + + " next_billing_date DATE NOT NULL," + + " period_end DATE NOT NULL," + + " granted_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP," + " UNIQUE KEY uq_player (player_name)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"; + // Migrations: Spalten ergänzen falls Tabelle schon in alter Form existiert + String[] migrations = { + "ALTER TABLE wis_fly_abos ADD COLUMN IF NOT EXISTS monthly_price INT NOT NULL DEFAULT 0 AFTER label", + "ALTER TABLE wis_fly_abos ADD COLUMN IF NOT EXISTS cancellation_reason VARCHAR(32) DEFAULT NULL AFTER cancelled", + "ALTER TABLE wis_fly_abos ADD COLUMN IF NOT EXISTS next_billing_date DATE NOT NULL DEFAULT (CURDATE()) AFTER cancellation_reason", + "ALTER TABLE wis_fly_abos ADD COLUMN IF NOT EXISTS period_end DATE NOT NULL DEFAULT (LAST_DAY(CURDATE())) AFTER next_billing_date", + }; String sqlUsage = "CREATE TABLE IF NOT EXISTS wis_fly_abo_usage (" + " id INT AUTO_INCREMENT PRIMARY KEY," @@ -2212,38 +2241,53 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { try (Statement stmt = connection.createStatement()) { stmt.execute(sqlAbo); stmt.execute(sqlUsage); + for (String m : migrations) { + try { stmt.execute(m); } catch (SQLException ignored) {} + } } catch (SQLException e) { plugin.getLogger().log(Level.SEVERE, "Abo-Tabellen-Erstellung fehlgeschlagen", e); } } - void saveAbo(String playerName, int days, String label) { + /** + * Neues Abo aktivieren oder bestehendes verlängern. + * next_billing_date = 1. des Folgemonats + * period_end = letzter Tag des laufenden Monats (oder des aktuellen Abomonats) + */ + void saveAbo(String playerName, String label, int monthlyPrice) { String sql = - "INSERT INTO wis_fly_abos (player_name, label, cancelled, expires_at)" - + " VALUES (?, ?, 0, DATE_ADD(NOW(), INTERVAL ? DAY))" + "INSERT INTO wis_fly_abos" + + " (player_name, label, monthly_price, cancelled, cancellation_reason," + + " next_billing_date, period_end)" + + " VALUES (?, ?, ?, 0, NULL," + + " DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 1 MONTH), '%Y-%m-01')," + + " LAST_DAY(CURDATE()))" + " ON DUPLICATE KEY UPDATE" - + " label = ?, cancelled = 0," - + " expires_at = IF(expires_at > NOW()," - + " DATE_ADD(expires_at, INTERVAL ? DAY)," - + " DATE_ADD(NOW(), INTERVAL ? DAY))," + + " label = ?, monthly_price = ?, cancelled = 0, cancellation_reason = NULL," + + " next_billing_date = DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 1 MONTH), '%Y-%m-01')," + + " period_end = LAST_DAY(CURDATE())," + " granted_at = NOW()"; try (PreparedStatement ps = connection.prepareStatement(sql)) { ps.setString(1, playerName); ps.setString(2, label); - ps.setInt(3, days); + ps.setInt(3, monthlyPrice); ps.setString(4, label); - ps.setInt(5, days); - ps.setInt(6, days); + ps.setInt(5, monthlyPrice); ps.executeUpdate(); } catch (SQLException e) { plugin.getLogger().log(Level.WARNING, "saveAbo SQL Fehler", e); } } + /** + * Abo durch Spieler kündigen – bleibt bis period_end aktiv. + * period_end wird auf LAST_DAY(CURDATE()) gesetzt (Ende des laufenden Monats). + */ boolean cancelAbo(String playerName) { String sql = - "UPDATE wis_fly_abos SET cancelled = 1" - + " WHERE player_name = ? AND expires_at > NOW() AND cancelled = 0"; + "UPDATE wis_fly_abos SET cancelled = 1, cancellation_reason = 'user'," + + " period_end = LAST_DAY(CURDATE())" + + " WHERE player_name = ? AND period_end >= CURDATE() AND cancelled = 0"; try (PreparedStatement ps = connection.prepareStatement(sql)) { ps.setString(1, playerName); return ps.executeUpdate() == 1; @@ -2253,20 +2297,88 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { } } + /** + * Abo wegen Zahlungsausfall kündigen – sofort zum period_end des laufenden Monats. + */ + void cancelAboPaymentFailed(String playerName) { + String sql = + "UPDATE wis_fly_abos SET cancelled = 1, cancellation_reason = 'payment_failed'," + + " period_end = LAST_DAY(CURDATE())" + + " WHERE player_name = ? AND cancelled = 0"; + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, playerName); + ps.executeUpdate(); + } catch (SQLException e) { + plugin.getLogger().log(Level.WARNING, "cancelAboPaymentFailed SQL Fehler", e); + } + } + + /** + * Billing verlängern: next_billing_date auf 1. des übernächsten Monats, + * period_end auf letzten Tag des neuen Monats setzen. + */ + void renewAbo(String playerName) { + String sql = + "UPDATE wis_fly_abos" + + " SET next_billing_date = DATE_FORMAT(DATE_ADD(next_billing_date, INTERVAL 1 MONTH), '%Y-%m-01')," + + " period_end = LAST_DAY(next_billing_date)" + + " WHERE player_name = ? AND cancelled = 0"; + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, playerName); + ps.executeUpdate(); + } catch (SQLException e) { + plugin.getLogger().log(Level.WARNING, "renewAbo SQL Fehler", e); + } + } + + /** + * Alle Abos holen die heute fällig sind (next_billing_date = heute, nicht gekündigt). + */ + List getDueBillings() { + List list = new ArrayList<>(); + String sql = + "SELECT player_name, label, monthly_price, cancelled, cancellation_reason," + + " DATE_FORMAT(next_billing_date, '%d.%m.%Y') AS billing_fmt," + + " DATE_FORMAT(period_end, '%d.%m.%Y') AS period_fmt" + + " FROM wis_fly_abos" + + " WHERE next_billing_date <= CURDATE() AND cancelled = 0"; + try (PreparedStatement ps = connection.prepareStatement(sql)) { + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) + list.add(new FlyAboManager.AboEntry( + rs.getString("player_name"), + rs.getString("label"), + rs.getInt("monthly_price"), + false, + null, + rs.getString("billing_fmt"), + rs.getString("period_fmt"))); + } + } catch (SQLException e) { + plugin.getLogger().log(Level.WARNING, "getDueBillings SQL Fehler", e); + } + return list; + } + FlyAboManager.AboEntry getActiveAbo(String playerName) { String sql = - "SELECT label, cancelled," - + " DATE_FORMAT(expires_at, '%d.%m.%Y') AS expires_fmt" + "SELECT label, monthly_price, cancelled, cancellation_reason," + + " DATE_FORMAT(next_billing_date, '%d.%m.%Y') AS billing_fmt," + + " DATE_FORMAT(period_end, '%d.%m.%Y') AS period_fmt" + " FROM wis_fly_abos" - + " WHERE player_name = ? AND expires_at > NOW()"; + + " WHERE player_name = ? AND period_end >= CURDATE()"; try (PreparedStatement ps = connection.prepareStatement(sql)) { ps.setString(1, playerName); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) return new FlyAboManager.AboEntry( + rs.getString("player_name"), rs.getString("label"), - rs.getString("expires_fmt"), - rs.getInt("cancelled") == 1); + rs.getInt("monthly_price"), + rs.getInt("cancelled") == 1, + rs.getString("cancellation_reason"), + rs.getString("billing_fmt"), + rs.getString("period_fmt")); } } catch (SQLException e) { plugin.getLogger().log(Level.WARNING, "getActiveAbo SQL Fehler", e); @@ -2275,7 +2387,7 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { } void cleanupExpiredAbos(String playerName) { - String sql = "DELETE FROM wis_fly_abos WHERE player_name = ? AND expires_at <= NOW()"; + String sql = "DELETE FROM wis_fly_abos WHERE player_name = ? AND period_end < CURDATE()"; try (PreparedStatement ps = connection.prepareStatement(sql)) { ps.setString(1, playerName); ps.executeUpdate(); @@ -2957,36 +3069,37 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { db.createAboTable(); } - // ── Abo vergeben (über Order) ────────────────────────────────── + // ── Abo aktivieren (über Order) ──────────────────────────────── - void grantAbo(Player player, int days, String label) { - grantAboByName(player.getName(), days, label); + void grantAbo(Player player, String label, int monthlyPrice) { + db.saveAbo(player.getName(), label, monthlyPrice); Bukkit.getScheduler().runTask(plugin, () -> { player.sendMessage(""); player.sendMessage(ChatColor.GOLD + "✈ ════════════════════════════"); player.sendMessage(ChatColor.YELLOW + " Fly-Abo aktiviert!"); - player.sendMessage(ChatColor.GRAY + " Paket: " + ChatColor.WHITE + label); - player.sendMessage(ChatColor.GRAY + " Laufzeit: " + ChatColor.WHITE + days + " Tag(e)"); - player.sendMessage(ChatColor.GRAY + " Tägl. Limit: " + ChatColor.WHITE + player.sendMessage(ChatColor.GRAY + " Paket: " + ChatColor.WHITE + label); + player.sendMessage(ChatColor.GRAY + " Monatspreis: " + ChatColor.WHITE + monthlyPrice + " " + currency); + player.sendMessage(ChatColor.GRAY + " Tägl. Limit: " + ChatColor.WHITE + flyManager.formatTime(flyAboMaxDailySec)); - player.sendMessage(ChatColor.GRAY + " Status: " + ChatColor.WHITE + "/flyabo"); + player.sendMessage(ChatColor.GRAY + " Nächste Abbuchung: " + ChatColor.YELLOW + "1. des Folgemonats"); + player.sendMessage(ChatColor.GRAY + " Status: " + ChatColor.WHITE + "/flyabo"); player.sendMessage(ChatColor.GOLD + " ════════════════════════════"); player.sendMessage(""); player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.2F); }); } - void grantAboByName(String playerName, int days, String label) { - db.saveAbo(playerName, days, label); + void grantAboByName(String playerName, String label, int monthlyPrice) { + db.saveAbo(playerName, label, monthlyPrice); } - // ── Abo kündigen ─────────────────────────────────────────────── + // ── Kündigung durch Spieler ──────────────────────────────────── boolean cancelAbo(String playerName) { return db.cancelAbo(playerName); } - // ── Abo-Status lesen ────────────────────────────────────────── + // ── Status lesen ────────────────────────────────────────────── AboEntry getActiveAbo(String playerName) { return db.getActiveAbo(playerName); @@ -2996,21 +3109,37 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { return db.getUsedTodaySec(playerName); } - // ── Beim Join: Abo prüfen ───────────────────────────────────── + // ── Beim Join: Abo prüfen + ggf. Zahlung-fehlgeschlagen-Meldung ── void onPlayerJoin(Player player) { AboEntry abo = db.getActiveAbo(player.getName()); - if (abo == null) return; - // Abgelaufenes Abo bereinigen + // Abgelaufene Einträge bereinigen db.cleanupExpiredAbos(player.getName()); abo = db.getActiveAbo(player.getName()); - if (abo == null) { - Bukkit.getScheduler().runTask(plugin, () -> - player.sendMessage(ChatColor.RED + "✈ Dein Fly-Abo ist abgelaufen.")); - return; + + if (abo == null) return; + + // Zahlungsausfall-Hinweis + final AboEntry finalAbo = abo; + if ("payment_failed".equals(abo.cancellationReason)) { + Bukkit.getScheduler().runTask(plugin, () -> { + player.sendMessage(""); + player.sendMessage(ChatColor.RED + "✈ ════════════════════════════"); + player.sendMessage(ChatColor.RED + " Fly-Abo: Zahlung fehlgeschlagen!"); + player.sendMessage(ChatColor.GRAY + " Dein Abo konnte nicht verlängert werden,"); + player.sendMessage(ChatColor.GRAY + " da dein Kontostand nicht ausreichte."); + player.sendMessage(ChatColor.GRAY + " Es läuft am " + ChatColor.YELLOW + finalAbo.periodEnd + + ChatColor.GRAY + " aus."); + player.sendMessage(ChatColor.WHITE + " Neues Abo: " + ChatColor.AQUA + "viper-network.de"); + player.sendMessage(ChatColor.RED + " ════════════════════════════"); + player.sendMessage(""); + player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1.0F, 0.5F); + }); } + if (abo.cancelled) return; // Gekündigtes Abo: kein Fly mehr gewähren wenn period_end heute + int usedToday = db.getUsedTodaySec(player.getName()); int remaining = Math.max(0, flyAboMaxDailySec - usedToday); if (remaining <= 0) { @@ -3023,27 +3152,23 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { return; } - // Fly mit verbleibender Tageszeit gewähren, aber in Abo-Modus - final int grantSec = remaining; - final String aboLabel = abo.label; - Bukkit.getScheduler().runTask(plugin, () -> { - flyManager.grantFlyWithAboTracking(player, grantSec, aboLabel, - player.getName(), flyAboMaxDailySec); - }); + final int grantSec = remaining; + Bukkit.getScheduler().runTask(plugin, () -> + flyManager.grantFlyWithAboTracking(player, grantSec, finalAbo.label, + player.getName(), flyAboMaxDailySec)); } - // ── Täglicher Reset-Check ───────────────────────────────────── + // ── Monatlicher Billing-Check (läuft täglich) ───────────────── void startDailyResetChecker() { - // Alle 5 Minuten prüfen ob Tagesdatum gewechselt hat → online Spieler neu versorgen + // Alle 5 Minuten: online Spieler mit Abo ohne aktiven Fly neu versorgen new BukkitRunnable() { @Override public void run() { for (Player p : Bukkit.getOnlinePlayers()) { new BukkitRunnable() { @Override public void run() { AboEntry abo = db.getActiveAbo(p.getName()); - if (abo == null) return; - // Wenn Tageslimit noch nicht erreicht und kein Fly aktiv → erneut gewähren + if (abo == null || abo.cancelled) return; int usedToday = db.getUsedTodaySec(p.getName()); int remaining = Math.max(0, flyAboMaxDailySec - usedToday); if (remaining > 0 @@ -3056,18 +3181,119 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { }.runTaskAsynchronously(plugin); } } - }.runTaskTimer(plugin, 20L * 300, 20L * 300); // alle 5 Minuten + }.runTaskTimer(plugin, 20L * 300, 20L * 300); + + // Täglich um ~00:05 Uhr: Billing-Check für den 1. des Monats + // Berechne Ticks bis 00:05 Uhr (approximiert: sofort starten, dann täglich wiederholen) + new BukkitRunnable() { + @Override public void run() { + java.time.LocalDate today = java.time.LocalDate.now(); + if (today.getDayOfMonth() != 1) return; // Nur am 1. des Monats ausführen + + new BukkitRunnable() { + @Override public void run() { + List due = db.getDueBillings(); + if (due.isEmpty()) return; + + getLogger().info("[FlyAbo] Billing-Tag: " + due.size() + " Abo(s) zu verlängern."); + + for (FlyAboManager.AboEntry entry : due) { + String playerName = entry.playerName; + int price = entry.monthlyPrice; + + // Vault-Abbuchung auf Main-Thread + Bukkit.getScheduler().runTask(plugin, () -> { + @SuppressWarnings("deprecation") + org.bukkit.OfflinePlayer op = Bukkit.getOfflinePlayer(playerName); + if (op == null || !op.hasPlayedBefore()) { + // Spieler unbekannt → überspringen (nächste Nacht nochmal) + return; + } + boolean hasBalance = (price == 0) || econ.getBalance(op) >= price; + if (hasBalance) { + if (price > 0) { + econ.withdrawPlayer(op, price); + if (!incomeReceiver.isEmpty()) { + @SuppressWarnings("deprecation") + org.bukkit.OfflinePlayer recv = + Bukkit.getOfflinePlayer(incomeReceiver); + if (recv != null && recv.hasPlayedBefore()) + econ.depositPlayer(recv, price); + } + } + // DB-Verlängerung async + new BukkitRunnable() { + @Override public void run() { + db.renewAbo(playerName); + getLogger().info("[FlyAbo] ✔ Abo verlängert: " + + playerName + " (" + price + " " + currency + ")"); + } + }.runTaskAsynchronously(plugin); + + // Online-Benachrichtigung + Player online = Bukkit.getPlayer(playerName); + if (online != null && online.isOnline()) { + online.sendMessage(""); + online.sendMessage(ChatColor.GOLD + "✈ ════════════════════════════"); + online.sendMessage(ChatColor.GREEN + " Fly-Abo verlängert!"); + online.sendMessage(ChatColor.GRAY + " Abgebucht: " + + ChatColor.WHITE + price + " " + currency); + online.sendMessage(ChatColor.GRAY + " Läuft bis: " + + ChatColor.YELLOW + "Ende des Monats"); + online.sendMessage(ChatColor.GOLD + " ════════════════════════════"); + online.sendMessage(""); + online.playSound(online.getLocation(), + Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F); + } + } else { + // Zahlung fehlgeschlagen → Abo kündigen + new BukkitRunnable() { + @Override public void run() { + db.cancelAboPaymentFailed(playerName); + getLogger().warning("[FlyAbo] ✘ Zahlung fehlgeschlagen: " + + playerName + " (benötigt: " + price + " " + currency + ")"); + } + }.runTaskAsynchronously(plugin); + + Player online = Bukkit.getPlayer(playerName); + if (online != null && online.isOnline()) { + online.sendMessage(""); + online.sendMessage(ChatColor.RED + "✈ ════════════════════════════"); + online.sendMessage(ChatColor.RED + " Fly-Abo: Zahlung fehlgeschlagen!"); + online.sendMessage(ChatColor.GRAY + " Benötigt: " + + ChatColor.WHITE + price + " " + currency); + online.sendMessage(ChatColor.GRAY + " Dein Abo läuft Ende des Monats aus."); + online.sendMessage(ChatColor.WHITE + " Verlängern: " + ChatColor.AQUA + "viper-network.de"); + online.sendMessage(ChatColor.RED + " ════════════════════════════"); + online.sendMessage(""); + online.playSound(online.getLocation(), + Sound.BLOCK_NOTE_BLOCK_BASS, 1.0F, 0.5F); + } + } + }); + } + } + }.runTaskAsynchronously(plugin); + } + }.runTaskTimer(plugin, 20L * 60 * 5, 20L * 60 * 60 * 24); // Start nach 5 min, dann täglich } // ── Data class ──────────────────────────────────────────────── static class AboEntry { - final String label, expiresAt; + final String playerName, label, cancellationReason, nextBillingDate, periodEnd; + final int monthlyPrice; final boolean cancelled; - AboEntry(String label, String expiresAt, boolean cancelled) { - this.label = label; - this.expiresAt = expiresAt; - this.cancelled = cancelled; + AboEntry(String playerName, String label, int monthlyPrice, + boolean cancelled, String cancellationReason, + String nextBillingDate, String periodEnd) { + this.playerName = playerName; + this.label = label; + this.monthlyPrice = monthlyPrice; + this.cancelled = cancelled; + this.cancellationReason = cancellationReason; + this.nextBillingDate = nextBillingDate; + this.periodEnd = periodEnd; } } } diff --git a/IngameShopSpigot/src/main/resources/config.yml b/IngameShopSpigot/src/main/resources/config.yml index 8cd1533..208bb7e 100644 --- a/IngameShopSpigot/src/main/resources/config.yml +++ b/IngameShopSpigot/src/main/resources/config.yml @@ -52,6 +52,7 @@ sell: # Hinweis: Den Basis-Ankaufspreis konfigurierst du im WP-Admin # unter Items → → "Ankauf aktivieren" price-offset: -10.0 + # ────────────────────────────────────────────── # Fly-Abo Einstellungen # ──────────────────────────────────────────────