mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 06:24:24 +01:00
Can send max invoice value for lightning payments
This commit is contained in:
@@ -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]
|
[Fact]
|
||||||
public void CanParseDerivationScheme()
|
public void CanParseDerivationScheme()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
<Version>1.0.1.63</Version>
|
<Version>1.0.1.64</Version>
|
||||||
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -86,15 +86,15 @@ namespace BTCPayServer.Controllers
|
|||||||
Network: _NetworkProvider.GetNetwork(c.PaymentId.CryptoCode),
|
Network: _NetworkProvider.GetNetwork(c.PaymentId.CryptoCode),
|
||||||
IsAvailable: Task.FromResult(false)))
|
IsAvailable: Task.FromResult(false)))
|
||||||
.Where(c => c.Network != null)
|
.Where(c => c.Network != null)
|
||||||
.Select(c =>
|
.Select(c =>
|
||||||
{
|
{
|
||||||
c.IsAvailable = c.Handler.IsAvailable(c.SupportedPaymentMethod, c.Network);
|
c.IsAvailable = c.Handler.IsAvailable(c.SupportedPaymentMethod, c.Network);
|
||||||
return c;
|
return c;
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
foreach(var supportedPaymentMethod in supportedPaymentMethods.ToList())
|
foreach (var supportedPaymentMethod in supportedPaymentMethods.ToList())
|
||||||
{
|
{
|
||||||
if(!await supportedPaymentMethod.IsAvailable)
|
if (!await supportedPaymentMethod.IsAvailable)
|
||||||
{
|
{
|
||||||
supportedPaymentMethods.Remove(supportedPaymentMethod);
|
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);
|
var rate = await storeBlob.ApplyRateRules(o.Network, _RateProviders.GetRateProvider(o.Network, false)).GetRateAsync(invoice.Currency);
|
||||||
PaymentMethod paymentMethod = new PaymentMethod();
|
PaymentMethod paymentMethod = new PaymentMethod();
|
||||||
paymentMethod.ParentEntity = entity;
|
paymentMethod.ParentEntity = entity;
|
||||||
|
paymentMethod.Network = o.Network;
|
||||||
paymentMethod.SetId(o.SupportedPaymentMethod.PaymentId);
|
paymentMethod.SetId(o.SupportedPaymentMethod.PaymentId);
|
||||||
paymentMethod.Rate = rate;
|
paymentMethod.Rate = rate;
|
||||||
var paymentDetails = await o.Handler.CreatePaymentMethodDetails(o.SupportedPaymentMethod, paymentMethod, o.Network);
|
var paymentDetails = await o.Handler.CreatePaymentMethodDetails(o.SupportedPaymentMethod, paymentMethod, o.Network);
|
||||||
@@ -152,14 +153,44 @@ namespace BTCPayServer.Controllers
|
|||||||
entity.DepositAddress = paymentMethod.DepositAddress;
|
entity.DepositAddress = paymentMethod.DepositAddress;
|
||||||
}
|
}
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
return paymentMethod;
|
return (SupportedPaymentMethod: o.SupportedPaymentMethod, PaymentMethod: paymentMethod);
|
||||||
});
|
});
|
||||||
entity.SetSupportedPaymentMethods(supportedPaymentMethods.Select(s => s.SupportedPaymentMethod));
|
|
||||||
var paymentMethods = new PaymentMethodDictionary();
|
var paymentMethods = new PaymentMethodDictionary();
|
||||||
|
List<ISupportedPaymentMethod> supported = new List<ISupportedPaymentMethod>();
|
||||||
foreach (var method in methods)
|
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
|
#pragma warning disable CS0618
|
||||||
// Legacy Bitpay clients expect information for BTC information, even if the store do not support it
|
// Legacy Bitpay clients expect information for BTC information, even if the store do not support it
|
||||||
var legacyBTCisSet = paymentMethods.Any(p => p.GetId().IsBTCOnChain);
|
var legacyBTCisSet = paymentMethods.Any(p => p.GetId().IsBTCOnChain);
|
||||||
@@ -177,8 +208,6 @@ namespace BTCPayServer.Controllers
|
|||||||
}
|
}
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
}
|
}
|
||||||
|
|
||||||
entity.SetPaymentMethods(paymentMethods);
|
|
||||||
entity.PosData = invoice.PosData;
|
entity.PosData = invoice.PosData;
|
||||||
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, _NetworkProvider);
|
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, _NetworkProvider);
|
||||||
_EventAggregator.Publish(new Events.InvoiceEvent(entity, 1001, "invoice_created"));
|
_EventAggregator.Publish(new Events.InvoiceEvent(entity, 1001, "invoice_created"));
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ namespace BTCPayServer.Controllers
|
|||||||
vm.SetLanguages(_LangService, storeBlob.DefaultLang);
|
vm.SetLanguages(_LangService, storeBlob.DefaultLang);
|
||||||
vm.StoreWebsite = store.StoreWebsite;
|
vm.StoreWebsite = store.StoreWebsite;
|
||||||
vm.NetworkFee = !storeBlob.NetworkFeeDisabled;
|
vm.NetworkFee = !storeBlob.NetworkFeeDisabled;
|
||||||
|
vm.LightningMaxValue = storeBlob.LightningMaxValue?.ToString() ?? "";
|
||||||
vm.SpeedPolicy = store.SpeedPolicy;
|
vm.SpeedPolicy = store.SpeedPolicy;
|
||||||
AddPaymentMethods(store, vm);
|
AddPaymentMethods(store, vm);
|
||||||
vm.StatusMessage = StatusMessage;
|
vm.StatusMessage = StatusMessage;
|
||||||
@@ -248,7 +249,14 @@ namespace BTCPayServer.Controllers
|
|||||||
[Route("{storeId}")]
|
[Route("{storeId}")]
|
||||||
public async Task<IActionResult> UpdateStore(string storeId, StoreViewModel model)
|
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)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
return View(model);
|
return View(model);
|
||||||
@@ -290,6 +298,7 @@ namespace BTCPayServer.Controllers
|
|||||||
blob.MonitoringExpiration = model.MonitoringExpiration;
|
blob.MonitoringExpiration = model.MonitoringExpiration;
|
||||||
blob.InvoiceExpiration = model.InvoiceExpiration;
|
blob.InvoiceExpiration = model.InvoiceExpiration;
|
||||||
blob.DefaultLang = model.DefaultLang;
|
blob.DefaultLang = model.DefaultLang;
|
||||||
|
blob.LightningMaxValue = currencyValue;
|
||||||
|
|
||||||
bool newExchange = blob.PreferredExchange != model.PreferredExchange;
|
bool newExchange = blob.PreferredExchange != model.PreferredExchange;
|
||||||
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 Newtonsoft.Json.Linq;
|
||||||
using BTCPayServer.Services.Rates;
|
using BTCPayServer.Services.Rates;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.JsonConverters;
|
||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
@@ -254,6 +255,9 @@ namespace BTCPayServer.Data
|
|||||||
public List<RateRule> RateRules { get; set; } = new List<RateRule>();
|
public List<RateRule> RateRules { get; set; } = new List<RateRule>();
|
||||||
public string PreferredExchange { get; set; }
|
public string PreferredExchange { get; set; }
|
||||||
|
|
||||||
|
[JsonConverter(typeof(CurrencyValueJsonConverter))]
|
||||||
|
public CurrencyValue LightningMaxValue { get; set; }
|
||||||
|
|
||||||
public IRateProvider ApplyRateRules(BTCPayNetwork network, IRateProvider rateProvider)
|
public IRateProvider ApplyRateRules(BTCPayNetwork network, IRateProvider rateProvider)
|
||||||
{
|
{
|
||||||
if (!PreferredExchange.IsCoinAverage())
|
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;
|
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...")]
|
[Display(Name = "Consider the invoice confirmed when the payment transaction...")]
|
||||||
public SpeedPolicy SpeedPolicy
|
public SpeedPolicy SpeedPolicy
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
public List<PaymentEntity> GetPayments()
|
public List<PaymentEntity> GetPayments()
|
||||||
{
|
{
|
||||||
return Payments.ToList();
|
return Payments?.ToList() ?? new List<PaymentEntity>();
|
||||||
}
|
}
|
||||||
public List<PaymentEntity> GetPayments(string cryptoCode)
|
public List<PaymentEntity> GetPayments(string cryptoCode)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ namespace BTCPayServer.Services.Rates
|
|||||||
|
|
||||||
private decimal GetRate(Dictionary<string, decimal> rates, string currency)
|
private decimal GetRate(Dictionary<string, decimal> rates, string currency)
|
||||||
{
|
{
|
||||||
|
if (currency == "BTC")
|
||||||
|
return 1.0m;
|
||||||
if (rates.TryGetValue(currency, out decimal result))
|
if (rates.TryGetValue(currency, out decimal result))
|
||||||
return result;
|
return result;
|
||||||
throw new RateUnavailableException(currency);
|
throw new RateUnavailableException(currency);
|
||||||
|
|||||||
@@ -119,6 +119,12 @@
|
|||||||
<span>This is experimental and not advised for production.</span>
|
<span>This is experimental and not advised for production.</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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">
|
<div class="form-group">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead class="thead-inverse">
|
<thead class="thead-inverse">
|
||||||
@@ -130,13 +136,13 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach(var scheme in Model.LightningNodes)
|
@foreach(var scheme in Model.LightningNodes)
|
||||||
{
|
{
|
||||||
<tr>
|
<tr>
|
||||||
<td>@scheme.CryptoCode</td>
|
<td>@scheme.CryptoCode</td>
|
||||||
<td>@scheme.Address</td>
|
<td>@scheme.Address</td>
|
||||||
<td style="text-align:right"><a asp-action="AddLightningNode" asp-route-cryptoCode="@scheme.CryptoCode">Modify</a></td>
|
<td style="text-align:right"><a asp-action="AddLightningNode" asp-route-cryptoCode="@scheme.CryptoCode">Modify</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user