Add Greenfield API endpoint for pull payment LNURL items (#4472)

* Add Greenfield API endpoint for pull payment LNURL items

close #4365

* Rename GetLNURLs to GetPullPaymentLNURL

* update "ln-url-not-supported" to "lnurl-not-supported"

* remove hardcoding of "BTC"

* update "PullPayments_LNURL" to "PullPayments_GetPullPaymentLNURL"

* update description of 400 status code response

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
This commit is contained in:
Umar Bolatov 2023-01-25 21:43:07 -08:00 committed by GitHub
parent de4ac2c830
commit 438dcc4c6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 126 additions and 3 deletions

View File

@ -97,5 +97,15 @@ namespace BTCPayServer.Client
method: HttpMethod.Post, bodyPayload: request), cancellationToken); method: HttpMethod.Post, bodyPayload: request), cancellationToken);
await HandleResponse(response); await HandleResponse(response);
} }
public virtual async Task<PullPaymentLNURL> GetPullPaymentLNURL(string pullPaymentId,
CancellationToken cancellationToken = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest(
$"/api/v1/pull-payments/{pullPaymentId}/lnurl",
method: HttpMethod.Get), cancellationToken);
return await HandleResponse<PullPaymentLNURL>(response);
}
} }
} }

View File

@ -0,0 +1,8 @@
namespace BTCPayServer.Client.Models
{
public class PullPaymentLNURL
{
public string LNURLBech32 { get; set; }
public string LNURLUri { get; set; }
}
}

View File

