diff --git a/ezeconomy-bukkit/src/test/java/com/skyblockexp/ezeconomy/feature/BankTransferBalanceFeatureTest.java b/ezeconomy-bukkit/src/test/java/com/skyblockexp/ezeconomy/feature/BankTransferBalanceFeatureTest.java new file mode 100644 index 0000000..97b5f8b --- /dev/null +++ b/ezeconomy-bukkit/src/test/java/com/skyblockexp/ezeconomy/feature/BankTransferBalanceFeatureTest.java @@ -0,0 +1,56 @@ +package com.skyblockexp.ezeconomy.feature; + +import com.skyblockexp.ezeconomy.core.EzEconomyPlugin; +import com.skyblockexp.ezeconomy.feature.support.TestSupport; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.MockBukkit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class BankTransferBalanceFeatureTest { + private Object server; + private EzEconomyPlugin plugin; + private TestSupport.MockStorage storage; + + @BeforeEach + void setup() throws Exception { + server = MockBukkit.mock(); + plugin = MockBukkit.load(EzEconomyPlugin.class); + storage = new TestSupport.MockStorage(); + TestSupport.injectField(plugin, "storage", storage); + plugin.loadMessageProvider(); + } + + @AfterEach + void teardown() { + MockBukkit.unmock(); + } + + @Test + void bankDepositAndWithdrawMovePlayerWalletBalance() throws Exception { + Object ownerObj = server.getClass().getMethod("addPlayer", String.class).invoke(server, "owner_wallet"); + org.bukkit.entity.Player owner = (org.bukkit.entity.Player) ownerObj; + owner.setOp(true); + + Object memberObj = server.getClass().getMethod("addPlayer", String.class).invoke(server, "member_wallet"); + org.bukkit.entity.Player member = (org.bukkit.entity.Player) memberObj; + member.setOp(true); + + String currency = plugin.getDefaultCurrency(); + storage.setBalance(owner.getUniqueId(), currency, 200.0); + storage.setBalance(member.getUniqueId(), currency, 0.0); + + assertTrue(owner.performCommand("bank create teamwallet")); + assertTrue(owner.performCommand("bank deposit teamwallet 100")); + assertEquals(100.0, storage.getBankBalance("teamwallet", currency), 0.0001); + assertEquals(100.0, storage.getBalance(owner.getUniqueId(), currency), 0.0001); + + assertTrue(owner.performCommand("bank addmember teamwallet member_wallet")); + assertTrue(member.performCommand("bank withdraw teamwallet 25")); + assertEquals(75.0, storage.getBankBalance("teamwallet", currency), 0.0001); + assertEquals(25.0, storage.getBalance(member.getUniqueId(), currency), 0.0001); + } +} diff --git a/ezeconomy-papi/src/main/java/com/skyblockexp/ezeconomy/papi/EzEconomyPapiPlugin.java b/ezeconomy-papi/src/main/java/com/skyblockexp/ezeconomy/papi/EzEconomyPapiPlugin.java index 3f98075..83a2a82 100644 --- a/ezeconomy-papi/src/main/java/com/skyblockexp/ezeconomy/papi/EzEconomyPapiPlugin.java +++ b/ezeconomy-papi/src/main/java/com/skyblockexp/ezeconomy/papi/EzEconomyPapiPlugin.java @@ -11,6 +11,12 @@ public void onEnable() { getServer().getPluginManager().disablePlugin(this); return; } + org.bukkit.plugin.Plugin ezEconomy = Bukkit.getPluginManager().getPlugin("EzEconomy"); + if (ezEconomy == null || !ezEconomy.isEnabled()) { + getLogger().warning("EzEconomy is not enabled; disabling EzEconomy-PAPI expansion."); + getServer().getPluginManager().disablePlugin(this); + return; + } // Register the expansion new EzEconomyPAPIExpansion(this).register(); diff --git a/src/main/java/com/skyblockexp/ezeconomy/bootstrap/component/LockingComponent.java b/src/main/java/com/skyblockexp/ezeconomy/bootstrap/component/LockingComponent.java index 7fa9c64..bcccb55 100644 --- a/src/main/java/com/skyblockexp/ezeconomy/bootstrap/component/LockingComponent.java +++ b/src/main/java/com/skyblockexp/ezeconomy/bootstrap/component/LockingComponent.java @@ -9,6 +9,7 @@ import java.util.ServiceLoader; import java.util.Iterator; import java.io.IOException; +import java.util.function.Supplier; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import java.io.File; @@ -54,10 +55,8 @@ public void start() { boolean fallback = bungeeCfg.getBoolean("fallback-to-local", true); try { // 1) Try ServiceLoader using current classloader (useful for development/classpath-loaded providers) - ServiceLoader loader = ServiceLoader.load(LockManager.class); - Iterator it = loader.iterator(); - if (it.hasNext()) { - this.manager = it.next(); + this.manager = safeLoadFirstLockManager(() -> ServiceLoader.load(LockManager.class).iterator(), "current classloader"); + if (this.manager != null) { plugin.getLogger().info("Loaded LockManager via ServiceLoader: " + this.manager.getClass().getName()); } @@ -69,10 +68,8 @@ public void start() { if (jars != null) { for (File jar : jars) { try (URLClassLoader cl = new URLClassLoader(new URL[]{jar.toURI().toURL()}, this.getClass().getClassLoader())) { - ServiceLoader sl = ServiceLoader.load(LockManager.class, cl); - Iterator sit = sl.iterator(); - if (sit.hasNext()) { - this.manager = sit.next(); + this.manager = safeLoadFirstLockManager(() -> ServiceLoader.load(LockManager.class, cl).iterator(), "jar " + jar.getName()); + if (this.manager != null) { plugin.getLogger().info("Loaded LockManager from " + jar.getName() + ": " + this.manager.getClass().getName()); break; } @@ -137,10 +134,8 @@ public void start() { boolean fallback = redisCfg.getBoolean("fallback-to-local", true); try { // 1) Try ServiceLoader using current classloader (useful for development/classpath-loaded providers) - ServiceLoader loader = ServiceLoader.load(LockManager.class); - Iterator it = loader.iterator(); - if (it.hasNext()) { - this.manager = it.next(); + this.manager = safeLoadFirstLockManager(() -> ServiceLoader.load(LockManager.class).iterator(), "current classloader"); + if (this.manager != null) { plugin.getLogger().info("Loaded LockManager via ServiceLoader: " + this.manager.getClass().getName()); } @@ -152,10 +147,8 @@ public void start() { if (jars != null) { for (File jar : jars) { try (URLClassLoader cl = new URLClassLoader(new URL[]{jar.toURI().toURL()}, this.getClass().getClassLoader())) { - ServiceLoader sl = ServiceLoader.load(LockManager.class, cl); - Iterator sit = sl.iterator(); - if (sit.hasNext()) { - this.manager = sit.next(); + this.manager = safeLoadFirstLockManager(() -> ServiceLoader.load(LockManager.class, cl).iterator(), "jar " + jar.getName()); + if (this.manager != null) { plugin.getLogger().info("Loaded LockManager from " + jar.getName() + ": " + this.manager.getClass().getName()); break; } @@ -197,6 +190,18 @@ public void start() { plugin.setLockManager(this.manager); } + private LockManager safeLoadFirstLockManager(Supplier> iteratorSupplier, String source) { + try { + Iterator it = iteratorSupplier.get(); + if (it != null && it.hasNext()) { + return it.next(); + } + } catch (Throwable t) { + plugin.getLogger().warning("Ignoring invalid LockManager provider from " + source + ": " + t.getMessage()); + } + return null; + } + @Override public void stop() { plugin.setLockManager(null); diff --git a/src/main/java/com/skyblockexp/ezeconomy/command/bank/DepositSubcommand.java b/src/main/java/com/skyblockexp/ezeconomy/command/bank/DepositSubcommand.java index 7121614..1fd2773 100644 --- a/src/main/java/com/skyblockexp/ezeconomy/command/bank/DepositSubcommand.java +++ b/src/main/java/com/skyblockexp/ezeconomy/command/bank/DepositSubcommand.java @@ -1,10 +1,11 @@ package com.skyblockexp.ezeconomy.command.bank; -import com.skyblockexp.ezeconomy.command.Subcommand; -import com.skyblockexp.ezeconomy.core.EzEconomyPlugin; -import com.skyblockexp.ezeconomy.util.NumberUtil; -import net.milkbowl.vault.economy.EconomyResponse; -import org.bukkit.command.CommandSender; +import com.skyblockexp.ezeconomy.command.Subcommand; +import com.skyblockexp.ezeconomy.core.EzEconomyPlugin; +import com.skyblockexp.ezeconomy.util.NumberUtil; +import com.skyblockexp.ezeconomy.api.storage.StorageProvider; +import net.milkbowl.vault.economy.EconomyResponse; +import org.bukkit.command.CommandSender; /** * Subcommand for /bank deposit [currency] @@ -33,29 +34,45 @@ public boolean execute(CommandSender sender, String[] args) { final String bankName; final String amountStr; final String currency; - if (args.length == 1) { - if (!(sender instanceof org.bukkit.entity.Player)) { - com.skyblockexp.ezeconomy.util.MessageUtils.send(sender, plugin, "only_players"); - return true; - } - bankName = sender.getName(); - amountStr = args[0]; - currency = "dollar"; - } else { - bankName = args[0]; - amountStr = args[1]; - currency = args.length >= 3 ? args[2] : "dollar"; - } + if (args.length == 1) { + if (!(sender instanceof org.bukkit.entity.Player)) { + com.skyblockexp.ezeconomy.util.MessageUtils.send(sender, plugin, "only_players"); + return true; + } + bankName = sender.getName(); + amountStr = args[0]; + currency = plugin.getDefaultCurrency(); + } else { + bankName = args[0]; + amountStr = args[1]; + currency = args.length >= 3 ? args[2] : plugin.getDefaultCurrency(); + } Double amount = NumberUtil.parseDouble(amountStr); if (amount == null || amount <= 0) { com.skyblockexp.ezeconomy.util.MessageUtils.send(sender, plugin, "invalid_amount"); return true; } - EconomyResponse depositResponse = plugin.getEconomy().bankDeposit(bankName, currency, amount); - if (handleEconomyFailure(sender, depositResponse, bankName)) { - return true; - } + StorageProvider storage = plugin.getStorageOrWarn(); + if (storage == null) return true; + + boolean walletDebited = false; + org.bukkit.entity.Player playerSender = sender instanceof org.bukkit.entity.Player ? (org.bukkit.entity.Player) sender : null; + if (playerSender != null) { + walletDebited = storage.tryWithdraw(playerSender.getUniqueId(), currency, amount); + if (!walletDebited) { + com.skyblockexp.ezeconomy.util.MessageUtils.send(sender, plugin, "not_enough_money"); + return true; + } + } + + EconomyResponse depositResponse = plugin.getEconomy().bankDeposit(bankName, currency, amount); + if (handleEconomyFailure(sender, depositResponse, bankName)) { + if (walletDebited && playerSender != null) { + storage.deposit(playerSender.getUniqueId(), currency, amount); + } + return true; + } String formattedAmount = plugin.getCurrencyFormatter().formatPriceForMessage(amount, currency); java.util.HashMap placeholders = new java.util.HashMap<>(); placeholders.put("name", bankName); @@ -79,4 +96,4 @@ private boolean handleEconomyFailure(CommandSender sender, EconomyResponse respo } return false; } -} \ No newline at end of file +} diff --git a/src/main/java/com/skyblockexp/ezeconomy/command/bank/WithdrawSubcommand.java b/src/main/java/com/skyblockexp/ezeconomy/command/bank/WithdrawSubcommand.java index 6c593d3..1d2977d 100644 --- a/src/main/java/com/skyblockexp/ezeconomy/command/bank/WithdrawSubcommand.java +++ b/src/main/java/com/skyblockexp/ezeconomy/command/bank/WithdrawSubcommand.java @@ -1,10 +1,11 @@ package com.skyblockexp.ezeconomy.command.bank; -import com.skyblockexp.ezeconomy.command.Subcommand; -import com.skyblockexp.ezeconomy.core.EzEconomyPlugin; -import com.skyblockexp.ezeconomy.util.NumberUtil; -import net.milkbowl.vault.economy.EconomyResponse; -import org.bukkit.command.CommandSender; +import com.skyblockexp.ezeconomy.command.Subcommand; +import com.skyblockexp.ezeconomy.core.EzEconomyPlugin; +import com.skyblockexp.ezeconomy.util.NumberUtil; +import com.skyblockexp.ezeconomy.api.storage.StorageProvider; +import net.milkbowl.vault.economy.EconomyResponse; +import org.bukkit.command.CommandSender; /** * Subcommand for /bank withdraw [currency] @@ -33,30 +34,45 @@ public boolean execute(CommandSender sender, String[] args) { final String bankName; final String amountStr; final String currency; - if (args.length == 1) { - if (!(sender instanceof org.bukkit.entity.Player)) { - com.skyblockexp.ezeconomy.util.MessageUtils.send(sender, plugin, "only_players"); - return true; - } - bankName = sender.getName(); - amountStr = args[0]; - currency = "dollar"; - } else { - bankName = args[0]; - amountStr = args[1]; - currency = args.length >= 3 ? args[2] : "dollar"; - } + if (args.length == 1) { + if (!(sender instanceof org.bukkit.entity.Player)) { + com.skyblockexp.ezeconomy.util.MessageUtils.send(sender, plugin, "only_players"); + return true; + } + bankName = sender.getName(); + amountStr = args[0]; + currency = plugin.getDefaultCurrency(); + } else { + bankName = args[0]; + amountStr = args[1]; + currency = args.length >= 3 ? args[2] : plugin.getDefaultCurrency(); + } Double amount = NumberUtil.parseDouble(amountStr); if (amount == null || amount <= 0) { com.skyblockexp.ezeconomy.util.MessageUtils.send(sender, plugin, "invalid_amount"); return true; } - EconomyResponse withdrawResponse = plugin.getEconomy().bankWithdraw(bankName, currency, amount); - if (handleEconomyFailure(sender, withdrawResponse, bankName)) { - return true; - } - String formattedAmount = plugin.getCurrencyFormatter().formatPriceForMessage(amount, currency); + EconomyResponse withdrawResponse = plugin.getEconomy().bankWithdraw(bankName, currency, amount); + if (handleEconomyFailure(sender, withdrawResponse, bankName)) { + return true; + } + + StorageProvider storage = plugin.getStorageOrWarn(); + if (storage == null) return true; + org.bukkit.entity.Player playerSender = sender instanceof org.bukkit.entity.Player ? (org.bukkit.entity.Player) sender : null; + if (playerSender != null) { + try { + storage.deposit(playerSender.getUniqueId(), currency, amount); + } catch (Exception ex) { + // Roll back the bank withdrawal if wallet credit fails unexpectedly. + storage.depositBank(bankName, currency, amount); + sender.sendMessage(com.skyblockexp.ezeconomy.util.MessageUtils.color(plugin, "Bank operation failed.")); + return true; + } + } + + String formattedAmount = plugin.getCurrencyFormatter().formatPriceForMessage(amount, currency); java.util.HashMap placeholders = new java.util.HashMap<>(); placeholders.put("name", bankName); placeholders.put("amount", formattedAmount); @@ -81,4 +97,4 @@ private boolean handleEconomyFailure(CommandSender sender, EconomyResponse respo } return false; } -} \ No newline at end of file +} diff --git a/src/test/java/com/skyblockexp/ezeconomy/feature/BankCommandFeatureTest.java b/src/test/java/com/skyblockexp/ezeconomy/feature/BankCommandFeatureTest.java index 26a20fd..ceb1dd7 100644 --- a/src/test/java/com/skyblockexp/ezeconomy/feature/BankCommandFeatureTest.java +++ b/src/test/java/com/skyblockexp/ezeconomy/feature/BankCommandFeatureTest.java @@ -52,6 +52,8 @@ public void testBankDepositWithdraw_ownerAndMemberFlow() throws Exception { // use shared MockStorage to back bank operations MockStorage storage = new MockStorage(); TestSupport.injectField(plugin, "storage", storage); + storage.setBalance(owner.getUniqueId(), plugin.getDefaultCurrency(), 200.0); + storage.setBalance(member.getUniqueId(), plugin.getDefaultCurrency(), 0.0); // owner creates bank boolean create = owner.performCommand("bank create mybank"); @@ -64,6 +66,7 @@ public void testBankDepositWithdraw_ownerAndMemberFlow() throws Exception { // check bank balance via storage double bal = storage.getBankBalance("mybank", plugin.getDefaultCurrency()); assertEquals(100.0, bal, 0.001); + assertEquals(100.0, storage.getBalance(owner.getUniqueId(), plugin.getDefaultCurrency()), 0.001); // add member and withdraw boolean added = owner.performCommand("bank addmember mybank member"); @@ -75,6 +78,7 @@ public void testBankDepositWithdraw_ownerAndMemberFlow() throws Exception { double bal2 = storage.getBankBalance("mybank", plugin.getDefaultCurrency()); assertEquals(75.0, bal2, 0.001); + assertEquals(25.0, storage.getBalance(member.getUniqueId(), plugin.getDefaultCurrency()), 0.001); } @Test @@ -89,6 +93,7 @@ public void testBank_withYMLStorage_persistsBankBalances() throws Exception { com.skyblockexp.ezeconomy.storage.YMLStorageProvider yml = new com.skyblockexp.ezeconomy.storage.YMLStorageProvider(plugin, cfg); TestSupport.injectField(plugin, "storage", yml); + yml.setBalance(owner.getUniqueId(), plugin.getDefaultCurrency(), 400.0); boolean create = owner.performCommand("bank create ybank"); assertTrue(create); @@ -98,6 +103,7 @@ public void testBank_withYMLStorage_persistsBankBalances() throws Exception { double bal = yml.getBankBalance("ybank", plugin.getDefaultCurrency()); org.junit.jupiter.api.Assertions.assertEquals(200.0, bal, 0.001); + org.junit.jupiter.api.Assertions.assertEquals(200.0, yml.getBalance(owner.getUniqueId(), plugin.getDefaultCurrency()), 0.001); TestSupport.cleanupTestDataFolder(plugin, "test-bank-yml"); }