mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +01:00
Greenfield: Get Lightning invoices (#4180)
* Greenfield: Get Lightning invoices Matching the data added in btcpayserver/BTCPayServer.Lightning#98 and btcpayserver/BTCPayServer.Lightning#99. * Small adjustments Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
This commit is contained in:
parent
0286c72256
commit
6d7c11f1b1
11 changed files with 270 additions and 13 deletions
|
@ -95,6 +95,24 @@ namespace BTCPayServer.Client
|
|||
method: HttpMethod.Get), token);
|
||||
return await HandleResponse<LightningInvoiceData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LightningInvoiceData[]> GetLightningInvoices(string cryptoCode,
|
||||
bool? pendingOnly = null, long? offsetIndex = null, CancellationToken token = default)
|
||||
{
|
||||
var queryPayload = new Dictionary<string, object>();
|
||||
if (pendingOnly is bool v)
|
||||
{
|
||||
queryPayload.Add("pendingOnly", v.ToString());
|
||||
}
|
||||
if (offsetIndex is > 0)
|
||||
{
|
||||
queryPayload.Add("offsetIndex", offsetIndex);
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/invoices", queryPayload), token);
|
||||
return await HandleResponse<LightningInvoiceData[]>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LightningInvoiceData> CreateLightningInvoice(string cryptoCode, CreateLightningInvoiceRequest request,
|
||||
CancellationToken token = default)
|
||||
|
|
|
@ -97,6 +97,24 @@ namespace BTCPayServer.Client
|
|||
method: HttpMethod.Get), token);
|
||||
return await HandleResponse<LightningInvoiceData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LightningInvoiceData[]> GetLightningInvoices(string storeId, string cryptoCode,
|
||||
bool? pendingOnly = null, long? offsetIndex = null, CancellationToken token = default)
|
||||
{
|
||||
var queryPayload = new Dictionary<string, object>();
|
||||
if (pendingOnly is bool v)
|
||||
{
|
||||
queryPayload.Add("pendingOnly", v.ToString());
|
||||
}
|
||||
if (offsetIndex is > 0)
|
||||
{
|
||||
queryPayload.Add("offsetIndex", offsetIndex);
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices", queryPayload), token);
|
||||
return await HandleResponse<LightningInvoiceData[]>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LightningInvoiceData> CreateLightningInvoice(string storeId, string cryptoCode,
|
||||
CreateLightningInvoiceRequest request, CancellationToken token = default)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.Lightning;
|
||||
using Newtonsoft.Json;
|
||||
|
@ -26,5 +27,8 @@ namespace BTCPayServer.Client.Models
|
|||
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney AmountReceived { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public Dictionary<ulong, string> CustomRecords { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1662,9 +1662,9 @@ namespace BTCPayServer.Tests
|
|||
var merchantClient = await merchant.CreateClient($"{Policies.CanUseLightningNodeInStore}:{merchant.StoreId}");
|
||||
var merchantInvoice = await merchantClient.CreateLightningInvoice(merchant.StoreId, "BTC", new CreateLightningInvoiceRequest(LightMoney.Satoshis(1_000), "hey", TimeSpan.FromSeconds(60)));
|
||||
// The default client is using charge, so we should not be able to query channels
|
||||
var client = await user.CreateClient(Policies.CanUseInternalLightningNode);
|
||||
var chargeClient = await user.CreateClient(Policies.CanUseInternalLightningNode);
|
||||
|
||||
var info = await client.GetLightningNodeInfo("BTC");
|
||||
var info = await chargeClient.GetLightningNodeInfo("BTC");
|
||||
Assert.Single(info.NodeURIs);
|
||||
Assert.NotEqual(0, info.BlockHeight);
|
||||
Assert.NotNull(info.Alias);
|
||||
|
@ -1675,10 +1675,10 @@ namespace BTCPayServer.Tests
|
|||
Assert.NotNull(info.InactiveChannelsCount);
|
||||
Assert.NotNull(info.PendingChannelsCount);
|
||||
|
||||
await AssertAPIError("lightning-node-unavailable", () => client.GetLightningNodeChannels("BTC"));
|
||||
await AssertAPIError("lightning-node-unavailable", () => chargeClient.GetLightningNodeChannels("BTC"));
|
||||
// Not permission for the store!
|
||||
await AssertAPIError("missing-permission", () => client.GetLightningNodeChannels(user.StoreId, "BTC"));
|
||||
var invoiceData = await client.CreateLightningInvoice("BTC", new CreateLightningInvoiceRequest()
|
||||
await AssertAPIError("missing-permission", () => chargeClient.GetLightningNodeChannels(user.StoreId, "BTC"));
|
||||
var invoiceData = await chargeClient.CreateLightningInvoice("BTC", new CreateLightningInvoiceRequest()
|
||||
{
|
||||
Amount = LightMoney.Satoshis(1000),
|
||||
Description = "lol",
|
||||
|
@ -1686,9 +1686,17 @@ namespace BTCPayServer.Tests
|
|||
PrivateRouteHints = false
|
||||
});
|
||||
var chargeInvoice = invoiceData;
|
||||
Assert.NotNull(await client.GetLightningInvoice("BTC", invoiceData.Id));
|
||||
Assert.NotNull(await chargeClient.GetLightningInvoice("BTC", invoiceData.Id));
|
||||
|
||||
client = await user.CreateClient($"{Policies.CanUseLightningNodeInStore}:{user.StoreId}");
|
||||
// check list for internal node
|
||||
var invoices = await chargeClient.GetLightningInvoices("BTC");
|
||||
var pendingInvoices = await chargeClient.GetLightningInvoices("BTC", true);
|
||||
Assert.NotEmpty(invoices);
|
||||
Assert.Contains(invoices, i => i.Id == invoiceData.Id);
|
||||
Assert.NotEmpty(pendingInvoices);
|
||||
Assert.Contains(pendingInvoices, i => i.Id == invoiceData.Id);
|
||||
|
||||
var client = await user.CreateClient($"{Policies.CanUseLightningNodeInStore}:{user.StoreId}");
|
||||
// Not permission for the server
|
||||
await AssertAPIError("missing-permission", () => client.GetLightningNodeChannels("BTC"));
|
||||
|
||||
|
@ -1706,6 +1714,11 @@ namespace BTCPayServer.Tests
|
|||
|
||||
Assert.NotNull(await client.GetLightningInvoice(user.StoreId, "BTC", invoiceData.Id));
|
||||
|
||||
// check pending list
|
||||
var merchantPendingInvoices = await merchantClient.GetLightningInvoices(merchant.StoreId, "BTC", true);
|
||||
Assert.NotEmpty(merchantPendingInvoices);
|
||||
Assert.Contains(merchantPendingInvoices, i => i.Id == merchantInvoice.Id);
|
||||
|
||||
await client.PayLightningInvoice(user.StoreId, "BTC", new PayLightningInvoiceRequest()
|
||||
{
|
||||
BOLT11 = merchantInvoice.BOLT11
|
||||
|
@ -1726,6 +1739,15 @@ namespace BTCPayServer.Tests
|
|||
var invoice = await merchantClient.GetLightningInvoice(merchant.StoreId, "BTC", merchantInvoice.Id);
|
||||
Assert.NotNull(invoice.PaidAt);
|
||||
Assert.Equal(LightMoney.Satoshis(1000), invoice.Amount);
|
||||
|
||||
// check list for store with paid invoice
|
||||
var merchantInvoices = await merchantClient.GetLightningInvoices(merchant.StoreId, "BTC");
|
||||
merchantPendingInvoices = await merchantClient.GetLightningInvoices(merchant.StoreId, "BTC", true);
|
||||
Assert.NotEmpty(merchantInvoices);
|
||||
Assert.Empty(merchantPendingInvoices);
|
||||
// if the test ran too many times the invoice might be on a later page
|
||||
if (merchantInvoices.Length < 100) Assert.Contains(merchantInvoices, i => i.Id == merchantInvoice.Id);
|
||||
|
||||
// Amount received might be bigger because of internal implementation shit from lightning
|
||||
Assert.True(LightMoney.Satoshis(1000) <= invoice.AmountReceived);
|
||||
|
||||
|
@ -1733,7 +1755,6 @@ namespace BTCPayServer.Tests
|
|||
Assert.Single(info.NodeURIs);
|
||||
Assert.NotEqual(0, info.BlockHeight);
|
||||
|
||||
|
||||
// As admin, can use the internal node through our store.
|
||||
await user.MakeAdmin(true);
|
||||
await user.RegisterInternalLightningNodeAsync("BTC");
|
||||
|
@ -1743,7 +1764,7 @@ namespace BTCPayServer.Tests
|
|||
await AssertPermissionError("btcpay.server.canuseinternallightningnode", () => client.GetLightningNodeInfo(user.StoreId, "BTC"));
|
||||
// However, even as a guest, you should be able to create an invoice
|
||||
var guest = tester.NewAccount();
|
||||
guest.GrantAccess(false);
|
||||
await guest.GrantAccessAsync();
|
||||
await user.AddGuest(guest.UserId);
|
||||
client = await guest.CreateClient(Policies.CanCreateLightningInvoiceInStore);
|
||||
await client.CreateLightningInvoice(user.StoreId, "BTC", new CreateLightningInvoiceRequest()
|
||||
|
|
|
@ -101,6 +101,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
return base.GetInvoice(cryptoCode, id, cancellationToken);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseInternalLightningNode,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/server/lightning/{cryptoCode}/invoices")]
|
||||
public override Task<IActionResult> GetInvoices(string cryptoCode, [FromQuery] bool? pendingOnly, [FromQuery] long? offsetIndex, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return base.GetInvoices(cryptoCode, pendingOnly, offsetIndex, cancellationToken);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseInternalLightningNode,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/server/lightning/{cryptoCode}/invoices/pay")]
|
||||
|
|
|
@ -111,6 +111,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
return base.GetInvoice(cryptoCode, id, cancellationToken);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices")]
|
||||
public override Task<IActionResult> GetInvoices(string cryptoCode, [FromQuery] bool? pendingOnly, [FromQuery] long? offsetIndex, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return base.GetInvoices(cryptoCode, pendingOnly, offsetIndex, cancellationToken);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanCreateLightningInvoiceInStore,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices")]
|
||||
|
|
|
@ -246,6 +246,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
return inv == null ? this.CreateAPIError(404, "invoice-not-found", "Impossible to find a lightning invoice with this id") : Ok(ToModel(inv));
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> GetInvoices(string cryptoCode, [FromQuery] bool? pendingOnly, [FromQuery] long? offsetIndex, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var lightningClient = await GetLightningClient(cryptoCode, false);
|
||||
var param = new ListInvoicesParams { PendingOnly = pendingOnly, OffsetIndex = offsetIndex };
|
||||
var invoices = await lightningClient.ListInvoices(param, cancellationToken);
|
||||
return Ok(invoices.Select(ToModel));
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> CreateInvoice(string cryptoCode, CreateLightningInvoiceRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var lightningClient = await GetLightningClient(cryptoCode, false);
|
||||
|
@ -303,7 +311,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
|
||||
private LightningInvoiceData ToModel(LightningInvoice invoice)
|
||||
{
|
||||
return new LightningInvoiceData
|
||||
var data = new LightningInvoiceData
|
||||
{
|
||||
Amount = invoice.Amount,
|
||||
Id = invoice.Id,
|
||||
|
@ -313,6 +321,12 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
BOLT11 = invoice.BOLT11,
|
||||
ExpiresAt = invoice.ExpiresAt
|
||||
};
|
||||
|
||||
if (invoice.CustomRecords != null)
|
||||
{
|
||||
data.CustomRecords = invoice.CustomRecords;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private LightningPaymentData ToModel(LightningPayment payment)
|
||||
|
|
|
@ -351,7 +351,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LightningNodeBalanceData>(
|
||||
await GetController<GreenfieldStoreLightningNodeApiController>().GetBalance(cryptoCode));
|
||||
await GetController<GreenfieldStoreLightningNodeApiController>().GetBalance(cryptoCode, token));
|
||||
}
|
||||
|
||||
public override async Task ConnectToLightningNode(string storeId, string cryptoCode,
|
||||
|
@ -394,6 +394,13 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
await GetController<GreenfieldStoreLightningNodeApiController>().GetInvoice(cryptoCode, invoiceId, token));
|
||||
}
|
||||
|
||||
public override async Task<LightningInvoiceData[]> GetLightningInvoices(string storeId, string cryptoCode,
|
||||
bool? pendingOnly = null, long? offsetIndex = null, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LightningInvoiceData[]>(
|
||||
await GetController<GreenfieldStoreLightningNodeApiController>().GetInvoices(cryptoCode, pendingOnly, offsetIndex, token));
|
||||
}
|
||||
|
||||
public override async Task<LightningInvoiceData> CreateLightningInvoice(string storeId, string cryptoCode,
|
||||
CreateLightningInvoiceRequest request, CancellationToken token = default)
|
||||
{
|
||||
|
@ -455,6 +462,13 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
await GetController<GreenfieldInternalLightningNodeApiController>().GetInvoice(cryptoCode, invoiceId, token));
|
||||
}
|
||||
|
||||
public override async Task<LightningInvoiceData[]> GetLightningInvoices(string cryptoCode,
|
||||
bool? pendingOnly = null, long? offsetIndex = null, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LightningInvoiceData[]>(
|
||||
await GetController<GreenfieldInternalLightningNodeApiController>().GetInvoices(cryptoCode, pendingOnly, offsetIndex, token));
|
||||
}
|
||||
|
||||
public override async Task<LightningInvoiceData> CreateLightningInvoice(string cryptoCode,
|
||||
CreateLightningInvoiceRequest request,
|
||||
CancellationToken token = default)
|
||||
|
|
|
@ -148,6 +148,11 @@
|
|||
"amountReceived": {
|
||||
"type": "string",
|
||||
"description": "The amount received in millisatoshi"
|
||||
},
|
||||
"customRecords": {
|
||||
"type": "object",
|
||||
"nullable": true,
|
||||
"description": "The custom TLV records attached to a keysend payment"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -389,7 +389,6 @@
|
|||
}
|
||||
},
|
||||
|
||||
|
||||
"/api/v1/server/lightning/{cryptoCode}/invoices/{id}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
|
@ -406,7 +405,8 @@
|
|||
"type": "string"
|
||||
},
|
||||
"example": "BTC"
|
||||
} , {
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
|
@ -446,6 +446,7 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
|
||||
"/api/v1/server/lightning/{cryptoCode}/invoices/pay": {
|
||||
"post": {
|
||||
"tags": [
|
||||
|
@ -524,7 +525,76 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
|
||||
"/api/v1/server/lightning/{cryptoCode}/invoices": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Lightning (Internal Node)"
|
||||
],
|
||||
"summary": "Get invoices",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "cryptoCode",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The cryptoCode of the lightning-node to query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": "BTC"
|
||||
},
|
||||
{
|
||||
"name": "pendingOnly",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Limit to pending invoices only",
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "offsetIndex",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "The index of an invoice that will be used as the start of the list",
|
||||
"schema": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"default": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "View information about the lightning invoices",
|
||||
"operationId": "InternalLightningNodeApi_GetInvoices",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Lightning invoice data",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/LightningInvoiceData"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"503": {
|
||||
"description": "Unable to access the lightning node"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"API_Key": [
|
||||
"btcpay.server.canuseinternallightningnode"
|
||||
],
|
||||
"Basic": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"Lightning (Internal Node)"
|
||||
|
|
|
@ -609,6 +609,83 @@
|
|||
}
|
||||
},
|
||||
"/api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Lightning (Store)"
|
||||
],
|
||||
"summary": "Get invoices",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "cryptoCode",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The cryptoCode of the lightning-node to query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": "BTC"
|
||||
},
|
||||
{
|
||||
"name": "storeId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The store id with the lightning-node configuration to query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "pendingOnly",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Limit to pending invoices only",
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "offsetIndex",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "The index of an invoice that will be used as the start of the list",
|
||||
"schema": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"default": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "View information about the lightning invoices",
|
||||
"operationId": "StoreLightningNodeApi_GetInvoices",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Lightning invoice data",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/LightningInvoiceData"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"503": {
|
||||
"description": "Unable to access the lightning node"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"API_Key": [
|
||||
"btcpay.store.cancreatelightninginvoice"
|
||||
],
|
||||
"Basic": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"Lightning (Store)"
|
||||
|
|
Loading…
Add table
Reference in a new issue