Merge pull request #172 from simoleo89/fix/marketplace-claimed-payout

fix(marketplace): only pay out claimed offers after detach
This commit is contained in:
DuckieTM
2026-06-15 07:19:10 +02:00
committed by GitHub
2 changed files with 47 additions and 7 deletions
@@ -398,11 +398,12 @@ public class MarketPlace {
synchronized (client.getHabbo().getInventory()) {
for (MarketPlaceOffer offer : offers) {
if (offer.getState().equals(MarketPlaceState.SOLD)) {
client.getHabbo().getInventory().removeMarketplaceOffer(offer);
credits += offer.getPrice();
removeUser(offer);
offer.needsUpdate(true);
Emulator.getThreading().run(offer);
if (removeUser(offer)) {
client.getHabbo().getInventory().removeMarketplaceOffer(offer);
credits += offer.getPrice();
offer.needsUpdate(true);
Emulator.getThreading().run(offer);
}
}
}
}
@@ -416,13 +417,14 @@ public class MarketPlace {
}
}
private static void removeUser(MarketPlaceOffer offer) {
private static boolean removeUser(MarketPlaceOffer offer) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE marketplace_items SET user_id = ? WHERE id = ?")) {
statement.setInt(1, -1);
statement.setInt(2, offer.getOfferId());
statement.execute();
return statement.executeUpdate() > 0;
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
return false;
}
}
@@ -0,0 +1,38 @@
package com.eu.habbo.habbohotel.catalog.marketplace;
import org.junit.jupiter.api.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertTrue;
class MarketPlaceCreditClaimContractTest {
private static String marketPlaceSource() throws Exception {
return Files.readString(Path.of("src/main/java/com/eu/habbo/habbohotel/catalog/marketplace/MarketPlace.java"));
}
@Test
void soldOfferIsDetachedBeforeCreditsAreGranted() throws Exception {
String source = marketPlaceSource();
int getCreditsStart = source.indexOf("public static void getCredits");
int removeUserCall = source.indexOf("removeUser(offer)", getCreditsStart);
int creditAccumulator = source.indexOf("credits += offer.getPrice()", getCreditsStart);
int inventoryRemoval = source.indexOf("removeMarketplaceOffer(offer)", getCreditsStart);
assertTrue(getCreditsStart > -1, "MarketPlace.getCredits must exist");
assertTrue(removeUserCall > -1, "Sold marketplace offers must be detached in the database");
assertTrue(removeUserCall < creditAccumulator,
"Credits must not be granted until the sold offer is detached from the seller in the database");
assertTrue(removeUserCall < inventoryRemoval,
"The in-memory sold offer should remain claimable if the database detach fails");
}
@Test
void detachFailureIsObservableByCaller() throws Exception {
String source = marketPlaceSource();
assertTrue(source.contains("private static boolean removeUser"),
"removeUser must report whether the marketplace ownership update succeeded");
}
}