diff --git a/src/main/kotlin/main/vp_server_integration/Vp_server_integration.kt b/src/main/kotlin/main/vp_server_integration/Vp_server_integration.kt index 440384e..847fe24 100644 --- a/src/main/kotlin/main/vp_server_integration/Vp_server_integration.kt +++ b/src/main/kotlin/main/vp_server_integration/Vp_server_integration.kt @@ -22,6 +22,7 @@ import java.io.FileInputStream import java.util.Properties import kotlin.collections.mutableMapOf import kotlin.math.abs +import java.util.Date class Vp_server_integration() : JavaPlugin(), CommandExecutor { companion object { @@ -38,6 +39,7 @@ class Vp_server_integration() : JavaPlugin(), CommandExecutor { var COURSE_STATIC_VALUE: Double = DEFAULT_COURSE_STATIC_VALUE lateinit var COURSE_DYNAMIC_COMMAND: String var COURSE_COMMISSION: Float = DEFAULT_COURSE_COMMISSION + var INVOICE_TIMEOUT_SECONDS: Long = DEFAULT_INVOICE_TIMEOUT_SECONDS // Default configuration values const val DEFAULT_USERNAME: String = "test" @@ -51,6 +53,7 @@ class Vp_server_integration() : JavaPlugin(), CommandExecutor { const val DEFAULT_COURSE_STATIC_VALUE: Double = 1000.0 const val DEFAULT_COURSE_DYNAMIC_COMMAND: String = "baltop force" const val DEFAULT_COURSE_COMMISSION: Float = 5.0f + const val DEFAULT_INVOICE_TIMEOUT_SECONDS: Long = 300 // 5 minutes const val PREFIX = "&9[&bVPC&7-&6I&9] &3" @@ -67,6 +70,10 @@ class Vp_server_integration() : JavaPlugin(), CommandExecutor { var TO_PAY_INVOICES: MutableMap = mutableMapOf() // Pair: {player: invoice_id} @JvmStatic var INVOICES_AMOUNT: MutableMap = mutableMapOf() // How many should we pay to player after invoice? + + // Track invoice creation times for timeout handling + @JvmStatic + var INVOICE_CREATION_TIMES: MutableMap = mutableMapOf() // Pair: {invoice_id: creation_timestamp} } /** @@ -102,6 +109,7 @@ class Vp_server_integration() : JavaPlugin(), CommandExecutor { COURSE_STATIC_VALUE = properties.getProperty("course_static_value", DEFAULT_COURSE_STATIC_VALUE.toString()).replace("'", "").toDouble() COURSE_DYNAMIC_COMMAND = properties.getProperty("course_dynamic_command", DEFAULT_COURSE_DYNAMIC_COMMAND).replace("'", "") COURSE_COMMISSION = properties.getProperty("course_commission", DEFAULT_COURSE_COMMISSION.toString()).replace("'", "").toFloat() + INVOICE_TIMEOUT_SECONDS = properties.getProperty("invoice_timeout_seconds", DEFAULT_INVOICE_TIMEOUT_SECONDS.toString()).replace("'", "").toLong() logger.info("Configuration loaded successfully - Username: $USERNAME, API URL: $USER_API_URL") } catch (e: Exception) { @@ -163,6 +171,11 @@ course_dynamic_command='$DEFAULT_COURSE_DYNAMIC_COMMAND' # For dynamic course recommended 5% and higher (avoid dupe), for static course you can set 0% course_commission=$DEFAULT_COURSE_COMMISSION # -------------------- END ------------------------ + +# --------- Invoice timeout configuration ---------- +# After how many seconds unpaid invoices will be deleted (default: 300 seconds = 5 minutes) +invoice_timeout_seconds=$DEFAULT_INVOICE_TIMEOUT_SECONDS +# -------------------- END ------------------------ """.trimIndent() ) } @@ -381,6 +394,7 @@ course_commission=$DEFAULT_COURSE_COMMISSION val invoiceId = VpcApi.create_invoice(amount).toString() TO_PAY_INVOICES[sender.name] = invoiceId INVOICES_AMOUNT[invoiceId] = amountLC + INVOICE_CREATION_TIMES[invoiceId] = System.currentTimeMillis() sendPaymentPrompt(sender, amount, amountLC, invoiceId) } @@ -437,6 +451,7 @@ course_commission=$DEFAULT_COURSE_COMMISSION synchronized(TO_AUTH_PLAYERS) { TO_AUTH_PLAYERS[sender.name] = vpcUsernameInput TO_AUTH_PLAYERS_INVOICES[sender.name] = invoiceId.toString() + INVOICE_CREATION_TIMES[invoiceId.toString()] = System.currentTimeMillis() } LOGGER.info("Authentication lists updated") @@ -502,6 +517,7 @@ course_commission=$DEFAULT_COURSE_COMMISSION override fun run() { processAuthenticationInvoices() processPaymentInvoices() + cleanupExpiredInvoices() } }.runTaskTimerAsynchronously(this, 0L, 20L) } @@ -531,6 +547,7 @@ course_commission=$DEFAULT_COURSE_COMMISSION // Clean up tracking maps TO_AUTH_PLAYERS.remove(entry.key) + INVOICE_CREATION_TIMES.remove(entry.value) iterator.remove() } } else { @@ -583,6 +600,7 @@ course_commission=$DEFAULT_COURSE_COMMISSION // Clean up tracking maps INVOICES_AMOUNT.remove(entry.key) + INVOICE_CREATION_TIMES.remove(entry.value) iterator.remove() } } else { @@ -596,4 +614,72 @@ course_commission=$DEFAULT_COURSE_COMMISSION } } } -} + + /** + * Cleanup expired invoices based on timeout configuration + */ + private fun cleanupExpiredInvoices() { + val currentTime = System.currentTimeMillis() + val timeoutMillis = INVOICE_TIMEOUT_SECONDS * 1000 + + // Cleanup auth invoices + synchronized(TO_AUTH_PLAYERS_INVOICES) { + val authIterator = TO_AUTH_PLAYERS_INVOICES.iterator() + while (authIterator.hasNext()) { + val entry = authIterator.next() + val invoiceId = entry.value + val creationTime = INVOICE_CREATION_TIMES[invoiceId] ?: continue + + if (currentTime - creationTime > timeoutMillis) { + LOGGER.info("Deleting expired auth invoice: $invoiceId") + try { + VpcApi.delete_invoice(invoiceId) + } catch (e: Exception) { + LOGGER.error("Error deleting expired auth invoice $invoiceId: ${e.message}") + } + + // Clean up tracking maps + TO_AUTH_PLAYERS.remove(entry.key) + INVOICE_CREATION_TIMES.remove(invoiceId) + authIterator.remove() + + // Notify player if online + val player = Bukkit.getPlayer(entry.key) + if (player != null && player.isOnline) { + Utils.send(player, "&cВаша авторизационная оплата истекла. Пожалуйста, попробуйте снова: /vpi auth <ник>") + } + } + } + } + + // Cleanup payment invoices + synchronized(TO_PAY_INVOICES) { + val paymentIterator = TO_PAY_INVOICES.iterator() + while (paymentIterator.hasNext()) { + val entry = paymentIterator.next() + val invoiceId = entry.value + val creationTime = INVOICE_CREATION_TIMES[invoiceId] ?: continue + + if (currentTime - creationTime > timeoutMillis) { + LOGGER.info("Deleting expired payment invoice: $invoiceId") + try { + VpcApi.delete_invoice(invoiceId) + } catch (e: Exception) { + LOGGER.error("Error deleting expired payment invoice $invoiceId: ${e.message}") + } + + // Clean up tracking maps + INVOICES_AMOUNT.remove(invoiceId) + INVOICE_CREATION_TIMES.remove(invoiceId) + paymentIterator.remove() + + // Notify player if online + val player = Bukkit.getPlayer(entry.key) + if (player != null && player.isOnline) { + Utils.send(player, "&cВаша оплата истекла. Пожалуйста, создайте новую: /vpi convert lc <сумма>") + } + } + } + } + } +} \ No newline at end of file