From e1e3e5d95310ccf025ba3298f226c2bc1d1bca10 Mon Sep 17 00:00:00 2001 From: mutedstorm Date: Sun, 15 Apr 2018 12:32:24 +0200 Subject: [PATCH 01/17] fix german translation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixed small errors and changed "Geldbörse / Brieftasche" back to Wallet because its never translated on German sites so its unnecessary. --- BTCPayServer/wwwroot/checkout/js/langs/de.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/BTCPayServer/wwwroot/checkout/js/langs/de.js b/BTCPayServer/wwwroot/checkout/js/langs/de.js index 8456bb907..fb6a188b4 100644 --- a/BTCPayServer/wwwroot/checkout/js/langs/de.js +++ b/BTCPayServer/wwwroot/checkout/js/langs/de.js @@ -5,8 +5,8 @@ const locales_de = { "Awaiting Payment...": "Warten auf Zahlung...", "Pay with": "Bezahlen mit", "Contact and Refund Email": "Kontakt und Rückerstattungs Email", - "Contact_Body": "Bitte geben Sie unten eine E-Mail-Adresse an. Wir werden Sie unter dieser Adresse kontaktieren, wenn ein Problem mit Ihrer Zahlung vorliegt.", - "Your email": "Deine Email", + "Contact_Body": "Bitte geben Sie unten eine E-Mail-Adresse an. Wir werden Sie unter dieser Adresse kontaktieren, falls ein Problem mit Ihrer Zahlung vorliegt.", + "Your email": "Deine Email-Adresse", "Continue": "Fortsetzen", "Please enter a valid email address": "Bitte geben Sie eine gültige E-Mail-Adresse ein", "Order Amount": "Bestellbetrag", @@ -16,9 +16,9 @@ const locales_de = { // Tabs "Scan": "Scan", "Copy": "Kopieren", - "Conversion": "Umwandlung", + "Conversion": "Umrechnung", // Scan tab - "Open in wallet": "In der Brieftasche öffnen", + "Open in wallet": "In der Wallet öffnen", // Copy tab "CompletePay_Body": "Um Ihre Zahlung abzuschließen, senden Sie bitte {{btcDue}} {{cryptoCode}} an die unten angegebene Adresse.", "Amount": "Menge", @@ -26,9 +26,9 @@ const locales_de = { "Copied": "Kopiert", // Conversion tab "ConversionTab_BodyTop": "Sie können {{btcDue}} {{cryptoCode}} mit altcoins bezahlen, die nicht direkt vom Händler unterstützt werden.", - "ConversionTab_BodyDesc": "Dieser Service wird von Drittanbietern bereitgestellt. Bitte beachten Sie, dass wir keine Kontrolle darüber haben, wie die Anbieter Ihre Gelder weiterleiten. Die Rechnung wird erst bezahlt, wenn das Geld in {{cryptoCode}} Blockchain eingegangen ist.", + "ConversionTab_BodyDesc": "Dieser Service wird von Drittanbietern bereitgestellt. Bitte beachten Sie, dass wir keine Kontrolle darüber haben, wie die Anbieter Ihre Gelder weiterleiten. Die Rechnung wird erst als bezahlt markiert, wenn das Geld in {{cryptoCode}} Blockchain eingegangen ist.", "Shapeshift_Button_Text": "Bezahlen mit Altcoins", - "ConversionTab_Lightning": "Für Lightning Network-Zahlungen sind keine Conversion-Anbieter verfügbar.", + "ConversionTab_Lightning": "Für Lightning Network-Zahlungen sind keine Umrechnungsanbieter verfügbar.", // Invoice expired "Invoice expiring soon...": "Die Rechnung läuft bald ab...", "Invoice expired": "Die Rechnung ist abgelaufen", @@ -36,7 +36,7 @@ const locales_de = { "InvoiceExpired_Body_1": "Diese Rechnung ist abgelaufen. Eine Rechnung ist nur für {{maxTimeMinutes}} Minuten gültig. \ Sie können zu {{storeName}} zurückkehren, wenn Sie Ihre Zahlung erneut senden möchten.", "InvoiceExpired_Body_2": "Wenn Sie versucht haben, eine Zahlung zu senden, wurde sie vom Bitcoin-Netzwerk noch nicht akzeptiert. Wir haben Ihre Gelder noch nicht erhalten.", - "InvoiceExpired_Body_3": "Wenn die Transaktion vom Bitcoin-Netzwerk nicht akzeptiert wird, ist das Geld wieder in Ihrer Brieftasche verfügbar. Abhängig von Ihrem Geldbeutel, kann dies 48-72 Stunden dauern.", + "InvoiceExpired_Body_3": "Wenn die Transaktion vom Bitcoin-Netzwerk nicht akzeptiert wird, ist das Geld wieder in Ihrer Wallet verfügbar. Abhängig von Ihrer Wallet, kann dies 48-72 Stunden dauern.", "Invoice ID": "Rechnungs ID", "Order ID": "Auftrag ID", "Return to StoreName": "Zurück zu {{storeName}}", @@ -47,7 +47,7 @@ Sie können zu {{storeName}} zurückkehren, wenn Sie Ihre Zahlung erneut senden "Archived_Body": "Bitte kontaktieren Sie den Shop für Bestellinformationen oder Hilfe", // Lightning "BOLT 11 Invoice": "BOLT 11 Rechnung", - "Node Info": "Knoten Info", + "Node Info": "Netzwerkknoten Info", // "txCount": "{{count}} transaktion", "txCount_plural": "{{count}} transaktionen" From 0723eec50886d8898eac3c15bb016f06d90ff4f5 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sun, 15 Apr 2018 21:18:51 +0900 Subject: [PATCH 02/17] Fix rate handling --- BTCPayServer.Tests/BTCPayServerTester.cs | 22 ++++++---- BTCPayServer.Tests/ServerTester.cs | 19 ++++----- BTCPayServer.Tests/UnitTest1.cs | 39 ++++++++++++++++++ BTCPayServer/BTCPayServer.csproj | 2 +- BTCPayServer/Controllers/InvoiceController.cs | 6 +-- BTCPayServer/Controllers/RateController.cs | 10 +++-- BTCPayServer/Data/StoreData.cs | 26 ++---------- .../HostedServices/RatesHostedService.cs | 33 ++++++++++++--- .../Rates/BTCPayRateProviderFactory.cs | 23 ++++++++++- .../Services/Rates/CoinAverageRateProvider.cs | 41 ++++++++++++++++++- .../Services/Rates/IRateProviderFactory.cs | 30 +++++++++++++- .../Services/Rates/MockRateProvider.cs | 9 +++- .../Services/Rates/TweakRateProvider.cs | 4 +- 13 files changed, 203 insertions(+), 61 deletions(-) diff --git a/BTCPayServer.Tests/BTCPayServerTester.cs b/BTCPayServer.Tests/BTCPayServerTester.cs index b42107839..ccd7aec85 100644 --- a/BTCPayServer.Tests/BTCPayServerTester.cs +++ b/BTCPayServer.Tests/BTCPayServerTester.cs @@ -47,7 +47,7 @@ namespace BTCPayServer.Tests } public Uri LTCNBXplorerUri { get; set; } - + public Uri ServerUri { get; @@ -65,6 +65,9 @@ namespace BTCPayServer.Tests get; set; } + + public bool MockRates { get; set; } = true; + public void Start() { if (!Directory.Exists(_Directory)) @@ -101,12 +104,15 @@ namespace BTCPayServer.Tests .UseConfiguration(conf) .ConfigureServices(s => { - var mockRates = new MockRateProviderFactory(); - var btc = new MockRateProvider("BTC", new Rate("USD", 5000m), new Rate("CAD", 4500m)); - var ltc = new MockRateProvider("LTC", new Rate("USD", 500m)); - mockRates.AddMock(btc); - mockRates.AddMock(ltc); - s.AddSingleton(mockRates); + if (MockRates) + { + var mockRates = new MockRateProviderFactory(); + var btc = new MockRateProvider("BTC", new Rate("USD", 5000m), new Rate("CAD", 4500m)); + var ltc = new MockRateProvider("LTC", new Rate("USD", 500m)); + mockRates.AddMock(btc); + mockRates.AddMock(ltc); + s.AddSingleton(mockRates); + } s.AddLogging(l => { l.SetMinimumLevel(LogLevel.Information) @@ -121,7 +127,7 @@ namespace BTCPayServer.Tests _Host.Start(); InvoiceRepository = (InvoiceRepository)_Host.Services.GetService(typeof(InvoiceRepository)); } - + public string HostName { get; diff --git a/BTCPayServer.Tests/ServerTester.cs b/BTCPayServer.Tests/ServerTester.cs index 73fc2e425..2e3c4f651 100644 --- a/BTCPayServer.Tests/ServerTester.cs +++ b/BTCPayServer.Tests/ServerTester.cs @@ -34,21 +34,11 @@ namespace BTCPayServer.Tests public ServerTester(string scope) { _Directory = scope; - } - - public bool Dockerized - { - get; set; - } - - public void Start() - { if (Directory.Exists(_Directory)) Utils.DeleteDirectory(_Directory); if (!Directory.Exists(_Directory)) Directory.CreateDirectory(_Directory); - NetworkProvider = new BTCPayNetworkProvider(ChainType.Regtest); ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_BTCRPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork("BTC").NBitcoinNetwork); LTCExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_LTCRPCCONNECTION", "server=http://127.0.0.1:43783;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork("LTC").NBitcoinNetwork); @@ -72,6 +62,15 @@ namespace BTCPayServer.Tests PayTester.Port = int.Parse(GetEnvironment("TESTS_PORT", Utils.FreeTcpPort().ToString(CultureInfo.InvariantCulture)), CultureInfo.InvariantCulture); PayTester.HostName = GetEnvironment("TESTS_HOSTNAME", "127.0.0.1"); PayTester.InContainer = bool.Parse(GetEnvironment("TESTS_INCONTAINER", "false")); + } + + public bool Dockerized + { + get; set; + } + + public void Start() + { PayTester.Start(); } diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 6825ff335..044a67ab5 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -616,12 +616,51 @@ namespace BTCPayServer.Tests } } + [Fact] + public void CanUseExchangeSpecificRate() + { + using (var tester = ServerTester.Create()) + { + tester.PayTester.MockRates = false; + tester.Start(); + var user = tester.NewAccount(); + user.GrantAccess(); + user.RegisterDerivationScheme("BTC"); + List rates = new List(); + rates.Add(CreateInvoice(tester, user, "coinaverage")); + rates.Add(CreateInvoice(tester, user, "bitflyer")); + + foreach(var rate in rates) + { + Assert.Single(rates.Where(r => r == rate)); + } + } + } + + private static decimal CreateInvoice(ServerTester tester, TestAccount user, string exchange) + { + var storeController = tester.PayTester.GetController(user.UserId); + var vm = (StoreViewModel)((ViewResult)storeController.UpdateStore(user.StoreId).Result).Model; + vm.PreferredExchange = exchange; + storeController.UpdateStore(user.StoreId, vm).Wait(); + var invoice2 = user.BitPay.CreateInvoice(new Invoice() + { + Price = 5000.0, + Currency = "USD", + PosData = "posData", + OrderId = "orderId", + ItemDesc = "Some description", + FullNotifications = true + }, Facade.Merchant); + return invoice2.CryptoInfo[0].Rate; + } [Fact] public void CanTweakRate() { using (var tester = ServerTester.Create()) { + tester.PayTester.MockRates = false; tester.Start(); var user = tester.NewAccount(); user.GrantAccess(); diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 5670ea00f..2cc18d247 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.0 - 1.0.1.82 + 1.0.1.83 NU1701,CA1816,CA1308,CA1810,CA2208 diff --git a/BTCPayServer/Controllers/InvoiceController.cs b/BTCPayServer/Controllers/InvoiceController.cs index af3496b9e..03a89cc84 100644 --- a/BTCPayServer/Controllers/InvoiceController.cs +++ b/BTCPayServer/Controllers/InvoiceController.cs @@ -165,7 +165,7 @@ namespace BTCPayServer.Controllers { var btc = _NetworkProvider.BTC; var feeProvider = ((IFeeProviderFactory)_ServiceProvider.GetService(typeof(IFeeProviderFactory))).CreateFeeProvider(btc); - var rateProvider = storeBlob.ApplyRateRules(btc, _RateProviders.GetRateProvider(btc)); + var rateProvider = _RateProviders.GetRateProvider(btc, storeBlob.GetRateRules()); if (feeProvider != null && rateProvider != null) { var gettingFee = feeProvider.GetFeeRateAsync(); @@ -186,7 +186,7 @@ namespace BTCPayServer.Controllers private async Task CreatePaymentMethodAsync(IPaymentMethodHandler handler, ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network, InvoiceEntity entity, StoreData store) { var storeBlob = store.GetStoreBlob(); - var rate = await storeBlob.ApplyRateRules(network, _RateProviders.GetRateProvider(network)).GetRateAsync(entity.ProductInformation.Currency); + var rate = await _RateProviders.GetRateProvider(network, storeBlob.GetRateRules()).GetRateAsync(entity.ProductInformation.Currency); PaymentMethod paymentMethod = new PaymentMethod(); paymentMethod.ParentEntity = entity; paymentMethod.Network = network; @@ -221,7 +221,7 @@ namespace BTCPayServer.Controllers if (limitValue.Currency == entity.ProductInformation.Currency) limitValueRate = paymentMethod.Rate; else - limitValueRate = await storeBlob.ApplyRateRules(network, _RateProviders.GetRateProvider(network)).GetRateAsync(limitValue.Currency); + limitValueRate = await _RateProviders.GetRateProvider(network, storeBlob.GetRateRules()).GetRateAsync(limitValue.Currency); var limitValueCrypto = Money.Coins(limitValue.Value / limitValueRate); if (compare(paymentMethod.Calculate().Due, limitValueCrypto)) diff --git a/BTCPayServer/Controllers/RateController.cs b/BTCPayServer/Controllers/RateController.cs index 3a2170ce6..63336ed0b 100644 --- a/BTCPayServer/Controllers/RateController.cs +++ b/BTCPayServer/Controllers/RateController.cs @@ -49,18 +49,20 @@ namespace BTCPayServer.Controllers var network = _NetworkProvider.GetNetwork(cryptoCode); if (network == null) return NotFound(); - var rateProvider = _RateProviderFactory.GetRateProvider(network); - if (rateProvider == null) - return NotFound(); + RateRules rules = null; if (storeId != null) { var store = await _StoreRepo.FindStore(storeId); if (store == null) return NotFound(); - rateProvider = store.GetStoreBlob().ApplyRateRules(network, rateProvider); + rules = store.GetStoreBlob().GetRateRules(); } + var rateProvider = _RateProviderFactory.GetRateProvider(network, rules); + if (rateProvider == null) + return NotFound(); + var allRates = (await rateProvider.GetRatesAsync()); return Json(allRates.Select(r => new NBitpayClient.Rate() diff --git a/BTCPayServer/Data/StoreData.cs b/BTCPayServer/Data/StoreData.cs index d953fabf1..6ac02a0d1 100644 --- a/BTCPayServer/Data/StoreData.cs +++ b/BTCPayServer/Data/StoreData.cs @@ -284,30 +284,12 @@ namespace BTCPayServer.Data } } - public IRateProvider ApplyRateRules(BTCPayNetwork network, IRateProvider rateProvider) + public RateRules GetRateRules() { - if (!PreferredExchange.IsCoinAverage()) + return new RateRules(RateRules) { - // If the original rateProvider is a cache, use the same inner provider as fallback, and same memory cache to wrap it all - if (rateProvider is CachedRateProvider cachedRateProvider) - { - rateProvider = new FallbackRateProvider(new IRateProvider[] { - new CoinAverageRateProvider(network.CryptoCode) { Exchange = PreferredExchange }, - cachedRateProvider.Inner - }); - rateProvider = new CachedRateProvider(network.CryptoCode, rateProvider, cachedRateProvider.MemoryCache) { AdditionalScope = PreferredExchange }; - } - else - { - rateProvider = new FallbackRateProvider(new IRateProvider[] { - new CoinAverageRateProvider(network.CryptoCode) { Exchange = PreferredExchange }, - rateProvider - }); - } - } - if (RateRules == null || RateRules.Count == 0) - return rateProvider; - return new TweakRateProvider(network, rateProvider, RateRules.ToList()); + PreferredExchange = PreferredExchange + }; } } } diff --git a/BTCPayServer/HostedServices/RatesHostedService.cs b/BTCPayServer/HostedServices/RatesHostedService.cs index 781b87818..18b0b3a6b 100644 --- a/BTCPayServer/HostedServices/RatesHostedService.cs +++ b/BTCPayServer/HostedServices/RatesHostedService.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -6,24 +7,44 @@ using System.Threading.Tasks; using BTCPayServer.Services; using BTCPayServer.Services.Rates; using Microsoft.Extensions.Hosting; +using BTCPayServer.Logging; namespace BTCPayServer.HostedServices { public class RatesHostedService : IHostedService { private SettingsRepository _SettingsRepository; - private BTCPayRateProviderFactory _RateProviderFactory; - public RatesHostedService(SettingsRepository repo, IRateProviderFactory rateProviderFactory) + private IRateProviderFactory _RateProviderFactory; + public RatesHostedService(SettingsRepository repo, + IRateProviderFactory rateProviderFactory) { this._SettingsRepository = repo; - _RateProviderFactory = rateProviderFactory as BTCPayRateProviderFactory; + _RateProviderFactory = rateProviderFactory; } - public async Task StartAsync(CancellationToken cancellationToken) + public Task StartAsync(CancellationToken cancellationToken) + { + Init(); + return Task.CompletedTask; + } + + async void Init() { - if (_RateProviderFactory == null) - return; var rates = (await _SettingsRepository.GetSettingAsync()) ?? new RatesSetting(); _RateProviderFactory.CacheSpan = TimeSpan.FromMinutes(rates.CacheInMinutes); + + //string[] availableExchanges = null; + //// So we don't run this in testing + //if(_RateProviderFactory is BTCPayRateProviderFactory) + //{ + // try + // { + // await new CoinAverageRateProvider("BTC").GetExchangeTickersAsync(); + // } + // catch(Exception ex) + // { + // Logs.PayServer.LogWarning(ex, "Failed to get exchange tickers"); + // } + //} } public Task StopAsync(CancellationToken cancellationToken) diff --git a/BTCPayServer/Services/Rates/BTCPayRateProviderFactory.cs b/BTCPayServer/Services/Rates/BTCPayRateProviderFactory.cs index d911f13b8..f62aa1166 100644 --- a/BTCPayServer/Services/Rates/BTCPayRateProviderFactory.cs +++ b/BTCPayServer/Services/Rates/BTCPayRateProviderFactory.cs @@ -51,9 +51,28 @@ namespace BTCPayServer.Services.Rates _Cache = new MemoryCache(_CacheOptions); } - public IRateProvider GetRateProvider(BTCPayNetwork network) + public IRateProvider GetRateProvider(BTCPayNetwork network, RateRules rules) { - return new CachedRateProvider(network.CryptoCode, GetDefaultRateProvider(network), _Cache) { CacheSpan = CacheSpan }; + rules = rules ?? new RateRules(); + var rateProvider = GetDefaultRateProvider(network); + if (!rules.PreferredExchange.IsCoinAverage()) + { + rateProvider = CreateExchangeRateProvider(network, rules.PreferredExchange); + } + rateProvider = CreateCachedRateProvider(network, rateProvider, rules.PreferredExchange); + return new TweakRateProvider(network, rateProvider, rules); + } + + private IRateProvider CreateExchangeRateProvider(BTCPayNetwork network, string exchange) + { + var coinAverage = new CoinAverageRateProviderDescription(network.CryptoCode).CreateRateProvider(serviceProvider); + coinAverage.Exchange = exchange; + return coinAverage; + } + + private CachedRateProvider CreateCachedRateProvider(BTCPayNetwork network, IRateProvider rateProvider, string additionalScope) + { + return new CachedRateProvider(network.CryptoCode, rateProvider, _Cache) { CacheSpan = CacheSpan, AdditionalScope = additionalScope }; } private IRateProvider GetDefaultRateProvider(BTCPayNetwork network) diff --git a/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs b/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs index e26356e6c..7d98f25a9 100644 --- a/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs +++ b/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs @@ -30,13 +30,31 @@ namespace BTCPayServer.Services.Rates public string CryptoCode { get; set; } - public IRateProvider CreateRateProvider(IServiceProvider serviceProvider) + public CoinAverageRateProvider CreateRateProvider(IServiceProvider serviceProvider) { return new CoinAverageRateProvider(CryptoCode) { Authenticator = serviceProvider.GetService() }; } + + IRateProvider RateProviderDescription.CreateRateProvider(IServiceProvider serviceProvider) + { + return CreateRateProvider(serviceProvider); + } + } + + public class GetExchangeTickersResponse + { + public class Exchange + { + public string Name { get; set; } + [JsonProperty("display_name")] + public string DisplayName { get; set; } + public string[] Symbols { get; set; } + } + public bool Success { get; set; } + public Exchange[] Exchanges { get; set; } } public class RatesSetting @@ -181,5 +199,26 @@ namespace BTCPayServer.Services.Rates var resp = await _Client.SendAsync(request); resp.EnsureSuccessStatusCode(); } + + public async Task GetExchangeTickersAsync() + { + var request = new HttpRequestMessage(HttpMethod.Get, "https://apiv2.bitcoinaverage.com/symbols/exchanges/ticker"); + var resp = await _Client.SendAsync(request); + resp.EnsureSuccessStatusCode(); + var jobj = JObject.Parse(await resp.Content.ReadAsStringAsync()); + var response = new GetExchangeTickersResponse(); + response.Success = jobj["success"].Value(); + var exchanges = (JObject)jobj["exchanges"]; + response.Exchanges = exchanges + .Properties() + .Select(p => + { + var exchange = JsonConvert.DeserializeObject(p.Value.ToString()); + exchange.Name = p.Name; + return exchange; + }) + .ToArray(); + return response; + } } } diff --git a/BTCPayServer/Services/Rates/IRateProviderFactory.cs b/BTCPayServer/Services/Rates/IRateProviderFactory.cs index 55a364668..5c3b76a77 100644 --- a/BTCPayServer/Services/Rates/IRateProviderFactory.cs +++ b/BTCPayServer/Services/Rates/IRateProviderFactory.cs @@ -1,12 +1,40 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using BTCPayServer.Data; namespace BTCPayServer.Services.Rates { + public class RateRules : IEnumerable + { + private List rateRules; + + public RateRules() + { + rateRules = new List(); + } + public RateRules(List rateRules) + { + this.rateRules = rateRules?.ToList() ?? new List(); + } + public string PreferredExchange { get; set; } + + public IEnumerator GetEnumerator() + { + return rateRules.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } public interface IRateProviderFactory { - IRateProvider GetRateProvider(BTCPayNetwork network); + IRateProvider GetRateProvider(BTCPayNetwork network, RateRules rules); + TimeSpan CacheSpan { get; set; } + void InvalidateCache(); } } diff --git a/BTCPayServer/Services/Rates/MockRateProvider.cs b/BTCPayServer/Services/Rates/MockRateProvider.cs index c735c980a..28d8298d1 100644 --- a/BTCPayServer/Services/Rates/MockRateProvider.cs +++ b/BTCPayServer/Services/Rates/MockRateProvider.cs @@ -14,14 +14,21 @@ namespace BTCPayServer.Services.Rates } + public TimeSpan CacheSpan { get; set; } + public void AddMock(MockRateProvider mock) { _Mocks.Add(mock); } - public IRateProvider GetRateProvider(BTCPayNetwork network) + public IRateProvider GetRateProvider(BTCPayNetwork network, RateRules rules) { return _Mocks.FirstOrDefault(m => m.CryptoCode == network.CryptoCode); } + + public void InvalidateCache() + { + + } } public class MockRateProvider : IRateProvider { diff --git a/BTCPayServer/Services/Rates/TweakRateProvider.cs b/BTCPayServer/Services/Rates/TweakRateProvider.cs index 292f9787f..dcca887cd 100644 --- a/BTCPayServer/Services/Rates/TweakRateProvider.cs +++ b/BTCPayServer/Services/Rates/TweakRateProvider.cs @@ -10,9 +10,9 @@ namespace BTCPayServer.Services.Rates { private BTCPayNetwork network; private IRateProvider rateProvider; - private List rateRules; + private RateRules rateRules; - public TweakRateProvider(BTCPayNetwork network, IRateProvider rateProvider, List rateRules) + public TweakRateProvider(BTCPayNetwork network, IRateProvider rateProvider, RateRules rateRules) { if (network == null) throw new ArgumentNullException(nameof(network)); From b8bf4d99ac220c67cb3fe104a7f244c6382005df Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sun, 15 Apr 2018 21:29:44 +0900 Subject: [PATCH 03/17] Bump --- BTCPayServer.Tests/UnitTest1.cs | 5 ++++- BTCPayServer/BTCPayServer.csproj | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 044a67ab5..7571bf6f4 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -628,7 +628,10 @@ namespace BTCPayServer.Tests user.RegisterDerivationScheme("BTC"); List rates = new List(); rates.Add(CreateInvoice(tester, user, "coinaverage")); - rates.Add(CreateInvoice(tester, user, "bitflyer")); + var bitflyer = CreateInvoice(tester, user, "bitflyer"); + var bitflyer2 = CreateInvoice(tester, user, "bitflyer"); + Assert.Equal(bitflyer, bitflyer2); // Should be equal because cache + rates.Add(bitflyer); foreach(var rate in rates) { diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 2cc18d247..690bc6596 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.0 - 1.0.1.83 + 1.0.1.84 NU1701,CA1816,CA1308,CA1810,CA2208 From fd5c4021f781639342d618b7d00780e026d53058 Mon Sep 17 00:00:00 2001 From: LinoxBE Date: Sun, 15 Apr 2018 20:00:11 +0200 Subject: [PATCH 04/17] Dutch update txCount --- BTCPayServer/wwwroot/checkout/js/langs/nl.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/BTCPayServer/wwwroot/checkout/js/langs/nl.js b/BTCPayServer/wwwroot/checkout/js/langs/nl.js index 4004f558b..33b0187b8 100644 --- a/BTCPayServer/wwwroot/checkout/js/langs/nl.js +++ b/BTCPayServer/wwwroot/checkout/js/langs/nl.js @@ -47,5 +47,8 @@ Je kan terug komen naar {{storeName}} indien je nog eens je betaling wilt prober "Archived_Body": "Bedankt om de winkel te contacteren voor bijstand met of informatie over deze bestelling", // Lightning "BOLT 11 Invoice": "BOLT 11 Factuur", - "Node Info": "Node Info" + "Node Info": "Node Info", + // + "txCount": "{{count}} transactie", + "txCount_plural": "{{count}} transacties" }; From 6267cccc3fdb1f1a0fb67c968513ecd5c70702fe Mon Sep 17 00:00:00 2001 From: mutedstorm Date: Sun, 15 Apr 2018 22:47:08 +0200 Subject: [PATCH 05/17] fix german translation minor changes, thanks to (@raindogdance) --- BTCPayServer/wwwroot/checkout/js/langs/de.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BTCPayServer/wwwroot/checkout/js/langs/de.js b/BTCPayServer/wwwroot/checkout/js/langs/de.js index fb6a188b4..902ace746 100644 --- a/BTCPayServer/wwwroot/checkout/js/langs/de.js +++ b/BTCPayServer/wwwroot/checkout/js/langs/de.js @@ -6,7 +6,7 @@ const locales_de = { "Pay with": "Bezahlen mit", "Contact and Refund Email": "Kontakt und Rückerstattungs Email", "Contact_Body": "Bitte geben Sie unten eine E-Mail-Adresse an. Wir werden Sie unter dieser Adresse kontaktieren, falls ein Problem mit Ihrer Zahlung vorliegt.", - "Your email": "Deine Email-Adresse", + "Your email": "Ihre Email-Adresse", "Continue": "Fortsetzen", "Please enter a valid email address": "Bitte geben Sie eine gültige E-Mail-Adresse ein", "Order Amount": "Bestellbetrag", @@ -25,7 +25,7 @@ const locales_de = { "Address": "Adresse", "Copied": "Kopiert", // Conversion tab - "ConversionTab_BodyTop": "Sie können {{btcDue}} {{cryptoCode}} mit altcoins bezahlen, die nicht direkt vom Händler unterstützt werden.", + "ConversionTab_BodyTop": "Sie können {{btcDue}} {{cryptoCode}} mit Altcoins bezahlen, die nicht direkt vom Händler unterstützt werden.", "ConversionTab_BodyDesc": "Dieser Service wird von Drittanbietern bereitgestellt. Bitte beachten Sie, dass wir keine Kontrolle darüber haben, wie die Anbieter Ihre Gelder weiterleiten. Die Rechnung wird erst als bezahlt markiert, wenn das Geld in {{cryptoCode}} Blockchain eingegangen ist.", "Shapeshift_Button_Text": "Bezahlen mit Altcoins", "ConversionTab_Lightning": "Für Lightning Network-Zahlungen sind keine Umrechnungsanbieter verfügbar.", From d7719d25b49060d7d9f0d81711fffa70487b0ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio?= <26830122+bitmario@users.noreply.github.com> Date: Mon, 16 Apr 2018 01:29:42 +0100 Subject: [PATCH 06/17] Add Portuguese (Portugal) translation --- BTCPayServer/Services/LanguageService.cs | 1 + BTCPayServer/Views/Invoice/Checkout.cshtml | 1 + BTCPayServer/wwwroot/checkout/js/langs/pt.js | 54 ++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 BTCPayServer/wwwroot/checkout/js/langs/pt.js diff --git a/BTCPayServer/Services/LanguageService.cs b/BTCPayServer/Services/LanguageService.cs index 2a14ffebd..c738be363 100644 --- a/BTCPayServer/Services/LanguageService.cs +++ b/BTCPayServer/Services/LanguageService.cs @@ -26,6 +26,7 @@ namespace BTCPayServer.Services new Language("ja-JP", "日本語"), new Language("fr-FR", "Français"), new Language("es-ES", "Spanish"), + new Language("pt-PT", "Portuguese"), new Language("pt-BR", "Portuguese (Brazil)"), new Language("nl-NL", "Dutch"), new Language("cs-CZ", "Česky"), diff --git a/BTCPayServer/Views/Invoice/Checkout.cshtml b/BTCPayServer/Views/Invoice/Checkout.cshtml index 9c79b9585..aa212c0a7 100644 --- a/BTCPayServer/Views/Invoice/Checkout.cshtml +++ b/BTCPayServer/Views/Invoice/Checkout.cshtml @@ -110,6 +110,7 @@ 'es-ES': { translation: locales_es }, 'ja-JP': { translation: locales_ja }, 'fr-FR': { translation: locales_fr }, + 'pt': ( translation: locales_pt }, 'pt-BR': { translation: locales_pt_br }, 'nl': { translation: locales_nl }, 'cs-CZ': { translation: locales_cs }, diff --git a/BTCPayServer/wwwroot/checkout/js/langs/pt.js b/BTCPayServer/wwwroot/checkout/js/langs/pt.js new file mode 100644 index 000000000..5441fede5 --- /dev/null +++ b/BTCPayServer/wwwroot/checkout/js/langs/pt.js @@ -0,0 +1,54 @@ +const locales_pt = { + nested: { + lang: 'Idioma' + }, + "Awaiting Payment...": "A Aguardar Pagamento...", + "Pay with": "Pague com", + "Contact and Refund Email": "E-mail de Contacto e Reembolso", + "Contact_Body": "Por favor indique um e-mail abaixo. Entraremos em contacto para este endereço se ocorrer algum problema com o seu pagamento.", + "Your email": "O seu e-mail", + "Continue": "Continuar", + "Please enter a valid email address": "Por favor introduza um e-mail válido", + "Order Amount": "Valor da Encomenda", + "Network Cost": "Custo da Rede", + "Already Paid": "Já Pago", + "Due": "Devido", + // Tabs + "Scan": "Digitalizar", + "Copy": "Copiar", + "Conversion": "Conversão", + // Scan tab + "Open in wallet": "Abrir na carteira", + // Copy tab + "CompletePay_Body": "Para completar o seu pagamento, por favor envie {{btcDue}} {{cryptoCode}} para o endereço abaixo.", + "Amount": "Quantia", + "Address": "Endereço", + "Copied": "Copiado", + // Conversion tab + "ConversionTab_BodyTop": "Pode pagar {{btcDue}} {{cryptoCode}} utilizando outras altcoins além das que a loja aceita diretamente.", + "ConversionTab_BodyDesc": "Este serviço é oferecido por terceiros. Por favor tenha em mente que não temos qualquer controlo sobre como os seus fundos serão utilizados. A fatura será marcada como paga apenas quando os fundos forem recebidos na Blockchain {{cryptoCode}}.", + "Shapeshift_Button_Text": "Pagar com Altcoins", + "ConversionTab_Lightning": "Não há fornecedores de conversão disponíveis para pagamentos via Lightning Network.", + // Invoice expired + "Invoice expiring soon...": "A fatura está a expirar...", + "Invoice expired": "Fatura expirada", + "What happened?": "O que aconteceu?", + "InvoiceExpired_Body_1": "Esta fatura expirou. Uma fatura é válida durante {{maxTimeMinutes}} minutos. \ +Pode voltar para {{storeName}} se quiser enviar o seu pagamento novamente.", + "InvoiceExpired_Body_2": "Se tentou enviar um pagamento, ele ainda não foi aceite pela rede Bitcoin. Nós ainda não recebemos o valor enviado.", + "InvoiceExpired_Body_3": "Se a transação não for aceite pela rede Bitcoin, o valor voltará para sua carteira. Dependendo da sua carteira, isto pode demorar entre 48 e 72 horas.", + "Invoice ID": "Nº da Fatura", + "Order ID": "Nº da Encomenda", + "Return to StoreName": "Voltar para {{storeName}}", + // Invoice paid + "This invoice has been paid": "Esta fatura foi paga", + // Invoice archived + "This invoice has been archived": "Esta fatura foi arquivada", + "Archived_Body": "Por favor, entre em contacto com o vendedor para informações e suporte", + // Lightning + "BOLT 11 Invoice": "Fatura BOLT 11", + "Node Info": "Informação do Nó", + // + "txCount": "{{count}} transação", + "txCount_plural": "{{count}} transações" +}; From 5ed56d1137a42596a75d6ec7d5039e9655d93cdd Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Mon, 16 Apr 2018 11:26:29 +0900 Subject: [PATCH 07/17] Update JA translations --- BTCPayServer/wwwroot/checkout/js/langs/ja.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/BTCPayServer/wwwroot/checkout/js/langs/ja.js b/BTCPayServer/wwwroot/checkout/js/langs/ja.js index fe30c39f0..b5c91d127 100644 --- a/BTCPayServer/wwwroot/checkout/js/langs/ja.js +++ b/BTCPayServer/wwwroot/checkout/js/langs/ja.js @@ -47,5 +47,8 @@ "Archived_Body": "ご注文に関わる詳細などでお困りの場合はお店の担当窓口へお問い合わせください。", // Lightning "BOLT 11 Invoice": "お支払いコード", - "Node Info": "接続情報" + "Node Info": "接続情報", + // + "txCount": "取引 {{count}} 個", + "txCount_plural": "取引 {{count}} 個" }; From b438312fde70573aac4974b2a728990da568ffaf Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Mon, 16 Apr 2018 11:38:10 +0900 Subject: [PATCH 08/17] fix js --- BTCPayServer/Views/Invoice/Checkout.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayServer/Views/Invoice/Checkout.cshtml b/BTCPayServer/Views/Invoice/Checkout.cshtml index aa212c0a7..8a4484ba1 100644 --- a/BTCPayServer/Views/Invoice/Checkout.cshtml +++ b/BTCPayServer/Views/Invoice/Checkout.cshtml @@ -110,7 +110,7 @@ 'es-ES': { translation: locales_es }, 'ja-JP': { translation: locales_ja }, 'fr-FR': { translation: locales_fr }, - 'pt': ( translation: locales_pt }, + 'pt': { translation: locales_pt }, 'pt-BR': { translation: locales_pt_br }, 'nl': { translation: locales_nl }, 'cs-CZ': { translation: locales_cs }, From 415cde16299f3b75f8a320d2f48182114c14749b Mon Sep 17 00:00:00 2001 From: pajasevi Date: Mon, 16 Apr 2018 09:11:46 +0200 Subject: [PATCH 09/17] Transaction count CS translation --- BTCPayServer/wwwroot/checkout/js/langs/cs.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/BTCPayServer/wwwroot/checkout/js/langs/cs.js b/BTCPayServer/wwwroot/checkout/js/langs/cs.js index 9a0102171..f260777c5 100644 --- a/BTCPayServer/wwwroot/checkout/js/langs/cs.js +++ b/BTCPayServer/wwwroot/checkout/js/langs/cs.js @@ -47,5 +47,8 @@ Můžete se vrátit do {{storeName}}, pokud chcete svojí objednávku založit z "Archived_Body": "Prosíme kontaktujte prodejce pro informace o objednávce a případnou pomoc", // Lightning "BOLT 11 Invoice": "BOLT 11 Faktura", - "Node Info": "Info o uzlu" + "Node Info": "Info o uzlu", + // + "txCount": "{{count}} transakce", + "txCount_plural": "{{count}} transakcí" }; From ead97a24bd66da03c24529eb35508c9afcdc5213 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Mon, 16 Apr 2018 19:15:44 +0900 Subject: [PATCH 10/17] Update NBitcoin --- BTCPayServer/BTCPayServer.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 690bc6596..767120e1e 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.0 - 1.0.1.84 + 1.0.1.85 NU1701,CA1816,CA1308,CA1810,CA2208 @@ -34,7 +34,7 @@ - + From 5cb8cdd511fdfe26d12bbf12a10fcab519270407 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Wed, 18 Apr 2018 16:07:16 +0900 Subject: [PATCH 11/17] Refactoring: Do not query database when asking for Coinaverage rates, periodically get exchange list --- BTCPayServer.Tests/UnitTest1.cs | 2 +- BTCPayServer/Controllers/ServerController.cs | 27 ++----- .../HostedServices/RatesHostedService.cs | 78 ++++++++++++++----- BTCPayServer/Hosting/BTCPayServerServices.cs | 3 +- .../Services/Rates/CoinAverageRateProvider.cs | 30 ------- .../Services/Rates/CoinAverageSettings.cs | 42 ++++++++++ BTCPayServer/Services/SettingsRepository.cs | 47 +++++++++++ BTCPayServer/Views/Server/Rates.cshtml | 2 +- 8 files changed, 157 insertions(+), 74 deletions(-) create mode 100644 BTCPayServer/Services/Rates/CoinAverageSettings.cs diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 7571bf6f4..6c1bfa5cf 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -632,7 +632,7 @@ namespace BTCPayServer.Tests var bitflyer2 = CreateInvoice(tester, user, "bitflyer"); Assert.Equal(bitflyer, bitflyer2); // Should be equal because cache rates.Add(bitflyer); - + foreach(var rate in rates) { Assert.Single(rates.Where(r => r == rate)); diff --git a/BTCPayServer/Controllers/ServerController.cs b/BTCPayServer/Controllers/ServerController.cs index de4bf4b49..a08f7f6b9 100644 --- a/BTCPayServer/Controllers/ServerController.cs +++ b/BTCPayServer/Controllers/ServerController.cs @@ -23,7 +23,6 @@ namespace BTCPayServer.Controllers { private UserManager _UserManager; SettingsRepository _SettingsRepository; - private IRateProviderFactory _RateProviderFactory; public ServerController(UserManager userManager, IRateProviderFactory rateProviderFactory, @@ -31,7 +30,6 @@ namespace BTCPayServer.Controllers { _UserManager = userManager; _SettingsRepository = settingsRepository; - _RateProviderFactory = rateProviderFactory; } [Route("server/rates")] @@ -47,22 +45,6 @@ namespace BTCPayServer.Controllers } - class TestCoinAverageAuthenticator : ICoinAverageAuthenticator - { - private RatesSetting settings; - - public TestCoinAverageAuthenticator(RatesSetting settings) - { - this.settings = settings; - } - public Task AddHeader(HttpRequestMessage message) - { - var sig = settings.GetCoinAverageSignature(); - if (sig != null) - message.Headers.Add("X-signature", settings.GetCoinAverageSignature()); - return Task.CompletedTask; - } - } [Route("server/rates")] [HttpPost] public async Task Rates(RatesViewModel vm) @@ -73,10 +55,14 @@ namespace BTCPayServer.Controllers rates.CacheInMinutes = vm.CacheMinutes; try { - if (rates.GetCoinAverageSignature() != null) + var settings = new CoinAverageSettings() + { + KeyPair = (vm.PublicKey, vm.PrivateKey) + }; + if (settings.GetCoinAverageSignature() != null) { await new CoinAverageRateProvider("BTC") - { Authenticator = new TestCoinAverageAuthenticator(rates) }.TestAuthAsync(); + { Authenticator = settings }.TestAuthAsync(); } } catch @@ -86,7 +72,6 @@ namespace BTCPayServer.Controllers if (!ModelState.IsValid) return View(vm); await _SettingsRepository.UpdateSetting(rates); - ((BTCPayRateProviderFactory)_RateProviderFactory).CacheSpan = TimeSpan.FromMinutes(vm.CacheMinutes); StatusMessage = "Rate settings successfully updated"; return RedirectToAction(nameof(Rates)); } diff --git a/BTCPayServer/HostedServices/RatesHostedService.cs b/BTCPayServer/HostedServices/RatesHostedService.cs index 18b0b3a6b..373e6287d 100644 --- a/BTCPayServer/HostedServices/RatesHostedService.cs +++ b/BTCPayServer/HostedServices/RatesHostedService.cs @@ -8,6 +8,7 @@ using BTCPayServer.Services; using BTCPayServer.Services.Rates; using Microsoft.Extensions.Hosting; using BTCPayServer.Logging; +using System.Runtime.CompilerServices; namespace BTCPayServer.HostedServices { @@ -15,41 +16,78 @@ namespace BTCPayServer.HostedServices { private SettingsRepository _SettingsRepository; private IRateProviderFactory _RateProviderFactory; - public RatesHostedService(SettingsRepository repo, + private CoinAverageSettings _coinAverageSettings; + public RatesHostedService(SettingsRepository repo, + CoinAverageSettings coinAverageSettings, IRateProviderFactory rateProviderFactory) { this._SettingsRepository = repo; _RateProviderFactory = rateProviderFactory; + _coinAverageSettings = coinAverageSettings; } + + + CancellationTokenSource _Cts = new CancellationTokenSource(); + + List _Tasks = new List(); + public Task StartAsync(CancellationToken cancellationToken) { - Init(); + _Tasks.Add(RefreshCoinAverageSupportedExchanges(_Cts.Token)); + _Tasks.Add(RefreshCoinAverageSettings(_Cts.Token)); return Task.CompletedTask; } - async void Init() - { - var rates = (await _SettingsRepository.GetSettingAsync()) ?? new RatesSetting(); - _RateProviderFactory.CacheSpan = TimeSpan.FromMinutes(rates.CacheInMinutes); - //string[] availableExchanges = null; - //// So we don't run this in testing - //if(_RateProviderFactory is BTCPayRateProviderFactory) - //{ - // try - // { - // await new CoinAverageRateProvider("BTC").GetExchangeTickersAsync(); - // } - // catch(Exception ex) - // { - // Logs.PayServer.LogWarning(ex, "Failed to get exchange tickers"); - // } - //} + async Task Timer(Func act, CancellationToken cancellation, [CallerMemberName]string caller = null) + { + while (!cancellation.IsCancellationRequested) + { + try + { + await act(); + } + catch (OperationCanceledException) when (cancellation.IsCancellationRequested) + { + } + catch (Exception ex) + { + Logs.PayServer.LogWarning(ex, caller + " failed"); + try + { + await Task.Delay(TimeSpan.FromMinutes(1), cancellation); + } + catch (OperationCanceledException) when (cancellation.IsCancellationRequested) { } + } + } + } + Task RefreshCoinAverageSupportedExchanges(CancellationToken cancellation) + { + return Timer(async () => + { + var tickers = await new CoinAverageRateProvider("BTC").GetExchangeTickersAsync(); + await Task.Delay(TimeSpan.FromHours(5), cancellation); + }, cancellation); + } + + Task RefreshCoinAverageSettings(CancellationToken cancellation) + { + return Timer(async () => + { + var rates = (await _SettingsRepository.GetSettingAsync()) ?? new RatesSetting(); + _RateProviderFactory.CacheSpan = TimeSpan.FromMinutes(rates.CacheInMinutes); + if (!string.IsNullOrWhiteSpace(rates.PrivateKey) && !string.IsNullOrWhiteSpace(rates.PublicKey)) + { + _coinAverageSettings.KeyPair = (rates.PublicKey, rates.PrivateKey); + } + await _SettingsRepository.WaitSettingsChanged(cancellation); + }, cancellation); } public Task StopAsync(CancellationToken cancellationToken) { - return Task.CompletedTask; + _Cts.Cancel(); + return Task.WhenAll(_Tasks.ToArray()); } } } diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index d98db23a2..76dc65f92 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -109,7 +109,8 @@ namespace BTCPayServer.Hosting services.AddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(o => { var opts = o.GetRequiredService(); diff --git a/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs b/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs index 7d98f25a9..7cdbb29aa 100644 --- a/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs +++ b/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs @@ -59,41 +59,11 @@ namespace BTCPayServer.Services.Rates public class RatesSetting { - private static readonly DateTime _epochUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public string PublicKey { get; set; } public string PrivateKey { get; set; } [DefaultValue(15)] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] public int CacheInMinutes { get; set; } = 15; - - public string GetCoinAverageSignature() - { - if (string.IsNullOrEmpty(PublicKey) || string.IsNullOrEmpty(PrivateKey)) - return null; - var timestamp = (int)((DateTime.UtcNow - _epochUtc).TotalSeconds); - var payload = timestamp + "." + PublicKey; - var digestValueBytes = new HMACSHA256(Encoding.ASCII.GetBytes(PrivateKey)).ComputeHash(Encoding.ASCII.GetBytes(payload)); - var digestValueHex = NBitcoin.DataEncoders.Encoders.Hex.EncodeData(digestValueBytes); - return payload + "." + digestValueHex; - } - } - public class BTCPayCoinAverageAuthenticator : ICoinAverageAuthenticator - { - private SettingsRepository settingsRepo; - - public BTCPayCoinAverageAuthenticator(SettingsRepository settingsRepo) - { - this.settingsRepo = settingsRepo; - } - public async Task AddHeader(HttpRequestMessage message) - { - var settings = (await settingsRepo.GetSettingAsync()) ?? new RatesSetting(); - var signature = settings.GetCoinAverageSignature(); - if (signature != null) - { - message.Headers.Add("X-signature", signature); - } - } } public interface ICoinAverageAuthenticator diff --git a/BTCPayServer/Services/Rates/CoinAverageSettings.cs b/BTCPayServer/Services/Rates/CoinAverageSettings.cs new file mode 100644 index 000000000..ff69aa38f --- /dev/null +++ b/BTCPayServer/Services/Rates/CoinAverageSettings.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace BTCPayServer.Services.Rates +{ + public class CoinAverageSettings : ICoinAverageAuthenticator + { + private static readonly DateTime _epochUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public (String PublicKey, String PrivateKey)? KeyPair { get; set; } + public string[] AvailableExchanges { get; set; } = Array.Empty(); + + public Task AddHeader(HttpRequestMessage message) + { + var signature = GetCoinAverageSignature(); + if (signature != null) + { + message.Headers.Add("X-signature", signature); + } + return Task.CompletedTask; + } + + public string GetCoinAverageSignature() + { + var keyPair = KeyPair; + if (!keyPair.HasValue) + return null; + if (string.IsNullOrEmpty(keyPair.Value.PublicKey) || string.IsNullOrEmpty(keyPair.Value.PrivateKey)) + return null; + var timestamp = (int)((DateTime.UtcNow - _epochUtc).TotalSeconds); + var payload = timestamp + "." + keyPair.Value.PublicKey; + var digestValueBytes = new HMACSHA256(Encoding.ASCII.GetBytes(keyPair.Value.PrivateKey)).ComputeHash(Encoding.ASCII.GetBytes(payload)); + var digestValueHex = NBitcoin.DataEncoders.Encoders.Hex.EncodeData(digestValueBytes); + return payload + "." + digestValueHex; + } + } +} diff --git a/BTCPayServer/Services/SettingsRepository.cs b/BTCPayServer/Services/SettingsRepository.cs index 51e80bfa7..a6911abc2 100644 --- a/BTCPayServer/Services/SettingsRepository.cs +++ b/BTCPayServer/Services/SettingsRepository.cs @@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore; using BTCPayServer.Models; using Microsoft.EntityFrameworkCore.Infrastructure.Internal; using Newtonsoft.Json; +using System.Threading; namespace BTCPayServer.Services { @@ -51,6 +52,22 @@ namespace BTCPayServer.Services await ctx.SaveChangesAsync(); } } + + IReadOnlyCollection> value; + lock (_Subscriptions) + { + if(_Subscriptions.TryGetValue(typeof(T), out value)) + { + _Subscriptions.Remove(typeof(T)); + } + } + if(value != null) + { + foreach(var v in value) + { + v.TrySetResult(true); + } + } } private T Deserialize(string value) @@ -62,5 +79,35 @@ namespace BTCPayServer.Services { return JsonConvert.SerializeObject(obj); } + + MultiValueDictionary> _Subscriptions = new MultiValueDictionary>(); + public async Task WaitSettingsChanged(CancellationToken cancellation) + { + var tcs = new TaskCompletionSource(); + using (cancellation.Register(() => + { + try + { + tcs.TrySetCanceled(); + } + catch { } + })) + { + lock (_Subscriptions) + { + _Subscriptions.Add(typeof(T), tcs); + } +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + tcs.Task.ContinueWith(_ => + { + lock (_Subscriptions) + { + _Subscriptions.Remove(typeof(T), tcs); + } + }, TaskScheduler.Default); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + await tcs.Task; + } + } } } diff --git a/BTCPayServer/Views/Server/Rates.cshtml b/BTCPayServer/Views/Server/Rates.cshtml index 15b2a5522..a8dfc17f7 100644 --- a/BTCPayServer/Views/Server/Rates.cshtml +++ b/BTCPayServer/Views/Server/Rates.cshtml @@ -29,7 +29,7 @@ -

You can find the information on bitcoinaverage api key page

+

You can find the information on bitcoinaverage api key page

From 73ed4003a36ca5fc0356b62834fef010664682e9 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Wed, 18 Apr 2018 16:38:17 +0900 Subject: [PATCH 12/17] Use a drop down for preferred exchange list --- BTCPayServer/Controllers/StoresController.cs | 27 ++++++++++++------- .../HostedServices/RatesHostedService.cs | 7 ++++- .../Models/StoreViewModels/StoreViewModel.cs | 16 +++++++++++ .../Services/Rates/CoinAverageSettings.cs | 2 +- BTCPayServer/Views/Stores/UpdateStore.cshtml | 10 +++---- 5 files changed, 45 insertions(+), 17 deletions(-) diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index 1c1ae5e90..d7c93dccd 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -5,6 +5,7 @@ using BTCPayServer.HostedServices; using BTCPayServer.Models; using BTCPayServer.Models.StoreViewModels; using BTCPayServer.Services; +using BTCPayServer.Services.Rates; using BTCPayServer.Services.Stores; using BTCPayServer.Services.Wallets; using Microsoft.AspNetCore.Authorization; @@ -47,7 +48,8 @@ namespace BTCPayServer.Controllers ExplorerClientProvider explorerProvider, IFeeProviderFactory feeRateProvider, LanguageService langService, - IHostingEnvironment env) + IHostingEnvironment env, + CoinAverageSettings coinAverage) { _Dashboard = dashboard; _Repo = repo; @@ -64,7 +66,9 @@ namespace BTCPayServer.Controllers _ServiceProvider = serviceProvider; _BtcpayServerOptions = btcpayServerOptions; _BTCPayEnv = btcpayEnv; + _CoinAverage = coinAverage; } + CoinAverageSettings _CoinAverage; NBXplorerDashboard _Dashboard; BTCPayServerOptions _BtcpayServerOptions; BTCPayServerEnvironment _BTCPayEnv; @@ -237,7 +241,7 @@ namespace BTCPayServer.Controllers model.SetCryptoCurrencies(_ExplorerProvider, model.DefaultCryptoCurrency); model.SetLanguages(_LangService, model.DefaultLang); - if(!ModelState.IsValid) + if (!ModelState.IsValid) { return View(model); } @@ -273,6 +277,7 @@ namespace BTCPayServer.Controllers var storeBlob = store.GetStoreBlob(); var vm = new StoreViewModel(); + vm.SetExchangeRates(GetSupportedExchanges(), storeBlob.PreferredExchange.IsCoinAverage() ? "coinaverage" : storeBlob.PreferredExchange); vm.Id = store.Id; vm.StoreName = store.StoreName; vm.StoreWebsite = store.StoreWebsite; @@ -283,7 +288,6 @@ namespace BTCPayServer.Controllers vm.InvoiceExpiration = storeBlob.InvoiceExpiration; vm.RateMultiplier = (double)storeBlob.GetRateMultiplier(); vm.LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate; - vm.PreferredExchange = storeBlob.PreferredExchange.IsCoinAverage() ? "coinaverage" : storeBlob.PreferredExchange; return View(vm); } @@ -325,6 +329,7 @@ namespace BTCPayServer.Controllers [Route("{storeId}")] public async Task UpdateStore(string storeId, StoreViewModel model) { + model.SetExchangeRates(GetSupportedExchanges(), model.PreferredExchange); if (!ModelState.IsValid) { return View(model); @@ -371,14 +376,11 @@ namespace BTCPayServer.Controllers if (!blob.PreferredExchange.IsCoinAverage() && newExchange) { - using (HttpClient client = new HttpClient()) + + if (!GetSupportedExchanges().Select(c => c.Name).Contains(blob.PreferredExchange, StringComparer.OrdinalIgnoreCase)) { - var rate = await client.GetAsync(model.RateSource); - if (rate.StatusCode == System.Net.HttpStatusCode.NotFound) - { - ModelState.AddModelError(nameof(model.PreferredExchange), $"Unsupported exchange ({model.RateSource})"); - return View(model); - } + ModelState.AddModelError(nameof(model.PreferredExchange), $"Unsupported exchange ({model.RateSource})"); + return View(model); } } @@ -394,6 +396,11 @@ namespace BTCPayServer.Controllers }); } + private (String DisplayName, String Name)[] GetSupportedExchanges() + { + return new[] { ("Coin Average", "coinaverage") }.Concat(_CoinAverage.AvailableExchanges).ToArray(); + } + private DerivationStrategy ParseDerivationStrategy(string derivationScheme, Script hint, BTCPayNetwork network) { var parser = new DerivationSchemeParser(network.NBitcoinNetwork, network.DefaultSettings.ChainType); diff --git a/BTCPayServer/HostedServices/RatesHostedService.cs b/BTCPayServer/HostedServices/RatesHostedService.cs index 373e6287d..5f98d24b2 100644 --- a/BTCPayServer/HostedServices/RatesHostedService.cs +++ b/BTCPayServer/HostedServices/RatesHostedService.cs @@ -63,9 +63,14 @@ namespace BTCPayServer.HostedServices } Task RefreshCoinAverageSupportedExchanges(CancellationToken cancellation) { - return Timer(async () => + return Timer(async () => { var tickers = await new CoinAverageRateProvider("BTC").GetExchangeTickersAsync(); + _coinAverageSettings.AvailableExchanges = tickers + .Exchanges + .Select(c => (c.DisplayName, c.Name)) + .ToArray(); + await Task.Delay(TimeSpan.FromHours(5), cancellation); }, cancellation); } diff --git a/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs b/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs index dc4a71e80..1ed1dce96 100644 --- a/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs @@ -12,6 +12,11 @@ namespace BTCPayServer.Models.StoreViewModels { public class StoreViewModel { + class Format + { + public string Name { get; set; } + public string Value { get; set; } + } public class DerivationScheme { public string Crypto { get; set; } @@ -44,6 +49,17 @@ namespace BTCPayServer.Models.StoreViewModels public List DerivationSchemes { get; set; } = new List(); + public void SetExchangeRates((String DisplayName, String Name)[] supportedList, string preferredExchange) + { + var defaultStore = preferredExchange ?? "coinaverage"; + var choices = supportedList.Select(o => new Format() { Name = o.DisplayName, Value = o.Name }).ToArray(); + var chosen = choices.FirstOrDefault(f => f.Value == defaultStore) ?? choices.FirstOrDefault(); + Exchanges = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen); + PreferredExchange = chosen.Value; + } + + public SelectList Exchanges { get; set; } + [Display(Name = "Preferred price source (eg. bitfinex, bitstamp...)")] public string PreferredExchange { get; set; } diff --git a/BTCPayServer/Services/Rates/CoinAverageSettings.cs b/BTCPayServer/Services/Rates/CoinAverageSettings.cs index ff69aa38f..0d666fb45 100644 --- a/BTCPayServer/Services/Rates/CoinAverageSettings.cs +++ b/BTCPayServer/Services/Rates/CoinAverageSettings.cs @@ -13,7 +13,7 @@ namespace BTCPayServer.Services.Rates private static readonly DateTime _epochUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public (String PublicKey, String PrivateKey)? KeyPair { get; set; } - public string[] AvailableExchanges { get; set; } = Array.Empty(); + public (String DisplayName, String Name)[] AvailableExchanges { get; set; } = Array.Empty<(String DisplayName, String Name)>(); public Task AddHeader(HttpRequestMessage message) { diff --git a/BTCPayServer/Views/Stores/UpdateStore.cshtml b/BTCPayServer/Views/Stores/UpdateStore.cshtml index 5672902cc..e35466c79 100644 --- a/BTCPayServer/Views/Stores/UpdateStore.cshtml +++ b/BTCPayServer/Views/Stores/UpdateStore.cshtml @@ -36,10 +36,10 @@
- +

- Current price source is @Model.PreferredExchange. (using 1 minute cache) + Current price source is @Model.PreferredExchange.

@@ -81,13 +81,13 @@ - @foreach (var scheme in Model.DerivationSchemes) + @foreach(var scheme in Model.DerivationSchemes) { @scheme.Crypto @scheme.Value - @if (!string.IsNullOrWhiteSpace(scheme.Value)) + @if(!string.IsNullOrWhiteSpace(scheme.Value)) { Wallet - } @@ -117,7 +117,7 @@ - @foreach (var scheme in Model.LightningNodes) + @foreach(var scheme in Model.LightningNodes) { @scheme.CryptoCode From 6936b034cbb593ac756b156940296f1f43ea54c5 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Wed, 18 Apr 2018 18:23:39 +0900 Subject: [PATCH 13/17] Add Bitcoin average quota --- .../Controllers/InvoiceController.UI.cs | 13 +---- BTCPayServer/Controllers/ServerController.cs | 48 ++++++++++++++----- BTCPayServer/Extensions.cs | 11 +++++ .../Models/ServerViewModels/RatesViewModel.cs | 2 + .../Services/Rates/CoinAverageRateProvider.cs | 23 +++++++++ BTCPayServer/Views/Server/Rates.cshtml | 15 +++++- 6 files changed, 88 insertions(+), 24 deletions(-) diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 91c39408d..dcc524849 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -253,7 +253,7 @@ namespace BTCPayServer.Controllers }; var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds); - model.TimeLeft = PrettyPrint(expiration); + model.TimeLeft = expiration.PrettyPrint(); return model; } @@ -272,17 +272,6 @@ namespace BTCPayServer.Controllers return price.ToString("C", _CurrencyNameTable.GetCurrencyProvider(currency)) + $" ({currency})"; } - private string PrettyPrint(TimeSpan expiration) - { - StringBuilder builder = new StringBuilder(); - if (expiration.Days >= 1) - builder.Append(expiration.Days.ToString(CultureInfo.InvariantCulture)); - if (expiration.Hours >= 1) - builder.Append(expiration.Hours.ToString("00", CultureInfo.InvariantCulture)); - builder.Append($"{expiration.Minutes.ToString("00", CultureInfo.InvariantCulture)}:{expiration.Seconds.ToString("00", CultureInfo.InvariantCulture)}"); - return builder.ToString(); - } - [HttpGet] [Route("i/{invoiceId}/status")] [Route("i/{invoiceId}/{paymentMethodId}/status")] diff --git a/BTCPayServer/Controllers/ServerController.cs b/BTCPayServer/Controllers/ServerController.cs index a08f7f6b9..5e6977389 100644 --- a/BTCPayServer/Controllers/ServerController.cs +++ b/BTCPayServer/Controllers/ServerController.cs @@ -36,13 +36,28 @@ namespace BTCPayServer.Controllers public async Task Rates() { var rates = (await _SettingsRepository.GetSettingAsync()) ?? new RatesSetting(); - return View(new RatesViewModel() + + var vm = new RatesViewModel() { CacheMinutes = rates.CacheInMinutes, PrivateKey = rates.PrivateKey, PublicKey = rates.PublicKey - }); + }; + await FetchRateLimits(vm); + return View(vm); + } + private static async Task FetchRateLimits(RatesViewModel vm) + { + var coinAverage = GetCoinaverageService(vm, false); + if (coinAverage != null) + { + try + { + vm.RateLimits = await coinAverage.GetRateLimitsAsync(); + } + catch { } + } } [Route("server/rates")] @@ -55,27 +70,38 @@ namespace BTCPayServer.Controllers rates.CacheInMinutes = vm.CacheMinutes; try { - var settings = new CoinAverageSettings() - { - KeyPair = (vm.PublicKey, vm.PrivateKey) - }; - if (settings.GetCoinAverageSignature() != null) - { - await new CoinAverageRateProvider("BTC") - { Authenticator = settings }.TestAuthAsync(); - } + var service = GetCoinaverageService(vm, true); + if(service != null) + await service.TestAuthAsync(); } catch { ModelState.AddModelError(nameof(vm.PrivateKey), "Invalid API key pair"); } if (!ModelState.IsValid) + { + await FetchRateLimits(vm); return View(vm); + } await _SettingsRepository.UpdateSetting(rates); StatusMessage = "Rate settings successfully updated"; return RedirectToAction(nameof(Rates)); } + private static CoinAverageRateProvider GetCoinaverageService(RatesViewModel vm, bool withAuth) + { + var settings = new CoinAverageSettings() + { + KeyPair = (vm.PublicKey, vm.PrivateKey) + }; + if (!withAuth || settings.GetCoinAverageSignature() != null) + { + return new CoinAverageRateProvider("BTC") + { Authenticator = settings }; + } + return null; + } + [Route("server/users")] public IActionResult ListUsers() { diff --git a/BTCPayServer/Extensions.cs b/BTCPayServer/Extensions.cs index 5f9df05c9..40fe847e5 100644 --- a/BTCPayServer/Extensions.cs +++ b/BTCPayServer/Extensions.cs @@ -28,11 +28,22 @@ using BTCPayServer.Payments; using Microsoft.AspNetCore.Identity; using BTCPayServer.Models; using System.Security.Claims; +using System.Globalization; namespace BTCPayServer { public static class Extensions { + public static string PrettyPrint(this TimeSpan expiration) + { + StringBuilder builder = new StringBuilder(); + if (expiration.Days >= 1) + builder.Append(expiration.Days.ToString(CultureInfo.InvariantCulture)); + if (expiration.Hours >= 1) + builder.Append(expiration.Hours.ToString("00", CultureInfo.InvariantCulture)); + builder.Append($"{expiration.Minutes.ToString("00", CultureInfo.InvariantCulture)}:{expiration.Seconds.ToString("00", CultureInfo.InvariantCulture)}"); + return builder.ToString(); + } public static decimal RoundUp(decimal value, int precision) { for (int i = 0; i < precision; i++) diff --git a/BTCPayServer/Models/ServerViewModels/RatesViewModel.cs b/BTCPayServer/Models/ServerViewModels/RatesViewModel.cs index b0e1c3cba..b72f1ba49 100644 --- a/BTCPayServer/Models/ServerViewModels/RatesViewModel.cs +++ b/BTCPayServer/Models/ServerViewModels/RatesViewModel.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; +using BTCPayServer.Services.Rates; namespace BTCPayServer.Models.ServerViewModels { @@ -14,5 +15,6 @@ namespace BTCPayServer.Models.ServerViewModels [Display(Name = "Cache the rates for ... minutes")] [Range(0, 60)] public int CacheMinutes { get; set; } + public GetRateLimitsResponse RateLimits { get; internal set; } } } diff --git a/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs b/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs index 7cdbb29aa..ebaa5bde0 100644 --- a/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs +++ b/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs @@ -170,6 +170,23 @@ namespace BTCPayServer.Services.Rates resp.EnsureSuccessStatusCode(); } + public async Task GetRateLimitsAsync() + { + var request = new HttpRequestMessage(HttpMethod.Get, "https://apiv2.bitcoinaverage.com/info/ratelimits"); + var auth = Authenticator; + if (auth != null) + { + await auth.AddHeader(request); + } + var resp = await _Client.SendAsync(request); + resp.EnsureSuccessStatusCode(); + var jobj = JObject.Parse(await resp.Content.ReadAsStringAsync()); + var response = new GetRateLimitsResponse(); + response.CounterReset = TimeSpan.FromSeconds(jobj["counter_reset"].Value()); + response.RequestsLeft = jobj["requests_left"].Value(); + return response; + } + public async Task GetExchangeTickersAsync() { var request = new HttpRequestMessage(HttpMethod.Get, "https://apiv2.bitcoinaverage.com/symbols/exchanges/ticker"); @@ -191,4 +208,10 @@ namespace BTCPayServer.Services.Rates return response; } } + + public class GetRateLimitsResponse + { + public TimeSpan CounterReset { get; set; } + public int RequestsLeft { get; set; } + } } diff --git a/BTCPayServer/Views/Server/Rates.cshtml b/BTCPayServer/Views/Server/Rates.cshtml index a8dfc17f7..89e145778 100644 --- a/BTCPayServer/Views/Server/Rates.cshtml +++ b/BTCPayServer/Views/Server/Rates.cshtml @@ -28,7 +28,6 @@ -

