diff --git a/BTCPayServer.Client/BTCPayServer.Client.csproj b/BTCPayServer.Client/BTCPayServer.Client.csproj index c0b6b4e20..694d2fc83 100644 --- a/BTCPayServer.Client/BTCPayServer.Client.csproj +++ b/BTCPayServer.Client/BTCPayServer.Client.csproj @@ -28,7 +28,7 @@ - + diff --git a/BTCPayServer.Client/Models/PayLightningInvoiceRequest.cs b/BTCPayServer.Client/Models/PayLightningInvoiceRequest.cs index 6d7e6a404..6e94a25a5 100644 --- a/BTCPayServer.Client/Models/PayLightningInvoiceRequest.cs +++ b/BTCPayServer.Client/Models/PayLightningInvoiceRequest.cs @@ -1,3 +1,4 @@ +using System; using BTCPayServer.Client.JsonConverters; using BTCPayServer.JsonConverters; using BTCPayServer.Lightning; @@ -19,5 +20,8 @@ namespace BTCPayServer.Client.Models [JsonConverter(typeof(LightMoneyJsonConverter))] public LightMoney Amount { get; set; } + + [JsonConverter(typeof(TimeSpanJsonConverter.Seconds))] + public TimeSpan? SendTimeout { get; set; } } } diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 4d36db9b7..8e4c644b1 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -41,7 +41,7 @@ - + diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.cs index d2f705ca4..0291b9254 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.cs @@ -2,7 +2,6 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; -using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Client; using BTCPayServer.Client.Models; @@ -222,16 +221,26 @@ namespace BTCPayServer.Controllers.Greenfield return this.CreateValidationError(ModelState); } - var param = lightningInvoice.MaxFeeFlat != null || lightningInvoice.MaxFeePercent != null || lightningInvoice.Amount != null - ? new PayInvoiceParams { MaxFeePercent = lightningInvoice.MaxFeePercent, MaxFeeFlat = lightningInvoice.MaxFeeFlat, Amount = lightningInvoice.Amount } + var param = lightningInvoice.MaxFeeFlat != null || lightningInvoice.MaxFeePercent != null + || lightningInvoice.Amount != null || lightningInvoice.SendTimeout != null + ? new PayInvoiceParams + { + MaxFeePercent = lightningInvoice.MaxFeePercent, + MaxFeeFlat = lightningInvoice.MaxFeeFlat, + Amount = lightningInvoice.Amount, + SendTimeout = lightningInvoice.SendTimeout + } : null; var result = await lightningClient.Pay(lightningInvoice.BOLT11, param, cancellationToken); - if (result.Result == PayResult.Ok && bolt11?.PaymentHash is not null) + if (result.Result is PayResult.Ok or PayResult.Unknown && bolt11?.PaymentHash is not null) { + // get a new instance of the LN client, because the old one might have disposed its HTTPClient + lightningClient = await GetLightningClient(cryptoCode, true); + var paymentHash = bolt11.PaymentHash.ToString(); var payment = await lightningClient.GetPayment(paymentHash, cancellationToken); - return Ok(new LightningPaymentData + var data = new LightningPaymentData { Id = payment.Id, PaymentHash = paymentHash, @@ -241,19 +250,25 @@ namespace BTCPayServer.Controllers.Greenfield CreatedAt = payment.CreatedAt, TotalAmount = payment.AmountSent, FeeAmount = payment.Fee, - }); + }; + return result.Result is PayResult.Ok ? Ok(data) : Accepted(data); } return result.Result switch { PayResult.CouldNotFindRoute => this.CreateAPIError("could-not-find-route", "Impossible to find a route to the peer"), PayResult.Error => this.CreateAPIError("generic-error", result.ErrorDetail), + PayResult.Unknown => Accepted(new LightningPaymentData + { + Status = LightningPaymentStatus.Unknown + }), PayResult.Ok => Ok(new LightningPaymentData { + Status = LightningPaymentStatus.Complete, TotalAmount = result.Details?.TotalAmount, FeeAmount = result.Details?.FeeAmount }), - _ => throw new NotSupportedException("Unsupported Payresult") + _ => throw new NotSupportedException("Unsupported PayResult") }; } diff --git a/BTCPayServer/Data/Payouts/LightningLike/UILightningLikePayoutController.cs b/BTCPayServer/Data/Payouts/LightningLike/UILightningLikePayoutController.cs index 4f5029dc8..63e031c80 100644 --- a/BTCPayServer/Data/Payouts/LightningLike/UILightningLikePayoutController.cs +++ b/BTCPayServer/Data/Payouts/LightningLike/UILightningLikePayoutController.cs @@ -281,6 +281,8 @@ namespace BTCPayServer.Data.Payouts.LightningLike var proofBlob = new PayoutLightningBlob() {PaymentHash = bolt11PaymentRequest.PaymentHash.ToString()}; try { + // TODO: Incorporate the changes from this PR here: + // https://github.com/btcpayserver/BTCPayServer.Lightning/pull/106 using var cts = new CancellationTokenSource(SendTimeout); var result = await lightningClient.Pay(bolt11PaymentRequest.ToString(), new PayInvoiceParams() diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.json index 1932a3a9c..d0fff2450 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.json @@ -100,7 +100,6 @@ }, "TimeSpanSeconds": { "allOf": [ { "$ref": "#/components/schemas/TimeSpan" } ], - "format": "seconds", "description": "A span of times in seconds" }, diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.common.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.common.json index 78f41eb42..7418d99fb 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.common.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.common.json @@ -323,6 +323,17 @@ "nullable": true, "description": "The fee limit expressed as a fixed amount in satoshi", "example": "21" + }, + "sendTimeout": { + "nullable": true, + "example": 30, + "default": 30, + "description": "The number of seconds after which the payment times out", + "allOf": [ + { + "$ref": "#/components/schemas/TimeSpanSeconds" + } + ] } } }, diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.internal.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.internal.json index 22f1c28f1..22eb5884a 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.internal.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.internal.json @@ -465,7 +465,7 @@ "example": "BTC" } ], - "description": "Pay a lightning invoice.", + "description": "Pay a lightning invoice. In case the payment response times out, the status will be reported as pending and the final status can be resolved using the [Get payment](#operation/InternalLightningNodeApi_GetPayment) endpoint. The default wait time for payment responses is 30 seconds — it might take longer if multiple routes are tried or a hold invoice is getting paid.", "operationId": "InternalLightningNodeApi_PayInvoice", "responses": { "200": { @@ -478,6 +478,16 @@ } } }, + "202": { + "description": "Payment initiated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LightningPaymentData" + } + } + } + }, "422": { "description": "Unable to validate the request", "content": { diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.store.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.store.json index 9de0f1903..f1c5744d3 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.store.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.store.json @@ -548,7 +548,7 @@ } } ], - "description": "Pay a lightning invoice.", + "description": "Pay a lightning invoice. In case the payment response times out, the status will be reported as pending and the final status can be resolved using the [Get payment](#operation/StoreLightningNodeApi_GetPayment) endpoint. The default wait time for payment responses is 30 seconds — it might take longer if multiple routes are tried or a hold invoice is getting paid.", "operationId": "StoreLightningNodeApi_PayInvoice", "responses": { "200": { @@ -561,6 +561,16 @@ } } }, + "202": { + "description": "Payment initiated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LightningPaymentData" + } + } + } + }, "422": { "description": "Unable to validate the request", "content": {