@ -672,10 +672,12 @@ namespace BTCPayServer.Tests
public async Task CanUsePullPaymentViaAPI() public async Task CanUsePullPaymentViaAPI()
{ {
using var tester = CreateServerTester(); using var tester = CreateServerTester();
tester.ActivateLightning();
await tester.StartAsync(); await tester.StartAsync();
await tester.EnsureChannelsSetup();
var acc = tester.NewAccount(); var acc = tester.NewAccount();
acc.Register(); await acc.GrantAccessAsync(true);
await acc.CreateStoreAsync(); acc.RegisterLightningNode("BTC", LightningConnectionType.CLightning, false);
var storeId = (await acc.RegisterDerivationSchemeAsync("BTC", importKeysToNBX: true)).StoreId; var storeId = (await acc.RegisterDerivationSchemeAsync("BTC", importKeysToNBX: true)).StoreId;
var client = await acc.CreateClient(); var client = await acc.CreateClient();
var result = await client.CreatePullPayment(storeId, new CreatePullPaymentRequest() var result = await client.CreatePullPayment(storeId, new CreatePullPaymentRequest()
@ -856,6 +858,8 @@ namespace BTCPayServer.Tests
PaymentMethods = new[] { "BTC" } PaymentMethods = new[] { "BTC" }
}); });
await this.AssertAPIError("lnurl-not-supported", async () => await unauthenticated.GetPullPaymentLNURL(pp.Id));
destination = (await tester.ExplorerNode.GetNewAddressAsync()).ToString(); destination = (await tester.ExplorerNode.GetNewAddressAsync()).ToString();
TestLogs.LogInformation("Try to pay it in BTC"); TestLogs.LogInformation("Try to pay it in BTC");
payout = await unauthenticated.CreatePayout(pp.Id, new CreatePayoutRequest() payout = await unauthenticated.CreatePayout(pp.Id, new CreatePayoutRequest()
@ -906,7 +910,18 @@ namespace BTCPayServer.Tests
payout = (await client.GetPayouts(payout.PullPaymentId)).First(data => data.Id == payout.Id); payout = (await client.GetPayouts(payout.PullPaymentId)).First(data => data.Id == payout.Id);
Assert.Equal(PayoutState.Completed, payout.State); Assert.Equal(PayoutState.Completed, payout.State);
await AssertAPIError("invalid-state", async () => await client.MarkPayoutPaid(storeId, payout.Id)); await AssertAPIError("invalid-state", async () => await client.MarkPayoutPaid(storeId, payout.Id));
// Test LNURL values
var test4 = await client.CreatePullPayment(storeId, new Client.Models.CreatePullPaymentRequest()
{
Name = "Test 3",
Amount = 12.303228134m,
Currency = "BTC",
PaymentMethods = new[] { "BTC", "BTC-LightningNetwork", "BTC_LightningLike" }
});
var lnrURLs = await unauthenticated.GetPullPaymentLNURL(test4.Id);
Assert.IsType<string>(lnrURLs.LNURLBech32);
Assert.IsType<string>(lnrURLs.LNURLUri);
//permission test around auto approved pps and payouts //permission test around auto approved pps and payouts
var nonApproved = await acc.CreateClient(Policies.CanCreateNonApprovedPullPayments); var nonApproved = await acc.CreateClient(Policies.CanCreateNonApprovedPullPayments);

View File

@ -34,6 +34,7 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly CurrencyNameTable _currencyNameTable; private readonly CurrencyNameTable _currencyNameTable;
private readonly BTCPayNetworkJsonSerializerSettings _serializerSettings; private readonly BTCPayNetworkJsonSerializerSettings _serializerSettings;
private readonly IEnumerable<IPayoutHandler> _payoutHandlers; private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
private readonly BTCPayNetworkProvider _networkProvider;
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
public GreenfieldPullPaymentController(PullPaymentHostedService pullPaymentService, public GreenfieldPullPaymentController(PullPaymentHostedService pullPaymentService,
@ -42,6 +43,7 @@ namespace BTCPayServer.Controllers.Greenfield
CurrencyNameTable currencyNameTable, CurrencyNameTable currencyNameTable,
Services.BTCPayNetworkJsonSerializerSettings serializerSettings, Services.BTCPayNetworkJsonSerializerSettings serializerSettings,
IEnumerable<IPayoutHandler> payoutHandlers, IEnumerable<IPayoutHandler> payoutHandlers,
BTCPayNetworkProvider btcPayNetworkProvider,
IAuthorizationService authorizationService) IAuthorizationService authorizationService)
{ {
_pullPaymentService = pullPaymentService; _pullPaymentService = pullPaymentService;
@ -50,6 +52,7 @@ namespace BTCPayServer.Controllers.Greenfield
_currencyNameTable = currencyNameTable; _currencyNameTable = currencyNameTable;
_serializerSettings = serializerSettings; _serializerSettings = serializerSettings;
_payoutHandlers = payoutHandlers; _payoutHandlers = payoutHandlers;
_networkProvider = btcPayNetworkProvider;
_authorizationService = authorizationService; _authorizationService = authorizationService;
} }
@ -243,6 +246,33 @@ namespace BTCPayServer.Controllers.Greenfield
return base.Ok(ToModel(payout)); return base.Ok(ToModel(payout));
} }
[HttpGet("~/api/v1/pull-payments/{pullPaymentId}/lnurl")]
[AllowAnonymous]
public async Task<IActionResult> GetPullPaymentLNURL(string pullPaymentId)
{
var pp = await _pullPaymentService.GetPullPayment(pullPaymentId, false);
if (pp is null)
return PullPaymentNotFound();
var blob = pp.GetBlob();
var pms = blob.SupportedPaymentMethods.FirstOrDefault(id => id.PaymentType == LightningPaymentType.Instance && _networkProvider.DefaultNetwork.CryptoCode == id.CryptoCode);
if (pms is not null && blob.Currency.Equals(pms.CryptoCode, StringComparison.InvariantCultureIgnoreCase))
{
var lnurlEndpoint = new Uri(Url.Action("GetLNURLForPullPayment", "UILNURL", new
{
cryptoCode = _networkProvider.DefaultNetwork.CryptoCode,
pullPaymentId = pullPaymentId
}, Request.Scheme, Request.Host.ToString()));
return base.Ok(new PullPaymentLNURL() {
LNURLBech32 = LNURL.LNURL.EncodeUri(lnurlEndpoint, "withdrawRequest", true).ToString(),
LNURLUri = LNURL.LNURL.EncodeUri(lnurlEndpoint, "withdrawRequest", false).ToString()
});
}
return this.CreateAPIError("lnurl-not-supported", "LNURL not supported for this pull payment");
}
private Client.Models.PayoutData ToModel(Data.PayoutData p) private Client.Models.PayoutData ToModel(Data.PayoutData p)
{ {
var blob = p.GetBlob(_serializerSettings); var blob = p.GetBlob(_serializerSettings);

View File

@ -1242,6 +1242,11 @@ namespace BTCPayServer.Controllers.Greenfield
return GetFromActionResult<PayoutData>(await GetController<GreenfieldPullPaymentController>().GetPayout(pullPaymentId, payoutId)); return GetFromActionResult<PayoutData>(await GetController<GreenfieldPullPaymentController>().GetPayout(pullPaymentId, payoutId));
} }
public override async Task<PullPaymentLNURL> GetPullPaymentLNURL(string pullPaymentId, CancellationToken cancellationToken = default)
{
return GetFromActionResult<PullPaymentLNURL>(await GetController<GreenfieldPullPaymentController>().GetPullPaymentLNURL(pullPaymentId));
}
public override async Task<PayoutData> GetStorePayout(string storeId, string payoutId, public override async Task<PayoutData> GetStorePayout(string storeId, string payoutId,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {

View File

@ -402,6 +402,46 @@
"security": [] "security": []
} }
}, },
"/api/v1/pull-payments/{pullPaymentId}/lnurl": {
"parameters": [
{
"name": "pullPaymentId",
"in": "path",
"required": true,
"description": "The ID of the pull payment",
"schema": {
"type": "string"
}
}
],
"get": {
"summary": "Get Pull Payment LNURL details",
"operationId": "PullPayments_GetPullPaymentLNURL",
"description": "Get Pull Payment LNURL details",
"responses": {
"200": {
"description": "Pull payment LNURL details",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LNURLData"
}
}
}
},
"404": {
"description": "Pull payment not found"
},
"400": {
"description": "Pull payment found but does not support LNURL"
}
},
"tags": [
"Pull payments (Public)"
],
"security": []
}
},
"/api/v1/stores/{storeId}/payouts": { "/api/v1/stores/{storeId}/payouts": {
"parameters": [ "parameters": [
{ {
@ -1028,6 +1068,21 @@
"description": "The link to a page to claim payouts to this pull payment" "description": "The link to a page to claim payouts to this pull payment"
} }
} }
},
"LNURLData": {
"type": "object",
"properties": {
"lnurlBech32": {
"type": "string",
"description": "Bech32 representation of LNRURL",
"example": "lightning:lnurl1dp68gup69uhnzv3h9cczuvpwxyarzdp3xsez7sj5gvh42j2vfe24ynp0wa5hg6rywfshwtmswqhngvntdd6x6uzvx4jrvu2kvvur23n8v46rwjpexcc45563fn53w7"
},
"lnurlUri": {
"type": "string",
"description": "Bech32 representation of LNURL",
"example": "lnurlw://example.com/BTC/UILNURL/withdraw/pp/42kktmpL5d6qVc85Fget7H961ZSQ"
}
}
} }
} }
}, },