You can find the information on bitcoinaverage api key page

@@ -36,6 +35,20 @@
+ @if(Model.RateLimits != null) + { +
Current Bitcoin Average Quotas:
+ + + + + + + + + +
Requests left@Model.RateLimits.RequestsLeft
Quota reset in@Model.RateLimits.CounterReset
+ } From 04210046160f8b1f3e4e48180806159c8e7bfc65 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Wed, 18 Apr 2018 21:52:13 +0900 Subject: [PATCH 14/17] fix point of sale view on mobile --- BTCPayServer/Views/Apps/ViewPointOfSale.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayServer/Views/Apps/ViewPointOfSale.cshtml b/BTCPayServer/Views/Apps/ViewPointOfSale.cshtml index 2fd64e279..807cfc080 100644 --- a/BTCPayServer/Views/Apps/ViewPointOfSale.cshtml +++ b/BTCPayServer/Views/Apps/ViewPointOfSale.cshtml @@ -15,7 +15,7 @@
-
+

@Model.Title

From 36528666606cbe914d1bc8f2abc474e326717f3a Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Wed, 18 Apr 2018 22:27:01 +0900 Subject: [PATCH 15/17] View offchain payments in Invoice screen --- .../Controllers/InvoiceController.UI.cs | 78 +++++++------ .../InvoicingModels/InvoiceDetailsModel.cs | 10 +- BTCPayServer/Views/Invoice/Invoice.cshtml | 109 +++++++++++------- 3 files changed, 122 insertions(+), 75 deletions(-) diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index dcc524849..369165e01 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -85,54 +85,66 @@ namespace BTCPayServer.Controllers model.CryptoPayments.Add(cryptoPayment); } - var payments = invoice + var onChainPayments = invoice .GetPayments() - .Where(p => p.GetPaymentMethodId().PaymentType == PaymentTypes.BTCLike) - .Select(async payment => + .Select>(async payment => { - var paymentData = (Payments.Bitcoin.BitcoinLikePaymentData)payment.GetCryptoPaymentData(); - var m = new InvoiceDetailsModel.Payment(); var paymentNetwork = _NetworkProvider.GetNetwork(payment.GetCryptoCode()); - m.PaymentMethod = ToString(payment.GetPaymentMethodId()); - m.DepositAddress = paymentData.Output.ScriptPubKey.GetDestinationAddress(paymentNetwork.NBitcoinNetwork); - - int confirmationCount = 0; - if ( (paymentData.ConfirmationCount < paymentNetwork.MaxTrackedConfirmation && payment.Accounted) - && (paymentData.Legacy || invoice.MonitoringExpiration < DateTimeOffset.UtcNow)) // The confirmation count in the paymentData is not up to date + var paymentData = payment.GetCryptoPaymentData(); + if (paymentData is Payments.Bitcoin.BitcoinLikePaymentData onChainPaymentData) { - confirmationCount = (await ((ExplorerClientProvider)_ServiceProvider.GetService(typeof(ExplorerClientProvider))).GetExplorerClient(payment.GetCryptoCode())?.GetTransactionAsync(paymentData.Outpoint.Hash))?.Confirmations ?? 0; - paymentData.ConfirmationCount = confirmationCount; - payment.SetCryptoPaymentData(paymentData); - await _InvoiceRepository.UpdatePayments(new List { payment }); + var m = new InvoiceDetailsModel.Payment(); + m.Crypto = payment.GetPaymentMethodId().CryptoCode; + m.DepositAddress = onChainPaymentData.Output.ScriptPubKey.GetDestinationAddress(paymentNetwork.NBitcoinNetwork); + + int confirmationCount = 0; + if ((onChainPaymentData.ConfirmationCount < paymentNetwork.MaxTrackedConfirmation && payment.Accounted) + && (onChainPaymentData.Legacy || invoice.MonitoringExpiration < DateTimeOffset.UtcNow)) // The confirmation count in the paymentData is not up to date + { + confirmationCount = (await ((ExplorerClientProvider)_ServiceProvider.GetService(typeof(ExplorerClientProvider))).GetExplorerClient(payment.GetCryptoCode())?.GetTransactionAsync(onChainPaymentData.Outpoint.Hash))?.Confirmations ?? 0; + onChainPaymentData.ConfirmationCount = confirmationCount; + payment.SetCryptoPaymentData(onChainPaymentData); + await _InvoiceRepository.UpdatePayments(new List { payment }); + } + else + { + confirmationCount = onChainPaymentData.ConfirmationCount; + } + if (confirmationCount >= paymentNetwork.MaxTrackedConfirmation) + { + m.Confirmations = "At least " + (paymentNetwork.MaxTrackedConfirmation); + } + else + { + m.Confirmations = confirmationCount.ToString(CultureInfo.InvariantCulture); + } + + m.TransactionId = onChainPaymentData.Outpoint.Hash.ToString(); + m.ReceivedTime = payment.ReceivedTime; + m.TransactionLink = string.Format(CultureInfo.InvariantCulture, paymentNetwork.BlockExplorerLink, m.TransactionId); + m.Replaced = !payment.Accounted; + return m; } else { - confirmationCount = paymentData.ConfirmationCount; + var lightningPaymentData = (Payments.Lightning.LightningLikePaymentData)paymentData; + return new InvoiceDetailsModel.OffChainPayment() + { + Crypto = paymentNetwork.CryptoCode, + BOLT11 = lightningPaymentData.BOLT11 + }; } - if (confirmationCount >= paymentNetwork.MaxTrackedConfirmation) - { - m.Confirmations = "At least " + (paymentNetwork.MaxTrackedConfirmation); - } - else - { - m.Confirmations = confirmationCount.ToString(CultureInfo.InvariantCulture); - } - - m.TransactionId = paymentData.Outpoint.Hash.ToString(); - m.ReceivedTime = payment.ReceivedTime; - m.TransactionLink = string.Format(CultureInfo.InvariantCulture, paymentNetwork.BlockExplorerLink, m.TransactionId); - m.Replaced = !payment.Accounted; - return m; }) .ToArray(); - await Task.WhenAll(payments); + await Task.WhenAll(onChainPayments); model.Addresses = invoice.HistoricalAddresses.Select(h => new InvoiceDetailsModel.AddressModel { Destination = h.GetAddress(), PaymentMethod = ToString(h.GetPaymentMethodId()), Current = !h.UnAssigned.HasValue }).ToArray(); - model.Payments = payments.Select(p => p.GetAwaiter().GetResult()).ToList(); + model.OnChainPayments = onChainPayments.Select(p => p.GetAwaiter().GetResult()).OfType().ToList(); + model.OffChainPayments = onChainPayments.Select(p => p.GetAwaiter().GetResult()).OfType().ToList(); model.StatusMessage = StatusMessage; return View(model); } @@ -439,7 +451,7 @@ namespace BTCPayServer.Controllers return View(model); } - if(StatusMessage != null) + if (StatusMessage != null) { return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new { diff --git a/BTCPayServer/Models/InvoicingModels/InvoiceDetailsModel.cs b/BTCPayServer/Models/InvoicingModels/InvoiceDetailsModel.cs index e889200b9..36b270c2b 100644 --- a/BTCPayServer/Models/InvoicingModels/InvoiceDetailsModel.cs +++ b/BTCPayServer/Models/InvoicingModels/InvoiceDetailsModel.cs @@ -27,7 +27,7 @@ namespace BTCPayServer.Models.InvoicingModels } public class Payment { - public string PaymentMethod { get; set; } + public string Crypto { get; set; } public string Confirmations { get; set; @@ -72,7 +72,13 @@ namespace BTCPayServer.Models.InvoicingModels get; set; } = new List(); - public List Payments { get; set; } = new List(); + public List OnChainPayments { get; set; } = new List(); + public List OffChainPayments { get; set; } = new List(); + public class OffChainPayment + { + public string Crypto { get; set; } + public string BOLT11 { get; set; } + } public string Status { diff --git a/BTCPayServer/Views/Invoice/Invoice.cshtml b/BTCPayServer/Views/Invoice/Invoice.cshtml index feb6d5d71..bcd147989 100644 --- a/BTCPayServer/Views/Invoice/Invoice.cshtml +++ b/BTCPayServer/Views/Invoice/Invoice.cshtml @@ -10,6 +10,7 @@ text-overflow: ellipsis; white-space: nowrap; } + .money { text-align: right; } @@ -165,46 +166,74 @@ @foreach(var payment in Model.CryptoPayments) - { - - @payment.PaymentMethod - @payment.Address - @payment.Rate - @payment.Paid - @payment.Due - - } + { + + @payment.PaymentMethod + @payment.Address + @payment.Rate + @payment.Paid + @payment.Due + + }
-
-
-

Payments

- - - - - - - - - - - @foreach(var payment in Model.Payments) - { - var replaced = payment.Replaced ? "text-decoration: line-through;" : ""; - - - - - - - } - -
Payment methodDeposit addressTransaction IdConfirmations
@payment.PaymentMethod@payment.DepositAddress@payment.TransactionId@payment.Confirmations
+ @if(Model.OnChainPayments.Count > 0) + { +
+
+

On-Chain payments

+ + + + + + + + + + + @foreach(var payment in Model.OnChainPayments) + { + var replaced = payment.Replaced ? "text-decoration: line-through;" : ""; + + + + + + + } + +
CryptoDeposit addressTransaction IdConfirmations
@payment.Crypto@payment.DepositAddress@payment.TransactionId@payment.Confirmations
+
-
+ } + @if(Model.OffChainPayments.Count > 0) + { +
+
+

Off-Chain payments

+ + + + + + + + + @foreach(var payment in Model.OffChainPayments) + { + + + + + } + +
CryptoBOLT11
@payment.Crypto@payment.BOLT11
+
+
+ }

Addresses

@@ -219,11 +248,11 @@ @foreach(var address in Model.Addresses) { var current = address.Current ? "font-weight: bold;" : ""; - - @address.PaymentMethod - @address.Destination - - } + + @address.PaymentMethod + @address.Destination + + }
From e864cf35f7569f8e289fecce44ddf5c0815a3f8b Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Wed, 18 Apr 2018 22:28:04 +0900 Subject: [PATCH 16/17] bump NBitcoin --- BTCPayServer/BTCPayServer.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 767120e1e..a1aa9989a 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -39,7 +39,7 @@ - + From e4299c09ea200c68aaa0b0c6ffa1a20310cddb2a Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Wed, 18 Apr 2018 22:28:31 +0900 Subject: [PATCH 17/17] bump --- BTCPayServer/BTCPayServer.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index a1aa9989a..bacd4accc 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.0 - 1.0.1.85 + 1.0.1.86 NU1701,CA1816,CA1308,CA1810,CA2208