mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-01-18 21:32:27 +01:00
Can send max invoice value for lightning payments
This commit is contained in:
parent
c2308675b2
commit
7dd88d8d8f
@ -806,6 +806,26 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanParseCurrencyValue()
|
||||
{
|
||||
Assert.True(CurrencyValue.TryParse("1.50USD", out var result));
|
||||
Assert.Equal("1.50 USD", result.ToString());
|
||||
Assert.True(CurrencyValue.TryParse("1.50 USD", out result));
|
||||
Assert.Equal("1.50 USD", result.ToString());
|
||||
Assert.True(CurrencyValue.TryParse("1.50 usd", out result));
|
||||
Assert.Equal("1.50 USD", result.ToString());
|
||||
Assert.True(CurrencyValue.TryParse("1 usd", out result));
|
||||
Assert.Equal("1 USD", result.ToString());
|
||||
Assert.True(CurrencyValue.TryParse("1usd", out result));
|
||||
Assert.Equal("1 USD", result.ToString());
|
||||
Assert.True(CurrencyValue.TryParse("1.501 usd", out result));
|
||||
Assert.Equal("1.50 USD", result.ToString());
|
||||
Assert.False(CurrencyValue.TryParse("1.501 WTFF", out result));
|
||||
Assert.False(CurrencyValue.TryParse("1,501 usd", out result));
|
||||
Assert.False(CurrencyValue.TryParse("1.501", out result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanParseDerivationScheme()
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<Version>1.0.1.63</Version>
|
||||
<Version>1.0.1.64</Version>
|
||||
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
|
@ -86,15 +86,15 @@ namespace BTCPayServer.Controllers
|
||||
Network: _NetworkProvider.GetNetwork(c.PaymentId.CryptoCode),
|
||||
IsAvailable: Task.FromResult(false)))
|
||||
.Where(c => c.Network != null)
|
||||
.Select(c =>
|
||||
.Select(c =>
|
||||
{
|
||||
c.IsAvailable = c.Handler.IsAvailable(c.SupportedPaymentMethod, c.Network);
|
||||
return c;
|
||||
})
|
||||
.ToList();
|
||||
foreach(var supportedPaymentMethod in supportedPaymentMethods.ToList())
|
||||
foreach (var supportedPaymentMethod in supportedPaymentMethods.ToList())
|
||||
{
|
||||
if(!await supportedPaymentMethod.IsAvailable)
|
||||
if (!await supportedPaymentMethod.IsAvailable)
|
||||
{
|
||||
supportedPaymentMethods.Remove(supportedPaymentMethod);
|
||||
}
|
||||
@ -138,6 +138,7 @@ namespace BTCPayServer.Controllers
|
||||
var rate = await storeBlob.ApplyRateRules(o.Network, _RateProviders.GetRateProvider(o.Network, false)).GetRateAsync(invoice.Currency);
|
||||
PaymentMethod paymentMethod = new PaymentMethod();
|
||||
paymentMethod.ParentEntity = entity;
|
||||
paymentMethod.Network = o.Network;
|
||||
paymentMethod.SetId(o.SupportedPaymentMethod.PaymentId);
|
||||
paymentMethod.Rate = rate;
|
||||
var paymentDetails = await o.Handler.CreatePaymentMethodDetails(o.SupportedPaymentMethod, paymentMethod, o.Network);
|
||||
@ -152,14 +153,44 @@ namespace BTCPayServer.Controllers
|
||||
entity.DepositAddress = paymentMethod.DepositAddress;
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
return paymentMethod;
|
||||
return (SupportedPaymentMethod: o.SupportedPaymentMethod, PaymentMethod: paymentMethod);
|
||||
});
|
||||
entity.SetSupportedPaymentMethods(supportedPaymentMethods.Select(s => s.SupportedPaymentMethod));
|
||||
|
||||
var paymentMethods = new PaymentMethodDictionary();
|
||||
List<ISupportedPaymentMethod> supported = new List<ISupportedPaymentMethod>();
|
||||
foreach (var method in methods)
|
||||
{
|
||||
paymentMethods.Add(await method);
|
||||
var o = await method;
|
||||
|
||||
// Check if Lightning Max value is exceeded
|
||||
if(o.SupportedPaymentMethod.PaymentId.PaymentType == PaymentTypes.LightningLike &&
|
||||
storeBlob.LightningMaxValue != null)
|
||||
{
|
||||
var lightningMaxValue = storeBlob.LightningMaxValue;
|
||||
decimal rate = 0.0m;
|
||||
if (lightningMaxValue.Currency == invoice.Currency)
|
||||
rate = o.PaymentMethod.Rate;
|
||||
else
|
||||
rate = await storeBlob.ApplyRateRules(o.PaymentMethod.Network, _RateProviders.GetRateProvider(o.PaymentMethod.Network, false)).GetRateAsync(lightningMaxValue.Currency);
|
||||
|
||||
var lightningMaxValueCrypto = Money.Coins(lightningMaxValue.Value / rate);
|
||||
if (o.PaymentMethod.Calculate().Due > lightningMaxValueCrypto)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
///////////////
|
||||
supported.Add(o.SupportedPaymentMethod);
|
||||
paymentMethods.Add(o.PaymentMethod);
|
||||
}
|
||||
|
||||
if(supported.Count == 0)
|
||||
{
|
||||
throw new BitpayHttpException(400, "No derivation strategy are available now for this store");
|
||||
}
|
||||
|
||||
entity.SetSupportedPaymentMethods(supported);
|
||||
entity.SetPaymentMethods(paymentMethods);
|
||||
#pragma warning disable CS0618
|
||||
// Legacy Bitpay clients expect information for BTC information, even if the store do not support it
|
||||
var legacyBTCisSet = paymentMethods.Any(p => p.GetId().IsBTCOnChain);
|
||||
@ -177,8 +208,6 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
entity.SetPaymentMethods(paymentMethods);
|
||||
entity.PosData = invoice.PosData;
|
||||
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, _NetworkProvider);
|
||||
_EventAggregator.Publish(new Events.InvoiceEvent(entity, 1001, "invoice_created"));
|
||||
|
@ -199,6 +199,7 @@ namespace BTCPayServer.Controllers
|
||||
vm.SetLanguages(_LangService, storeBlob.DefaultLang);
|
||||
vm.StoreWebsite = store.StoreWebsite;
|
||||
vm.NetworkFee = !storeBlob.NetworkFeeDisabled;
|
||||
vm.LightningMaxValue = storeBlob.LightningMaxValue?.ToString() ?? "";
|
||||
vm.SpeedPolicy = store.SpeedPolicy;
|
||||
AddPaymentMethods(store, vm);
|
||||
vm.StatusMessage = StatusMessage;
|
||||
@ -248,7 +249,14 @@ namespace BTCPayServer.Controllers
|
||||
[Route("{storeId}")]
|
||||
public async Task<IActionResult> UpdateStore(string storeId, StoreViewModel model)
|
||||
{
|
||||
|
||||
CurrencyValue currencyValue = null;
|
||||
if (!string.IsNullOrWhiteSpace(model.LightningMaxValue))
|
||||
{
|
||||
if(!CurrencyValue.TryParse(model.LightningMaxValue, out currencyValue))
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.LightningMaxValue), "Invalid currency value");
|
||||
}
|
||||
}
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
@ -290,6 +298,7 @@ namespace BTCPayServer.Controllers
|
||||
blob.MonitoringExpiration = model.MonitoringExpiration;
|
||||
blob.InvoiceExpiration = model.InvoiceExpiration;
|
||||
blob.DefaultLang = model.DefaultLang;
|
||||
blob.LightningMaxValue = currencyValue;
|
||||
|
||||
bool newExchange = blob.PreferredExchange != model.PreferredExchange;
|
||||
blob.PreferredExchange = model.PreferredExchange;
|
||||
|
44
BTCPayServer/CurrencyValue.cs
Normal file
44
BTCPayServer/CurrencyValue.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Rates;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class CurrencyValue
|
||||
{
|
||||
static Regex _Regex = new Regex("^([0-9]+(\\.[0-9]+)?)\\s*([a-zA-Z]+)$");
|
||||
static CurrencyNameTable _CurrencyTable = new CurrencyNameTable();
|
||||
public static bool TryParse(string str, out CurrencyValue value)
|
||||
{
|
||||
value = null;
|
||||
var match = _Regex.Match(str);
|
||||
if (!match.Success ||
|
||||
!decimal.TryParse(match.Groups[1].Value, out var v))
|
||||
return false;
|
||||
|
||||
var currency = match.Groups.Last().Value.ToUpperInvariant();
|
||||
var currencyData = _CurrencyTable.GetCurrencyData(currency);
|
||||
if (currencyData == null)
|
||||
return false;
|
||||
v = Math.Round(v, currencyData.Divisibility);
|
||||
value = new CurrencyValue()
|
||||
{
|
||||
Value = v,
|
||||
Currency = currency
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
public decimal Value { get; set; }
|
||||
public string Currency { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString(CultureInfo.InvariantCulture) + " " + Currency;
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.JsonConverters;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
@ -254,6 +255,9 @@ namespace BTCPayServer.Data
|
||||
public List<RateRule> RateRules { get; set; } = new List<RateRule>();
|
||||
public string PreferredExchange { get; set; }
|
||||
|
||||
[JsonConverter(typeof(CurrencyValueJsonConverter))]
|
||||
public CurrencyValue LightningMaxValue { get; set; }
|
||||
|
||||
public IRateProvider ApplyRateRules(BTCPayNetwork network, IRateProvider rateProvider)
|
||||
{
|
||||
if (!PreferredExchange.IsCoinAverage())
|
||||
|
39
BTCPayServer/JsonConverters/CurrencyValueJsonConverter.cs
Normal file
39
BTCPayServer/JsonConverters/CurrencyValueJsonConverter.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using NBitcoin.JsonConverters;
|
||||
|
||||
namespace BTCPayServer.JsonConverters
|
||||
{
|
||||
public class CurrencyValueJsonConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(CurrencyValue).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
|
||||
}
|
||||
|
||||
Type longType = typeof(long).GetTypeInfo();
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
try
|
||||
{
|
||||
return reader.TokenType == JsonToken.Null ? null :
|
||||
CurrencyValue.TryParse((string)reader.Value, out var result) ? result :
|
||||
throw new JsonObjectException("Invalid Currency value", reader);
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
throw new JsonObjectException("Invalid Currency value", reader);
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
if(value != null)
|
||||
writer.WriteValue(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
@ -83,6 +83,11 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Display(Name = "Do not propose lightning payment if value of the invoice is above...")]
|
||||
[MaxLength(20)]
|
||||
public string LightningMaxValue { get; set; }
|
||||
|
||||
[Display(Name = "Consider the invoice confirmed when the payment transaction...")]
|
||||
public SpeedPolicy SpeedPolicy
|
||||
{
|
||||
|
@ -242,7 +242,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
#pragma warning disable CS0618
|
||||
public List<PaymentEntity> GetPayments()
|
||||
{
|
||||
return Payments.ToList();
|
||||
return Payments?.ToList() ?? new List<PaymentEntity>();
|
||||
}
|
||||
public List<PaymentEntity> GetPayments(string cryptoCode)
|
||||
{
|
||||
|
@ -41,6 +41,8 @@ namespace BTCPayServer.Services.Rates
|
||||
|
||||
private decimal GetRate(Dictionary<string, decimal> rates, string currency)
|
||||
{
|
||||
if (currency == "BTC")
|
||||
return 1.0m;
|
||||
if (rates.TryGetValue(currency, out decimal result))
|
||||
return result;
|
||||
throw new RateUnavailableException(currency);
|
||||
|
@ -119,6 +119,12 @@
|
||||
<span>This is experimental and not advised for production.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="LightningMaxValue"></label>
|
||||
<input asp-for="LightningMaxValue" class="form-control" />
|
||||
<span asp-validation-for="LightningMaxValue" class="text-danger"></span>
|
||||
<p class="form-text text-muted">Example: 5.50 USD</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
@ -130,13 +136,13 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(var scheme in Model.LightningNodes)
|
||||
{
|
||||
<tr>
|
||||
<td>@scheme.CryptoCode</td>
|
||||
<td>@scheme.Address</td>
|
||||
<td style="text-align:right"><a asp-action="AddLightningNode" asp-route-cryptoCode="@scheme.CryptoCode">Modify</a></td>
|
||||
</tr>
|
||||
}
|
||||
{
|
||||
<tr>
|
||||
<td>@scheme.CryptoCode</td>
|
||||
<td>@scheme.Address</td>
|
||||
<td style="text-align:right"><a asp-action="AddLightningNode" asp-route-cryptoCode="@scheme.CryptoCode">Modify</a></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user