Update for LNBitsV1
This commit is contained in:
parent
91f9eb5746
commit
1ad9cce30f
574
Orangemart.cs
574
Orangemart.cs
@ -9,7 +9,7 @@ using Oxide.Core.Libraries;
|
|||||||
|
|
||||||
namespace Oxide.Plugins
|
namespace Oxide.Plugins
|
||||||
{
|
{
|
||||||
[Info("Orangemart", "saulteafarmer", "0.3.0")]
|
[Info("Orangemart", "RustySats", "0.3.0")]
|
||||||
[Description("Allows players to buy and sell in-game units and VIP status using Bitcoin Lightning Network payments via LNbits")]
|
[Description("Allows players to buy and sell in-game units and VIP status using Bitcoin Lightning Network payments via LNbits")]
|
||||||
public class Orangemart : CovalencePlugin
|
public class Orangemart : CovalencePlugin
|
||||||
{
|
{
|
||||||
@ -101,13 +101,21 @@ namespace Oxide.Plugins
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Invoice and Payment Classes
|
// Invoice and Payment Classes
|
||||||
private class InvoiceResponse
|
private class InvoiceResponse
|
||||||
{
|
{
|
||||||
[JsonProperty("payment_request")]
|
[JsonProperty("bolt11")]
|
||||||
public string PaymentRequest { get; set; }
|
public string PaymentRequest { get; set; }
|
||||||
|
|
||||||
[JsonProperty("payment_hash")]
|
[JsonProperty("payment_hash")]
|
||||||
public string PaymentHash { get; set; }
|
public string PaymentHash { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NEW: Wrapper class for LNbits v1 responses
|
||||||
|
private class InvoiceResponseWrapper
|
||||||
|
{
|
||||||
|
[JsonProperty("data")]
|
||||||
|
public InvoiceResponse Data { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SellInvoiceLogEntry
|
private class SellInvoiceLogEntry
|
||||||
@ -434,6 +442,45 @@ namespace Oxide.Plugins
|
|||||||
return allItems;
|
return allItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsCurrencyItem(Item item)
|
||||||
|
{
|
||||||
|
return item.info.itemid == currencyItemID && (currencySkinID == 0 || item.skin == currencySkinID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryReserveCurrency(BasePlayer player, int amount)
|
||||||
|
{
|
||||||
|
var items = GetAllInventoryItems(player).Where(IsCurrencyItem).ToList();
|
||||||
|
int totalCurrency = items.Sum(item => item.amount);
|
||||||
|
|
||||||
|
if (totalCurrency < amount)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int remaining = amount;
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (item.amount > remaining)
|
||||||
|
{
|
||||||
|
item.UseItem(remaining);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
remaining -= item.amount;
|
||||||
|
item.Remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remaining <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void CheckPendingInvoices()
|
private void CheckPendingInvoices()
|
||||||
{
|
{
|
||||||
foreach (var invoice in pendingInvoices.ToList())
|
foreach (var invoice in pendingInvoices.ToList())
|
||||||
@ -927,293 +974,122 @@ namespace Oxide.Plugins
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendPayment(string bolt11, int satsAmount, Action<bool, string> callback)
|
// UPDATED: SendPayment now deserializes using the wrapper class
|
||||||
|
private void SendPayment(string bolt11, int satsAmount, Action<bool, string> callback)
|
||||||
|
{
|
||||||
|
// For outbound payments, LNbits expects only "out" and "bolt11"
|
||||||
|
string url = $"{config.BaseUrl}/api/v1/payments";
|
||||||
|
var requestBody = new
|
||||||
|
{
|
||||||
|
@out = true,
|
||||||
|
bolt11 = bolt11
|
||||||
|
};
|
||||||
|
string jsonBody = JsonConvert.SerializeObject(requestBody);
|
||||||
|
|
||||||
|
var headers = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "X-Api-Key", config.ApiKey },
|
||||||
|
{ "Content-Type", "application/json" }
|
||||||
|
};
|
||||||
|
|
||||||
|
MakeWebRequest(url, jsonBody, (code, response) =>
|
||||||
|
{
|
||||||
|
if (code != 200 && code != 201)
|
||||||
{
|
{
|
||||||
string url = $"{config.BaseUrl}/api/v1/payments";
|
PrintError($"Error processing payment: HTTP {code}");
|
||||||
var requestBody = new
|
callback(false, null);
|
||||||
{
|
return;
|
||||||
@out = true,
|
|
||||||
bolt11 = bolt11,
|
|
||||||
amount = satsAmount
|
|
||||||
};
|
|
||||||
string jsonBody = JsonConvert.SerializeObject(requestBody);
|
|
||||||
|
|
||||||
var headers = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{ "X-Api-Key", config.ApiKey },
|
|
||||||
{ "Content-Type", "application/json" }
|
|
||||||
};
|
|
||||||
|
|
||||||
MakeWebRequest(url, jsonBody, (code, response) =>
|
|
||||||
{
|
|
||||||
if (code != 200 && code != 201)
|
|
||||||
{
|
|
||||||
PrintError($"Error processing payment: HTTP {code}");
|
|
||||||
callback(false, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var paymentResponse = JsonConvert.DeserializeObject<Dictionary<string, object>>(response);
|
|
||||||
string paymentHash = paymentResponse.ContainsKey("payment_hash") ? paymentResponse["payment_hash"].ToString() : null;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(paymentHash))
|
|
||||||
{
|
|
||||||
callback(true, paymentHash);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PrintError("Payment hash (rhash) is missing or invalid in the response.");
|
|
||||||
callback(false, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
PrintError($"Exception occurred while parsing payment response: {ex.Message}");
|
|
||||||
callback(false, null);
|
|
||||||
}
|
|
||||||
}, RequestMethod.POST, headers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendInvoiceToDiscord(IPlayer player, string invoice, int amountSats, string memo)
|
try
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(config.DiscordWebhookUrl))
|
InvoiceResponse invoiceResponse = null;
|
||||||
|
// First, attempt to deserialize using the wrapper (if present)
|
||||||
|
try
|
||||||
{
|
{
|
||||||
PrintError("Discord webhook URL is not configured.");
|
var wrapper = JsonConvert.DeserializeObject<InvoiceResponseWrapper>(response);
|
||||||
return;
|
invoiceResponse = wrapper?.Data;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
// Fallback: try direct deserialization
|
||||||
|
if (invoiceResponse == null)
|
||||||
|
{
|
||||||
|
invoiceResponse = JsonConvert.DeserializeObject<InvoiceResponse>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
string qrCodeUrl = $"https://api.qrserver.com/v1/create-qr-code/?data={Uri.EscapeDataString(invoice)}&size=200x200";
|
string paymentHash = invoiceResponse != null ? invoiceResponse.PaymentHash : null;
|
||||||
|
|
||||||
var webhookPayload = new
|
if (!string.IsNullOrEmpty(paymentHash))
|
||||||
{
|
{
|
||||||
content = $"**{player.Name}**, please pay **{amountSats} sats** using the Lightning Network.",
|
callback(true, paymentHash);
|
||||||
embeds = new[]
|
|
||||||
{
|
|
||||||
new
|
|
||||||
{
|
|
||||||
title = "Payment Invoice",
|
|
||||||
description = $"{memo}\n\nPlease pay the following Lightning invoice to complete your purchase:\n\n```\n{invoice}\n```",
|
|
||||||
image = new
|
|
||||||
{
|
|
||||||
url = qrCodeUrl
|
|
||||||
},
|
|
||||||
fields = new[]
|
|
||||||
{
|
|
||||||
new { name = "Amount", value = $"{amountSats} sats", inline = true },
|
|
||||||
new { name = "Steam ID", value = GetPlayerId(player), inline = true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
string jsonPayload = JsonConvert.SerializeObject(webhookPayload);
|
|
||||||
|
|
||||||
MakeWebRequest(config.DiscordWebhookUrl, jsonPayload, (code, response) =>
|
|
||||||
{
|
|
||||||
if (code != 204)
|
|
||||||
{
|
|
||||||
PrintError($"Failed to send invoice to Discord webhook: HTTP {code}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Puts($"Invoice sent to Discord for player {GetPlayerId(player)}.");
|
|
||||||
}
|
|
||||||
}, RequestMethod.POST, new Dictionary<string, string> { { "Content-Type", "application/json" } });
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RewardPlayer(IPlayer player, int amount)
|
|
||||||
{
|
|
||||||
player.Reply($"You have successfully purchased {amount} {currencyName}!");
|
|
||||||
|
|
||||||
var basePlayer = player.Object as BasePlayer;
|
|
||||||
|
|
||||||
if (basePlayer != null)
|
|
||||||
{
|
|
||||||
var currencyItem = ItemManager.CreateByItemID(currencyItemID, amount);
|
|
||||||
if (currencyItem != null)
|
|
||||||
{
|
|
||||||
if (currencySkinID > 0)
|
|
||||||
{
|
|
||||||
currencyItem.skin = currencySkinID;
|
|
||||||
}
|
|
||||||
|
|
||||||
basePlayer.GiveItem(currencyItem);
|
|
||||||
Puts($"Gave {amount} {currencyName} (skinID: {currencySkinID}) to player {basePlayer.UserIDString}.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PrintError($"Failed to create {currencyName} item for player {basePlayer.UserIDString}.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PrintError($"Failed to find base player object for player {player.Id}.");
|
PrintError("Payment hash (rhash) is missing or invalid in the response.");
|
||||||
|
PrintWarning($"[SendPayment] Raw response: {response}");
|
||||||
|
callback(false, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
private void GrantVip(IPlayer player)
|
|
||||||
{
|
{
|
||||||
player.Reply("You have successfully purchased VIP status!");
|
PrintError($"Exception occurred while parsing payment response: {ex.Message}");
|
||||||
|
callback(false, null);
|
||||||
|
}
|
||||||
|
}, RequestMethod.POST, headers);
|
||||||
|
}
|
||||||
|
|
||||||
permission.AddUserGroup(player.Id, vipPermissionGroup);
|
|
||||||
|
|
||||||
Puts($"Player {GetPlayerId(player)} added to VIP group '{vipPermissionGroup}'.");
|
// UPDATED: CreateInvoice now deserializes using the wrapper class
|
||||||
|
private void CreateInvoice(int amountSats, string memo, Action<InvoiceResponse> callback)
|
||||||
|
{
|
||||||
|
string url = $"{config.BaseUrl}/api/v1/payments";
|
||||||
|
|
||||||
|
var requestBody = new
|
||||||
|
{
|
||||||
|
@out = false,
|
||||||
|
amount = amountSats,
|
||||||
|
memo = memo
|
||||||
|
};
|
||||||
|
string jsonBody = JsonConvert.SerializeObject(requestBody);
|
||||||
|
|
||||||
|
var headers = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "X-Api-Key", config.ApiKey },
|
||||||
|
{ "Content-Type", "application/json" }
|
||||||
|
};
|
||||||
|
|
||||||
|
MakeWebRequest(url, jsonBody, (code, response) =>
|
||||||
|
{
|
||||||
|
if (code != 200 && code != 201)
|
||||||
|
{
|
||||||
|
PrintError($"Error creating invoice: HTTP {code}");
|
||||||
|
callback(null);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryReserveCurrency(BasePlayer player, int amount)
|
if (string.IsNullOrEmpty(response))
|
||||||
{
|
{
|
||||||
var items = GetAllInventoryItems(player).Where(IsCurrencyItem).ToList();
|
PrintError("Empty response received when creating invoice.");
|
||||||
int totalCurrency = items.Sum(item => item.amount);
|
callback(null);
|
||||||
|
return;
|
||||||
if (totalCurrency < amount)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int remaining = amount;
|
|
||||||
|
|
||||||
foreach (var item in items)
|
|
||||||
{
|
|
||||||
if (item.amount > remaining)
|
|
||||||
{
|
|
||||||
item.UseItem(remaining);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
remaining -= item.amount;
|
|
||||||
item.Remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remaining <= 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReturnCurrency(BasePlayer player, int amount)
|
// Log the raw response for debugging purposes.
|
||||||
|
PrintWarning($"[CreateInvoice] Raw response: {response}");
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var returnedCurrency = ItemManager.CreateByItemID(currencyItemID, amount);
|
var invoiceResponse = JsonConvert.DeserializeObject<InvoiceResponse>(response);
|
||||||
if (returnedCurrency != null)
|
callback(invoiceResponse != null && !string.IsNullOrEmpty(invoiceResponse.PaymentHash) ? invoiceResponse : null);
|
||||||
{
|
|
||||||
if (currencySkinID > 0)
|
|
||||||
{
|
|
||||||
returnedCurrency.skin = currencySkinID;
|
|
||||||
}
|
|
||||||
returnedCurrency.MoveToContainer(player.inventory.containerMain);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PrintError($"Failed to create {currencyName} item to return to player {player.UserIDString}.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
private bool IsCurrencyItem(Item item)
|
|
||||||
{
|
{
|
||||||
return item.info.itemid == currencyItemID && (currencySkinID == 0 || item.skin == currencySkinID);
|
PrintError($"Failed to deserialize invoice response: {ex.Message}");
|
||||||
}
|
callback(null);
|
||||||
|
|
||||||
private void LogSellTransaction(SellInvoiceLogEntry logEntry)
|
|
||||||
{
|
|
||||||
var logs = LoadSellLogData();
|
|
||||||
logs.Add(logEntry);
|
|
||||||
SaveSellLogData(logs);
|
|
||||||
Puts($"[Orangemart] Logged sell transaction: {JsonConvert.SerializeObject(logEntry)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SellInvoiceLogEntry> LoadSellLogData()
|
|
||||||
{
|
|
||||||
var path = Path.Combine(Interface.Oxide.DataDirectory, SellLogFile);
|
|
||||||
return File.Exists(path)
|
|
||||||
? JsonConvert.DeserializeObject<List<SellInvoiceLogEntry>>(File.ReadAllText(path))
|
|
||||||
: new List<SellInvoiceLogEntry>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SaveSellLogData(List<SellInvoiceLogEntry> data)
|
|
||||||
{
|
|
||||||
var path = Path.Combine(Interface.Oxide.DataDirectory, SellLogFile);
|
|
||||||
var directory = Path.GetDirectoryName(path);
|
|
||||||
if (!Directory.Exists(directory))
|
|
||||||
Directory.CreateDirectory(directory);
|
|
||||||
|
|
||||||
File.WriteAllText(path, JsonConvert.SerializeObject(data, Formatting.Indented));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LogBuyInvoice(BuyInvoiceLogEntry logEntry)
|
|
||||||
{
|
|
||||||
var logPath = Path.Combine(Interface.Oxide.DataDirectory, BuyInvoiceLogFile);
|
|
||||||
var directory = Path.GetDirectoryName(logPath);
|
|
||||||
if (!Directory.Exists(directory))
|
|
||||||
Directory.CreateDirectory(directory);
|
|
||||||
|
|
||||||
List<BuyInvoiceLogEntry> invoiceLogs = File.Exists(logPath)
|
|
||||||
? JsonConvert.DeserializeObject<List<BuyInvoiceLogEntry>>(File.ReadAllText(logPath)) ?? new List<BuyInvoiceLogEntry>()
|
|
||||||
: new List<BuyInvoiceLogEntry>();
|
|
||||||
|
|
||||||
invoiceLogs.Add(logEntry);
|
|
||||||
File.WriteAllText(logPath, JsonConvert.SerializeObject(invoiceLogs, Formatting.Indented));
|
|
||||||
Puts($"[Orangemart] Logged buy invoice: {JsonConvert.SerializeObject(logEntry)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private BuyInvoiceLogEntry CreateBuyInvoiceLogEntry(IPlayer player, string invoiceID, bool isPaid, int amount, PurchaseType type, int retryCount)
|
|
||||||
{
|
|
||||||
return new BuyInvoiceLogEntry
|
|
||||||
{
|
|
||||||
SteamID = GetPlayerId(player),
|
|
||||||
InvoiceID = invoiceID,
|
|
||||||
IsPaid = isPaid,
|
|
||||||
Timestamp = DateTime.UtcNow,
|
|
||||||
Amount = type == PurchaseType.SendBitcoin ? amount : amount * pricePerCurrencyUnit,
|
|
||||||
CurrencyGiven = isPaid && type == PurchaseType.Currency,
|
|
||||||
VipGranted = isPaid && type == PurchaseType.Vip,
|
|
||||||
RetryCount = retryCount
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateInvoice(int amountSats, string memo, Action<InvoiceResponse> callback)
|
|
||||||
{
|
|
||||||
string url = $"{config.BaseUrl}/api/v1/payments";
|
|
||||||
|
|
||||||
var requestBody = new
|
|
||||||
{
|
|
||||||
@out = false,
|
|
||||||
amount = amountSats,
|
|
||||||
memo = memo
|
|
||||||
};
|
|
||||||
string jsonBody = JsonConvert.SerializeObject(requestBody);
|
|
||||||
|
|
||||||
var headers = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{ "X-Api-Key", config.ApiKey },
|
|
||||||
{ "Content-Type", "application/json" }
|
|
||||||
};
|
|
||||||
|
|
||||||
MakeWebRequest(url, jsonBody, (code, response) =>
|
|
||||||
{
|
|
||||||
if (code != 200 && code != 201)
|
|
||||||
{
|
|
||||||
PrintError($"Error creating invoice: HTTP {code}");
|
|
||||||
callback(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var invoiceResponse = JsonConvert.DeserializeObject<InvoiceResponse>(response);
|
|
||||||
callback(invoiceResponse != null && !string.IsNullOrEmpty(invoiceResponse.PaymentHash) ? invoiceResponse : null);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
PrintError($"Failed to deserialize invoice response: {ex.Message}");
|
|
||||||
callback(null);
|
|
||||||
}
|
|
||||||
}, RequestMethod.POST, headers);
|
|
||||||
}
|
}
|
||||||
|
}, RequestMethod.POST, headers);
|
||||||
|
}
|
||||||
|
|
||||||
private string GetPlayerId(IPlayer player)
|
private string GetPlayerId(IPlayer player)
|
||||||
{
|
{
|
||||||
@ -1356,10 +1232,170 @@ namespace Oxide.Plugins
|
|||||||
|
|
||||||
private string ExtractLightningAddress(string memo)
|
private string ExtractLightningAddress(string memo)
|
||||||
{
|
{
|
||||||
// Extract the Lightning Address from the memo
|
|
||||||
// Expected format: "Sending {amount} {currency} to {lightning_address}"
|
// Expected format: "Sending {amount} {currency} to {lightning_address}"
|
||||||
var parts = memo.Split(" to ");
|
var parts = memo.Split(" to ");
|
||||||
return parts.Length == 2 ? parts[1] : "unknown@unknown.com";
|
return parts.Length == 2 ? parts[1] : "unknown@unknown.com";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NEW: RewardPlayer method to grant currency items to the player
|
||||||
|
private void RewardPlayer(IPlayer player, int amount)
|
||||||
|
{
|
||||||
|
player.Reply($"You have successfully purchased {amount} {currencyName}!");
|
||||||
|
|
||||||
|
var basePlayer = player.Object as BasePlayer;
|
||||||
|
if (basePlayer != null)
|
||||||
|
{
|
||||||
|
var currencyItem = ItemManager.CreateByItemID(currencyItemID, amount);
|
||||||
|
if (currencyItem != null)
|
||||||
|
{
|
||||||
|
if (currencySkinID > 0)
|
||||||
|
{
|
||||||
|
currencyItem.skin = currencySkinID;
|
||||||
|
}
|
||||||
|
basePlayer.GiveItem(currencyItem);
|
||||||
|
Puts($"Gave {amount} {currencyName} (skinID: {currencySkinID}) to player {basePlayer.UserIDString}.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PrintError($"Failed to create {currencyName} item for player {basePlayer.UserIDString}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PrintError($"Failed to find base player object for player {player.Id}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NEW: GrantVip method to add the VIP permission group to the player
|
||||||
|
private void GrantVip(IPlayer player)
|
||||||
|
{
|
||||||
|
player.Reply("You have successfully purchased VIP status!");
|
||||||
|
permission.AddUserGroup(player.Id, vipPermissionGroup);
|
||||||
|
Puts($"Player {GetPlayerId(player)} added to VIP group '{vipPermissionGroup}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// NEW: ReturnCurrency method to refund currency items to the player
|
||||||
|
private void ReturnCurrency(BasePlayer player, int amount)
|
||||||
|
{
|
||||||
|
var returnedCurrency = ItemManager.CreateByItemID(currencyItemID, amount);
|
||||||
|
if (returnedCurrency != null)
|
||||||
|
{
|
||||||
|
if (currencySkinID > 0)
|
||||||
|
{
|
||||||
|
returnedCurrency.skin = currencySkinID;
|
||||||
|
}
|
||||||
|
returnedCurrency.MoveToContainer(player.inventory.containerMain);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PrintError($"Failed to create {currencyName} item to return to player {player.UserIDString}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NEW: LogSellTransaction helper
|
||||||
|
private void LogSellTransaction(SellInvoiceLogEntry logEntry)
|
||||||
|
{
|
||||||
|
var logs = LoadSellLogData();
|
||||||
|
logs.Add(logEntry);
|
||||||
|
SaveSellLogData(logs);
|
||||||
|
Puts($"[Orangemart] Logged sell transaction: {JsonConvert.SerializeObject(logEntry)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SellInvoiceLogEntry> LoadSellLogData()
|
||||||
|
{
|
||||||
|
var path = Path.Combine(Interface.Oxide.DataDirectory, SellLogFile);
|
||||||
|
return File.Exists(path)
|
||||||
|
? JsonConvert.DeserializeObject<List<SellInvoiceLogEntry>>(File.ReadAllText(path))
|
||||||
|
: new List<SellInvoiceLogEntry>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveSellLogData(List<SellInvoiceLogEntry> data)
|
||||||
|
{
|
||||||
|
var path = Path.Combine(Interface.Oxide.DataDirectory, SellLogFile);
|
||||||
|
var directory = Path.GetDirectoryName(path);
|
||||||
|
if (!Directory.Exists(directory))
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
|
||||||
|
File.WriteAllText(path, JsonConvert.SerializeObject(data, Formatting.Indented));
|
||||||
|
}
|
||||||
|
|
||||||
|
// NEW: LogBuyInvoice helper
|
||||||
|
private void LogBuyInvoice(BuyInvoiceLogEntry logEntry)
|
||||||
|
{
|
||||||
|
var logPath = Path.Combine(Interface.Oxide.DataDirectory, BuyInvoiceLogFile);
|
||||||
|
var directory = Path.GetDirectoryName(logPath);
|
||||||
|
if (!Directory.Exists(directory))
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
|
||||||
|
List<BuyInvoiceLogEntry> invoiceLogs = File.Exists(logPath)
|
||||||
|
? JsonConvert.DeserializeObject<List<BuyInvoiceLogEntry>>(File.ReadAllText(logPath)) ?? new List<BuyInvoiceLogEntry>()
|
||||||
|
: new List<BuyInvoiceLogEntry>();
|
||||||
|
|
||||||
|
invoiceLogs.Add(logEntry);
|
||||||
|
File.WriteAllText(logPath, JsonConvert.SerializeObject(invoiceLogs, Formatting.Indented));
|
||||||
|
Puts($"[Orangemart] Logged buy invoice: {JsonConvert.SerializeObject(logEntry)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private BuyInvoiceLogEntry CreateBuyInvoiceLogEntry(IPlayer player, string invoiceID, bool isPaid, int amount, PurchaseType type, int retryCount)
|
||||||
|
{
|
||||||
|
return new BuyInvoiceLogEntry
|
||||||
|
{
|
||||||
|
SteamID = GetPlayerId(player),
|
||||||
|
InvoiceID = invoiceID,
|
||||||
|
IsPaid = isPaid,
|
||||||
|
Timestamp = DateTime.UtcNow,
|
||||||
|
Amount = type == PurchaseType.SendBitcoin ? amount : amount * pricePerCurrencyUnit,
|
||||||
|
CurrencyGiven = isPaid && type == PurchaseType.Currency,
|
||||||
|
VipGranted = isPaid && type == PurchaseType.Vip,
|
||||||
|
RetryCount = retryCount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendInvoiceToDiscord(IPlayer player, string invoice, int amountSats, string memo)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(config.DiscordWebhookUrl))
|
||||||
|
{
|
||||||
|
PrintError("Discord webhook URL is not configured.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string qrCodeUrl = $"https://api.qrserver.com/v1/create-qr-code/?data={Uri.EscapeDataString(invoice)}&size=200x200";
|
||||||
|
|
||||||
|
var webhookPayload = new
|
||||||
|
{
|
||||||
|
content = $"**{player.Name}**, please pay **{amountSats} sats** using the Lightning Network.",
|
||||||
|
embeds = new[]
|
||||||
|
{
|
||||||
|
new
|
||||||
|
{
|
||||||
|
title = "Payment Invoice",
|
||||||
|
description = $"{memo}\n\nPlease pay the following Lightning invoice to complete your purchase:\n\n```\n{invoice}\n```",
|
||||||
|
image = new
|
||||||
|
{
|
||||||
|
url = qrCodeUrl
|
||||||
|
},
|
||||||
|
fields = new[]
|
||||||
|
{
|
||||||
|
new { name = "Amount", value = $"{amountSats} sats", inline = true },
|
||||||
|
new { name = "Steam ID", value = GetPlayerId(player), inline = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
string jsonPayload = JsonConvert.SerializeObject(webhookPayload);
|
||||||
|
|
||||||
|
MakeWebRequest(config.DiscordWebhookUrl, jsonPayload, (code, response) =>
|
||||||
|
{
|
||||||
|
if (code != 204)
|
||||||
|
{
|
||||||
|
PrintError($"Failed to send invoice to Discord webhook: HTTP {code}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Puts($"Invoice sent to Discord for player {GetPlayerId(player)}.");
|
||||||
|
}
|
||||||
|
}, RequestMethod.POST, new Dictionary<string, string> { { "Content-Type", "application/json" } });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user