using BTCPayServer.Lightning; using LNURL; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using Nostr.Client.Json; using Nostr.Client.Messages; using NostrStreamer.Database; using NostrStreamer.Services; namespace NostrStreamer.Controllers; [Route("/api/pay")] public class LnurlController : Controller { private readonly Config _config; private readonly UserService _userService; public LnurlController(Config config, UserService userService) { _config = config; _userService = userService; } [HttpGet("/.well-known/lnurlp/{key}")] public async Task GetPayService([FromRoute] string key) { var user = await _userService.GetUser(key); if (user == default) return LnurlError("User not found"); var metadata = GetMetadata(user); var pubKey = _config.GetPubKey(); return Json(new LNURLPayRequest { Callback = new Uri(_config.ApiHost, $"/api/pay/{key}"), Metadata = JsonConvert.SerializeObject(metadata), MinSendable = LightMoney.Satoshis(1), MaxSendable = LightMoney.Coins(1), Tag = "payRequest", NostrPubkey = pubKey }); } [HttpGet("{key}")] public async Task PayUserBalance([FromRoute] string key, [FromQuery] long amount, [FromQuery] string? nostr) { try { if (!string.IsNullOrEmpty(nostr)) { var ev = JsonConvert.DeserializeObject(nostr, NostrSerializer.Settings); if (ev?.Kind != NostrKind.ZapRequest || ev.Tags?.FindFirstTagValue("amount") != amount.ToString() || !ev.IsSignatureValid()) { throw new Exception("Invalid nostr event"); } } var invoice = await _userService.CreateTopup(key, (ulong)(amount / 1000), nostr); return Json(new LNURLPayRequest.LNURLPayRequestCallbackResponse { Pr = invoice }); } catch (Exception ex) { return LnurlError($"Failed to create invoice (${ex.Message})"); } } private List> GetMetadata(User u) { return new List>() { new("text/plain", $"Topup for {u.PubKey}") }; } private IActionResult LnurlError(string reason) { return Json(new LNUrlStatusResponse() { Reason = reason, Status = "ERROR" }); } }