mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +01:00
* Add configurable BOLT11Expiration for refunds (Fix #3281) * Add BOLT11Expiration configuration in Payment
This commit is contained in:
parent
28dbf10a31
commit
090da6cfb6
24 changed files with 150 additions and 22 deletions
|
@ -29,6 +29,17 @@ namespace BTCPayServer.Client.JsonConverters
|
|||
return TimeSpan.FromMinutes(value);
|
||||
}
|
||||
}
|
||||
public class Days : TimeSpanJsonConverter
|
||||
{
|
||||
protected override long ToLong(TimeSpan value)
|
||||
{
|
||||
return (long)value.TotalDays;
|
||||
}
|
||||
protected override TimeSpan ToTimespan(long value)
|
||||
{
|
||||
return TimeSpan.FromDays(value);
|
||||
}
|
||||
}
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(TimeSpan) || objectType == typeof(TimeSpan?);
|
||||
|
|
|
@ -13,6 +13,9 @@ namespace BTCPayServer.Client.Models
|
|||
public string Currency { get; set; }
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
|
||||
public TimeSpan? Period { get; set; }
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter.Days))]
|
||||
[JsonProperty("BOLT11Expiration")]
|
||||
public TimeSpan? BOLT11Expiration { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset? ExpiresAt { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
|
|
|
@ -18,6 +18,9 @@ namespace BTCPayServer.Client.Models
|
|||
public decimal Amount { get; set; }
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
|
||||
public TimeSpan? Period { get; set; }
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter.Days))]
|
||||
[JsonProperty("BOLT11Expiration")]
|
||||
public TimeSpan BOLT11Expiration { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
public string ViewLink { get; set; }
|
||||
}
|
||||
|
|
|
@ -386,6 +386,9 @@ namespace BTCPayServer.Tests
|
|||
|
||||
// BTC crash by 50%
|
||||
s.Server.PayTester.ChangeRate("BTC_USD", new Rating.BidAsk(5000.0m / 2.0m, 5100.0m / 2.0m));
|
||||
s.GoToStore(StoreNavPages.Payment);
|
||||
s.Driver.FindElement(By.Id("BOLT11Expiration")).Clear();
|
||||
s.Driver.FindElement(By.Id("BOLT11Expiration")).SendKeys("5" + Keys.Enter);
|
||||
s.GoToInvoice(invoice.Id);
|
||||
s.Driver.FindElement(By.Id("refundlink")).Click();
|
||||
if (multiCurrency)
|
||||
|
@ -408,6 +411,11 @@ namespace BTCPayServer.Tests
|
|||
s.GoToInvoice(invoice.Id);
|
||||
s.Driver.FindElement(By.Id("refundlink")).Click();
|
||||
Assert.Contains("pull-payments", s.Driver.Url);
|
||||
var client = await user.CreateClient();
|
||||
var ppid = s.Driver.Url.Split('/').Last();
|
||||
var pps = await client.GetPullPayments(user.StoreId);
|
||||
var pp = Assert.Single(pps, p => p.Id == ppid);
|
||||
Assert.Equal(TimeSpan.FromDays(5.0), pp.BOLT11Expiration);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
|
|
|
@ -1252,6 +1252,14 @@ namespace BTCPayServer.Tests
|
|||
Assert.Equal(cache.States[0].Rates[0].Pair, cache2.States[0].Rates[0].Pair);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KitchenSinkTest()
|
||||
{
|
||||
var b = JsonConvert.DeserializeObject<PullPaymentBlob>("{}");
|
||||
Assert.Equal(TimeSpan.FromDays(30.0), b.BOLT11Expiration);
|
||||
var aaa = JsonConvert.SerializeObject(b);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanParseRateRules()
|
||||
{
|
||||
|
|
|
@ -389,14 +389,15 @@ namespace BTCPayServer.Tests
|
|||
result = Assert.Single(pullPayments);
|
||||
VerifyResult();
|
||||
|
||||
Thread.Sleep(1000);
|
||||
var test2 = await client.CreatePullPayment(storeId, new Client.Models.CreatePullPaymentRequest()
|
||||
{
|
||||
Name = "Test 2",
|
||||
Amount = 12.3m,
|
||||
Currency = "BTC",
|
||||
PaymentMethods = new[] { "BTC" }
|
||||
PaymentMethods = new[] { "BTC" },
|
||||
BOLT11Expiration = TimeSpan.FromDays(31.0)
|
||||
});
|
||||
Assert.Equal(TimeSpan.FromDays(31.0), test2.BOLT11Expiration);
|
||||
|
||||
TestLogs.LogInformation("Can't archive without knowing the walletId");
|
||||
var ex = await AssertAPIError("missing-permission", async () => await client.ArchivePullPayment("lol", result.Id));
|
||||
|
@ -405,6 +406,7 @@ namespace BTCPayServer.Tests
|
|||
await AssertAPIError("unauthenticated", async () => await unauthenticated.ArchivePullPayment(storeId, result.Id));
|
||||
await client.ArchivePullPayment(storeId, result.Id);
|
||||
result = await unauthenticated.GetPullPayment(result.Id);
|
||||
Assert.Equal(TimeSpan.FromDays(30.0), result.BOLT11Expiration);
|
||||
Assert.True(result.Archived);
|
||||
var pps = await client.GetPullPayments(storeId);
|
||||
result = Assert.Single(pps);
|
||||
|
|
|
@ -99,6 +99,10 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
{
|
||||
ModelState.AddModelError(nameof(request.Period), $"The period should be positive");
|
||||
}
|
||||
if (request.BOLT11Expiration <= TimeSpan.Zero)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.BOLT11Expiration), $"The BOLT11 expiration should be positive");
|
||||
}
|
||||
PaymentMethodId?[]? paymentMethods = null;
|
||||
if (request.PaymentMethods is { } paymentMethodsStr)
|
||||
{
|
||||
|
@ -127,6 +131,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
StartsAt = request.StartsAt,
|
||||
ExpiresAt = request.ExpiresAt,
|
||||
Period = request.Period,
|
||||
BOLT11Expiration = request.BOLT11Expiration,
|
||||
Name = request.Name,
|
||||
Amount = request.Amount,
|
||||
Currency = request.Currency,
|
||||
|
@ -150,6 +155,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
Currency = ppBlob.Currency,
|
||||
Period = ppBlob.Period,
|
||||
Archived = pp.Archived,
|
||||
BOLT11Expiration = ppBlob.BOLT11Expiration,
|
||||
ViewLink = _linkGenerator.GetUriByAction(
|
||||
nameof(UIPullPaymentController.ViewPullPayment),
|
||||
"UIPullPayment",
|
||||
|
@ -245,7 +251,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
if (pp is null)
|
||||
return PullPaymentNotFound();
|
||||
var ppBlob = pp.GetBlob();
|
||||
var destination = await payoutHandler.ParseClaimDestination(paymentMethodId, request!.Destination, true);
|
||||
var destination = await payoutHandler.ParseAndValidateClaimDestination(paymentMethodId, request!.Destination, ppBlob);
|
||||
if (destination.destination is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Destination), destination.error ?? "The destination is invalid for the payment specified");
|
||||
|
|
|
@ -285,7 +285,8 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
Name = $"Refund {invoice.Id}",
|
||||
PaymentMethodIds = new[] { paymentMethodId },
|
||||
StoreId = invoice.StoreId
|
||||
StoreId = invoice.StoreId,
|
||||
BOLT11Expiration = store.GetStoreBlob().RefundBOLT11Expiration
|
||||
};
|
||||
switch (model.SelectedRefundOption)
|
||||
{
|
||||
|
|
|
@ -115,7 +115,7 @@ namespace BTCPayServer.Controllers
|
|||
ModelState.AddModelError(nameof(vm.SelectedPaymentMethod), $"Invalid destination with selected payment method");
|
||||
return await ViewPullPayment(pullPaymentId);
|
||||
}
|
||||
var destination = await payoutHandler?.ParseClaimDestination(paymentMethodId, vm.Destination, true);
|
||||
var destination = await payoutHandler?.ParseAndValidateClaimDestination(paymentMethodId, vm.Destination, ppBlob);
|
||||
if (destination.destination is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.Destination), destination.error ?? "Invalid destination with selected payment method");
|
||||
|
|
|
@ -134,7 +134,8 @@ namespace BTCPayServer.Controllers
|
|||
StoreId = storeId,
|
||||
PaymentMethodIds = selectedPaymentMethodIds,
|
||||
EmbeddedCSS = model.EmbeddedCSS,
|
||||
CustomCSSLink = model.CustomCSSLink
|
||||
CustomCSSLink = model.CustomCSSLink,
|
||||
BOLT11Expiration = TimeSpan.FromDays(model.BOLT11Expiration)
|
||||
});
|
||||
this.TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
|
|
|
@ -604,7 +604,8 @@ namespace BTCPayServer.Controllers
|
|||
AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice,
|
||||
PaymentTolerance = storeBlob.PaymentTolerance,
|
||||
InvoiceExpiration = (int)storeBlob.InvoiceExpiration.TotalMinutes,
|
||||
DefaultCurrency = storeBlob.DefaultCurrency
|
||||
DefaultCurrency = storeBlob.DefaultCurrency,
|
||||
BOLT11Expiration = (long)storeBlob.RefundBOLT11Expiration.TotalDays
|
||||
};
|
||||
|
||||
return View(vm);
|
||||
|
@ -620,6 +621,7 @@ namespace BTCPayServer.Controllers
|
|||
blob.PaymentTolerance = model.PaymentTolerance;
|
||||
blob.DefaultCurrency = model.DefaultCurrency;
|
||||
blob.InvoiceExpiration = TimeSpan.FromMinutes(model.InvoiceExpiration);
|
||||
blob.RefundBOLT11Expiration = TimeSpan.FromDays(model.BOLT11Expiration);
|
||||
|
||||
if (CurrentStore.SetStoreBlob(blob))
|
||||
{
|
||||
|
|
|
@ -68,7 +68,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
|||
await explorerClient.TrackAsync(TrackedSource.Create(bitcoinLikeClaimDestination.Address));
|
||||
}
|
||||
|
||||
public Task<(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination, bool validate)
|
||||
public Task<(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination)
|
||||
{
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
destination = destination.Trim();
|
||||
|
@ -88,6 +88,11 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
|||
}
|
||||
}
|
||||
|
||||
public (bool valid, string error) ValidateClaimDestination(IClaimDestination claimDestination, PullPaymentBlob pullPaymentBlob)
|
||||
{
|
||||
return (true, null);
|
||||
}
|
||||
|
||||
public IPayoutProof ParseProof(PayoutData payout)
|
||||
{
|
||||
if (payout?.Proof is null)
|
||||
|
@ -251,7 +256,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
|||
var blob = payout.GetBlob(_jsonSerializerSettings);
|
||||
if (payout.GetPaymentMethodId() != paymentMethodId)
|
||||
continue;
|
||||
var claim = await ParseClaimDestination(paymentMethodId, blob.Destination, false);
|
||||
var claim = await ParseClaimDestination(paymentMethodId, blob.Destination);
|
||||
switch (claim.destination)
|
||||
{
|
||||
case UriClaimDestination uriClaimDestination:
|
||||
|
|
|
@ -14,7 +14,18 @@ public interface IPayoutHandler
|
|||
public bool CanHandle(PaymentMethodId paymentMethod);
|
||||
public Task TrackClaim(PaymentMethodId paymentMethodId, IClaimDestination claimDestination);
|
||||
//Allows payout handler to parse payout destinations on its own
|
||||
public Task<(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination, bool validate);
|
||||
public Task<(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination);
|
||||
public (bool valid, string error) ValidateClaimDestination(IClaimDestination claimDestination, PullPaymentBlob pullPaymentBlob);
|
||||
public async Task<(IClaimDestination destination, string error)> ParseAndValidateClaimDestination(PaymentMethodId paymentMethodId, string destination, PullPaymentBlob pullPaymentBlob)
|
||||
{
|
||||
var res = await ParseClaimDestination(paymentMethodId, destination);
|
||||
if (res.destination is null)
|
||||
return res;
|
||||
var res2 = ValidateClaimDestination(res.destination, pullPaymentBlob);
|
||||
if (!res2.valid)
|
||||
return (null, res2.error);
|
||||
return res;
|
||||
}
|
||||
public IPayoutProof ParseProof(PayoutData payout);
|
||||
//Allows you to subscribe the main pull payment hosted service to events and prepare the handler
|
||||
void StartBackgroundCheck(Action<Type[]> subscribe);
|
||||
|
|
|
@ -57,7 +57,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
: LightningLikePayoutHandlerClearnetNamedClient);
|
||||
}
|
||||
|
||||
public async Task<(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination, bool validate)
|
||||
public async Task<(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination)
|
||||
{
|
||||
destination = destination.Trim();
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
|
@ -93,12 +93,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
: null;
|
||||
|
||||
if (result == null)
|
||||
return (null, "A valid BOLT11 invoice (with 30+ day expiry) or LNURL Pay or Lightning address was not provided.");
|
||||
if (validate && (invoice.ExpiryDate.UtcDateTime - DateTime.UtcNow).Days < 30)
|
||||
{
|
||||
return (null,
|
||||
$"The BOLT11 invoice must have an expiry date of at least 30 days from submission (Provided was only {(invoice.ExpiryDate.UtcDateTime - DateTime.UtcNow).Days}).");
|
||||
}
|
||||
return (null, "A valid BOLT11 invoice or LNURL Pay or Lightning address was not provided.");
|
||||
if (invoice.ExpiryDate.UtcDateTime < DateTime.UtcNow)
|
||||
{
|
||||
return (null,
|
||||
|
@ -108,6 +103,19 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
return (result, null);
|
||||
}
|
||||
|
||||
public (bool valid, string error) ValidateClaimDestination(IClaimDestination claimDestination, PullPaymentBlob pullPaymentBlob)
|
||||
{
|
||||
if (claimDestination is not BoltInvoiceClaimDestination bolt)
|
||||
return (true, null);
|
||||
var invoice = bolt.PaymentRequest;
|
||||
if ((invoice.ExpiryDate.UtcDateTime - DateTime.UtcNow) < pullPaymentBlob.BOLT11Expiration)
|
||||
{
|
||||
return (false,
|
||||
$"The BOLT11 invoice must have an expiry date of at least {(long)pullPaymentBlob.BOLT11Expiration.TotalDays} days from submission (Provided was only {(invoice.ExpiryDate.UtcDateTime - DateTime.UtcNow).Days}).");
|
||||
}
|
||||
return (true, null);
|
||||
}
|
||||
|
||||
public IPayoutProof ParseProof(PayoutData payout)
|
||||
{
|
||||
return null;
|
||||
|
|
|
@ -190,7 +190,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
foreach (var payoutData in payoutDatas)
|
||||
{
|
||||
var blob = payoutData.GetBlob(_btcPayNetworkJsonSerializerSettings);
|
||||
var claim = await payoutHandler.ParseClaimDestination(pmi, blob.Destination, false);
|
||||
var claim = await payoutHandler.ParseClaimDestination(pmi, blob.Destination);
|
||||
try
|
||||
{
|
||||
switch (claim.destination)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using BTCPayServer.Payments;
|
||||
|
@ -19,6 +20,12 @@ namespace BTCPayServer.Data
|
|||
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
|
||||
public TimeSpan? Period { get; set; }
|
||||
|
||||
[DefaultValue(typeof(TimeSpan), "30.00:00:00")]
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter.Days))]
|
||||
public TimeSpan BOLT11Expiration { get; set; }
|
||||
|
||||
|
||||
[JsonProperty(ItemConverterType = typeof(PaymentMethodIdJsonConverter))]
|
||||
public PaymentMethodId[] SupportedPaymentMethods { get; set; }
|
||||
|
||||
|
|
|
@ -174,6 +174,11 @@ namespace BTCPayServer.Data
|
|||
[JsonExtensionData]
|
||||
public IDictionary<string, JToken> AdditionalData { get; set; } = new Dictionary<string, JToken>();
|
||||
|
||||
[DefaultValue(typeof(TimeSpan), "30.00:00:00")]
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter.Days))]
|
||||
public TimeSpan RefundBOLT11Expiration { get; set; }
|
||||
|
||||
public class StoreHints
|
||||
{
|
||||
public bool Wallet { get; set; }
|
||||
|
|
|
@ -34,6 +34,7 @@ namespace BTCPayServer.HostedServices
|
|||
public string EmbeddedCSS { get; set; }
|
||||
public PaymentMethodId[] PaymentMethodIds { get; set; }
|
||||
public TimeSpan? Period { get; set; }
|
||||
public TimeSpan? BOLT11Expiration { get; set; }
|
||||
}
|
||||
public class PullPaymentHostedService : BaseAsyncService
|
||||
{
|
||||
|
@ -113,7 +114,8 @@ namespace BTCPayServer.HostedServices
|
|||
CustomCSSLink = create.CustomCSSLink,
|
||||
Email = null,
|
||||
EmbeddedCSS = create.EmbeddedCSS,
|
||||
}
|
||||
},
|
||||
BOLT11Expiration = create.BOLT11Expiration ?? TimeSpan.FromDays(30.0)
|
||||
});
|
||||
ctx.PullPayments.Add(o);
|
||||
await ctx.SaveChangesAsync();
|
||||
|
@ -296,7 +298,7 @@ namespace BTCPayServer.HostedServices
|
|||
var payoutHandler = _payoutHandlers.FindPayoutHandler(paymentMethod);
|
||||
if (payoutHandler is null)
|
||||
throw new InvalidOperationException($"No payout handler for {paymentMethod}");
|
||||
var dest = await payoutHandler.ParseClaimDestination(paymentMethod, payoutBlob.Destination, false);
|
||||
var dest = await payoutHandler.ParseClaimDestination(paymentMethod, payoutBlob.Destination);
|
||||
decimal minimumCryptoAmount = await payoutHandler.GetMinimumPayoutAmount(paymentMethod, dest.destination);
|
||||
if (cryptoAmount < minimumCryptoAmount)
|
||||
{
|
||||
|
|
|
@ -203,7 +203,7 @@ namespace BTCPayServer.Hosting
|
|||
{
|
||||
continue;
|
||||
}
|
||||
var claim = await handler?.ParseClaimDestination(pmi, payoutData.GetBlob(_btcPayNetworkJsonSerializerSettings).Destination, false);
|
||||
var claim = await handler?.ParseClaimDestination(pmi, payoutData.GetBlob(_btcPayNetworkJsonSerializerSettings).Destination);
|
||||
payoutData.Destination = claim.destination?.Id;
|
||||
}
|
||||
await ctx.SaveChangesAsync();
|
||||
|
|
|
@ -24,5 +24,9 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||
[Display(Name = "Default currency")]
|
||||
[MaxLength(10)]
|
||||
public string DefaultCurrency { get; set; }
|
||||
|
||||
[Display(Name = "Minimum acceptable expiration time for BOLT11 for refunds")]
|
||||
[Range(1, 365 * 10)]
|
||||
public long BOLT11Expiration { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,5 +52,8 @@ namespace BTCPayServer.Models.WalletViewModels
|
|||
[Display(Name = "Payment Methods")]
|
||||
public IEnumerable<string> PaymentMethods { get; set; }
|
||||
public IEnumerable<SelectListItem> PaymentMethodItems { get; set; }
|
||||
[Display(Name = "Minimum acceptable expiration time for BOLT11 for refunds")]
|
||||
[Range(1, 365 * 10)]
|
||||
public long BOLT11Expiration { get; set; } = 30;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,6 +73,25 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item"> <h2 class="accordion-header" id="additional-custom-css-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-lightning" aria-expanded="false" aria-controls="additional-lightning">
|
||||
Lightning network settings
|
||||
<vc:icon symbol="caret-down"/>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="additional-lightning" class="accordion-collapse collapse" aria-labelledby="additional-lightning-header">
|
||||
<div class="accordion-body">
|
||||
<div class="form-group">
|
||||
<label asp-for="BOLT11Expiration" class="form-label"></label>
|
||||
<div class="input-group">
|
||||
<input asp-for="BOLT11Expiration" class="form-control" style="max-width:10ch;"/>
|
||||
<span class="input-group-text">days</span>
|
||||
</div>
|
||||
<span asp-validation-for="BOLT11Expiration" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -57,7 +57,14 @@
|
|||
</div>
|
||||
<span asp-validation-for="PaymentTolerance" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="BOLT11Expiration" class="form-label"></label>
|
||||
<div class="input-group">
|
||||
<input asp-for="BOLT11Expiration" class="form-control" style="max-width:10ch;"/>
|
||||
<span class="input-group-text">days</span>
|
||||
</div>
|
||||
<span asp-validation-for="BOLT11Expiration" class="text-danger"></span>
|
||||
</div>
|
||||
<button name="command" type="submit" class="btn btn-primary px-4 mt-3" value="Save" id="Save">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -81,6 +81,13 @@
|
|||
"nullable": true,
|
||||
"description": "The length of each period in seconds."
|
||||
},
|
||||
"BOLT11Expiration": {
|
||||
"type": "string",
|
||||
"example": 30,
|
||||
"default": 30,
|
||||
"nullable": true,
|
||||
"description": "If lightning is activated, do not accept BOLT11 invoices with expiration less than … days"
|
||||
},
|
||||
"startsAt": {
|
||||
"type": "integer",
|
||||
"format": "unix timestamp in seconds",
|
||||
|
@ -657,6 +664,11 @@
|
|||
"nullable": true,
|
||||
"description": "The length of each period in seconds"
|
||||
},
|
||||
"BOLT11Expiration": {
|
||||
"type": "string",
|
||||
"example": 30,
|
||||
"description": "If lightning is activated, do not accept BOLT11 invoices with expiration less than … days"
|
||||
},
|
||||
"archived": {
|
||||
"type": "boolean",
|
||||
"description": "Whether this pull payment is archived"
|
||||
|
|
Loading…
Add table
Reference in a new issue