mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-20 07:24:25 +01:00
Use CoinAverage as rate provider + add caching to avoid hitting limits
This commit is contained in:
@@ -20,182 +20,184 @@ using System.Diagnostics;
|
|||||||
using Microsoft.EntityFrameworkCore.Extensions;
|
using Microsoft.EntityFrameworkCore.Extensions;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using BTCPayServer.Services.Rates;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
public class UnitTest1
|
public class UnitTest1
|
||||||
{
|
{
|
||||||
public UnitTest1(ITestOutputHelper helper)
|
public UnitTest1(ITestOutputHelper helper)
|
||||||
{
|
{
|
||||||
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
|
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
|
||||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanCalculateCryptoDue()
|
public void CanCalculateCryptoDue()
|
||||||
{
|
{
|
||||||
var entity = new InvoiceEntity();
|
var entity = new InvoiceEntity();
|
||||||
entity.TxFee = Money.Coins(0.1m);
|
entity.TxFee = Money.Coins(0.1m);
|
||||||
entity.Rate = 5000;
|
entity.Rate = 5000;
|
||||||
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||||
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
||||||
|
|
||||||
Assert.Equal(Money.Coins(1.1m), entity.GetCryptoDue());
|
Assert.Equal(Money.Coins(1.1m), entity.GetCryptoDue());
|
||||||
Assert.Equal(Money.Coins(1.1m), entity.GetTotalCryptoDue());
|
Assert.Equal(Money.Coins(1.1m), entity.GetTotalCryptoDue());
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.5m), new Key()) });
|
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.5m), new Key()) });
|
||||||
|
|
||||||
//Since we need to spend one more txout, it should be 1.1 - 0,5 + 0.1
|
//Since we need to spend one more txout, it should be 1.1 - 0,5 + 0.1
|
||||||
Assert.Equal(Money.Coins(0.7m), entity.GetCryptoDue());
|
Assert.Equal(Money.Coins(0.7m), entity.GetCryptoDue());
|
||||||
Assert.Equal(Money.Coins(1.2m), entity.GetTotalCryptoDue());
|
Assert.Equal(Money.Coins(1.2m), entity.GetTotalCryptoDue());
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()) });
|
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()) });
|
||||||
Assert.Equal(Money.Coins(0.6m), entity.GetCryptoDue());
|
Assert.Equal(Money.Coins(0.6m), entity.GetCryptoDue());
|
||||||
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.6m), new Key()) });
|
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.6m), new Key()) });
|
||||||
|
|
||||||
Assert.Equal(Money.Zero, entity.GetCryptoDue());
|
Assert.Equal(Money.Zero, entity.GetCryptoDue());
|
||||||
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()) });
|
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()) });
|
||||||
|
|
||||||
Assert.Equal(Money.Zero, entity.GetCryptoDue());
|
Assert.Equal(Money.Zero, entity.GetCryptoDue());
|
||||||
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanPayUsingBIP70()
|
public void CanPayUsingBIP70()
|
||||||
{
|
{
|
||||||
using (var tester = ServerTester.Create())
|
using(var tester = ServerTester.Create())
|
||||||
{
|
{
|
||||||
tester.Start();
|
tester.Start();
|
||||||
var user = tester.NewAccount();
|
var user = tester.NewAccount();
|
||||||
user.GrantAccess();
|
user.GrantAccess();
|
||||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||||
{
|
{
|
||||||
Buyer = new Buyer() { email = "test@fwf.com" },
|
Buyer = new Buyer() { email = "test@fwf.com" },
|
||||||
Price = 5000.0,
|
Price = 5000.0,
|
||||||
Currency = "USD",
|
Currency = "USD",
|
||||||
PosData = "posData",
|
PosData = "posData",
|
||||||
OrderId = "orderId",
|
OrderId = "orderId",
|
||||||
//RedirectURL = redirect + "redirect",
|
//RedirectURL = redirect + "redirect",
|
||||||
//NotificationURL = CallbackUri + "/notification",
|
//NotificationURL = CallbackUri + "/notification",
|
||||||
ItemDesc = "Some description",
|
ItemDesc = "Some description",
|
||||||
FullNotifications = true
|
FullNotifications = true
|
||||||
}, Facade.Merchant);
|
}, Facade.Merchant);
|
||||||
|
|
||||||
Assert.False(invoice.Refundable);
|
Assert.False(invoice.Refundable);
|
||||||
|
|
||||||
var url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP72);
|
var url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP72);
|
||||||
var request = url.GetPaymentRequest();
|
var request = url.GetPaymentRequest();
|
||||||
var payment = request.CreatePayment();
|
var payment = request.CreatePayment();
|
||||||
|
|
||||||
Transaction tx = new Transaction();
|
Transaction tx = new Transaction();
|
||||||
tx.Outputs.AddRange(request.Details.Outputs.Select(o => new TxOut(o.Amount, o.Script)));
|
tx.Outputs.AddRange(request.Details.Outputs.Select(o => new TxOut(o.Amount, o.Script)));
|
||||||
var cashCow = tester.ExplorerNode;
|
var cashCow = tester.ExplorerNode;
|
||||||
tx = cashCow.FundRawTransaction(tx).Transaction;
|
tx = cashCow.FundRawTransaction(tx).Transaction;
|
||||||
tx = cashCow.SignRawTransaction(tx);
|
tx = cashCow.SignRawTransaction(tx);
|
||||||
|
|
||||||
payment.Transactions.Add(tx);
|
payment.Transactions.Add(tx);
|
||||||
|
|
||||||
payment.RefundTo.Add(new PaymentOutput(Money.Coins(1.0m), new Key().ScriptPubKey));
|
payment.RefundTo.Add(new PaymentOutput(Money.Coins(1.0m), new Key().ScriptPubKey));
|
||||||
var ack = payment.SubmitPayment();
|
var ack = payment.SubmitPayment();
|
||||||
Assert.NotNull(ack);
|
Assert.NotNull(ack);
|
||||||
|
|
||||||
Eventually(() =>
|
Eventually(() =>
|
||||||
{
|
{
|
||||||
tester.SimulateCallback(url.Address);
|
tester.SimulateCallback(url.Address);
|
||||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||||
Assert.Equal("paid", localInvoice.Status);
|
Assert.Equal("paid", localInvoice.Status);
|
||||||
Assert.True(localInvoice.Refundable);
|
Assert.True(localInvoice.Refundable);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanUseServerInitiatedPairingCode()
|
public void CanUseServerInitiatedPairingCode()
|
||||||
{
|
{
|
||||||
using (var tester = ServerTester.Create())
|
using(var tester = ServerTester.Create())
|
||||||
{
|
{
|
||||||
tester.Start();
|
tester.Start();
|
||||||
var acc = tester.NewAccount();
|
var acc = tester.NewAccount();
|
||||||
acc.Register();
|
acc.Register();
|
||||||
acc.CreateStore();
|
acc.CreateStore();
|
||||||
|
|
||||||
var controller = tester.PayTester.GetController<StoresController>(acc.UserId);
|
var controller = tester.PayTester.GetController<StoresController>(acc.UserId);
|
||||||
var token = (RedirectToActionResult)controller.CreateToken(acc.StoreId, new Models.StoreViewModels.CreateTokenViewModel()
|
var token = (RedirectToActionResult)controller.CreateToken(acc.StoreId, new Models.StoreViewModels.CreateTokenViewModel()
|
||||||
{
|
{
|
||||||
Facade = Facade.Merchant.ToString(),
|
Facade = Facade.Merchant.ToString(),
|
||||||
Label = "bla",
|
Label = "bla",
|
||||||
PublicKey = null
|
PublicKey = null
|
||||||
}).GetAwaiter().GetResult();
|
}).GetAwaiter().GetResult();
|
||||||
|
|
||||||
var pairingCode = (string)token.RouteValues["pairingCode"];
|
var pairingCode = (string)token.RouteValues["pairingCode"];
|
||||||
|
|
||||||
acc.BitPay.AuthorizeClient(new PairingCode(pairingCode)).GetAwaiter().GetResult();
|
acc.BitPay.AuthorizeClient(new PairingCode(pairingCode)).GetAwaiter().GetResult();
|
||||||
Assert.True(acc.BitPay.TestAccess(Facade.Merchant));
|
Assert.True(acc.BitPay.TestAccess(Facade.Merchant));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanSendIPN()
|
public void CanSendIPN()
|
||||||
{
|
{
|
||||||
using (var callbackServer = new CustomServer())
|
using(var callbackServer = new CustomServer())
|
||||||
{
|
{
|
||||||
using (var tester = ServerTester.Create())
|
using(var tester = ServerTester.Create())
|
||||||
{
|
{
|
||||||
tester.Start();
|
tester.Start();
|
||||||
var acc = tester.NewAccount();
|
var acc = tester.NewAccount();
|
||||||
acc.GrantAccess();
|
acc.GrantAccess();
|
||||||
var invoice = acc.BitPay.CreateInvoice(new Invoice()
|
var invoice = acc.BitPay.CreateInvoice(new Invoice()
|
||||||
{
|
{
|
||||||
Price = 5.0,
|
Price = 5.0,
|
||||||
Currency = "USD",
|
Currency = "USD",
|
||||||
PosData = "posData",
|
PosData = "posData",
|
||||||
OrderId = "orderId",
|
OrderId = "orderId",
|
||||||
NotificationURL = callbackServer.GetUri().AbsoluteUri,
|
NotificationURL = callbackServer.GetUri().AbsoluteUri,
|
||||||
ItemDesc = "Some description",
|
ItemDesc = "Some description",
|
||||||
FullNotifications = true
|
FullNotifications = true
|
||||||
});
|
});
|
||||||
BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21);
|
BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21);
|
||||||
tester.ExplorerNode.SendToAddress(url.Address, url.Amount);
|
tester.ExplorerNode.SendToAddress(url.Address, url.Amount);
|
||||||
Thread.Sleep(5000);
|
Thread.Sleep(5000);
|
||||||
tester.SimulateCallback(url.Address);
|
tester.SimulateCallback(url.Address);
|
||||||
callbackServer.ProcessNextRequest((ctx) =>
|
callbackServer.ProcessNextRequest((ctx) =>
|
||||||
{
|
{
|
||||||
var ipn = new StreamReader(ctx.Request.Body).ReadToEnd();
|
var ipn = new StreamReader(ctx.Request.Body).ReadToEnd();
|
||||||
JsonConvert.DeserializeObject<InvoicePaymentNotification>(ipn); //can deserialize
|
JsonConvert.DeserializeObject<InvoicePaymentNotification>(ipn); //can deserialize
|
||||||
});
|
});
|
||||||
var invoice2 = acc.BitPay.GetInvoice(invoice.Id);
|
var invoice2 = acc.BitPay.GetInvoice(invoice.Id);
|
||||||
Assert.NotNull(invoice2);
|
Assert.NotNull(invoice2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CantPairTwiceWithSamePubkey()
|
public void CantPairTwiceWithSamePubkey()
|
||||||
{
|
{
|
||||||
using (var tester = ServerTester.Create())
|
using(var tester = ServerTester.Create())
|
||||||
{
|
{
|
||||||
tester.Start();
|
tester.Start();
|
||||||
var acc = tester.NewAccount();
|
var acc = tester.NewAccount();
|
||||||
acc.Register();
|
acc.Register();
|
||||||
var store = acc.CreateStore();
|
var store = acc.CreateStore();
|
||||||
var pairingCode = acc.BitPay.RequestClientAuthorization("test", Facade.Merchant);
|
var pairingCode = acc.BitPay.RequestClientAuthorization("test", Facade.Merchant);
|
||||||
Assert.IsType<RedirectToActionResult>(store.Pair(pairingCode.ToString(), acc.StoreId).GetAwaiter().GetResult());
|
Assert.IsType<RedirectToActionResult>(store.Pair(pairingCode.ToString(), acc.StoreId).GetAwaiter().GetResult());
|
||||||
|
|
||||||
pairingCode = acc.BitPay.RequestClientAuthorization("test1", Facade.Merchant);
|
pairingCode = acc.BitPay.RequestClientAuthorization("test1", Facade.Merchant);
|
||||||
var store2 = acc.CreateStore();
|
var store2 = acc.CreateStore();
|
||||||
store2.Pair(pairingCode.ToString(), store2.CreatedStoreId).GetAwaiter().GetResult();
|
store2.Pair(pairingCode.ToString(), store2.CreatedStoreId).GetAwaiter().GetResult();
|
||||||
Assert.Contains(nameof(PairingResult.ReusedKey), store2.StatusMessage);
|
Assert.Contains(nameof(PairingResult.ReusedKey), store2.StatusMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void InvoiceFlowThroughDifferentStatesCorrectly()
|
public void InvoiceFlowThroughDifferentStatesCorrectly()
|
||||||
{
|
{
|
||||||
using (var tester = ServerTester.Create())
|
using(var tester = ServerTester.Create())
|
||||||
{
|
{
|
||||||
tester.Start();
|
tester.Start();
|
||||||
var user = tester.NewAccount();
|
var user = tester.NewAccount();
|
||||||
@@ -220,9 +222,7 @@ namespace BTCPayServer.Tests
|
|||||||
StoreId = user.StoreId,
|
StoreId = user.StoreId,
|
||||||
TextSearch = invoice.OrderId
|
TextSearch = invoice.OrderId
|
||||||
}).GetAwaiter().GetResult();
|
}).GetAwaiter().GetResult();
|
||||||
|
|
||||||
Assert.Equal(1, textSearchResult.Length);
|
Assert.Equal(1, textSearchResult.Length);
|
||||||
|
|
||||||
textSearchResult = tester.PayTester.Runtime.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
textSearchResult = tester.PayTester.Runtime.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||||
{
|
{
|
||||||
StoreId = user.StoreId,
|
StoreId = user.StoreId,
|
||||||
@@ -352,6 +352,21 @@ namespace BTCPayServer.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CheckRatesProvider()
|
||||||
|
{
|
||||||
|
var coinAverage = new CoinAverageRateProvider();
|
||||||
|
var jpy = coinAverage.GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||||
|
var jpy2 = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/"))).GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
var cached = new CachedRateProvider(coinAverage, new MemoryCache(new MemoryCacheOptions() { ExpirationScanFrequency = TimeSpan.FromSeconds(1.0) }));
|
||||||
|
cached.CacheSpan = TimeSpan.FromSeconds(10);
|
||||||
|
var a = cached.GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||||
|
var b = cached.GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||||
|
//Manually check that cache get hit after 10 sec
|
||||||
|
var c = cached.GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx)
|
private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx)
|
||||||
{
|
{
|
||||||
var h = BitcoinAddress.Create(invoice.BitcoinAddress).ScriptPubKey.Hash.ToString();
|
var h = BitcoinAddress.Create(invoice.BitcoinAddress).ScriptPubKey.Hash.ToString();
|
||||||
@@ -359,20 +374,20 @@ namespace BTCPayServer.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void Eventually(Action act)
|
private void Eventually(Action act)
|
||||||
{
|
{
|
||||||
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
||||||
while (true)
|
while(true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
act();
|
act();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (XunitException) when (!cts.Token.IsCancellationRequested)
|
catch(XunitException) when(!cts.Token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
cts.Token.WaitHandle.WaitOne(500);
|
cts.Token.WaitHandle.WaitOne(500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ using System.Threading.Tasks;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using BTCPayServer.Services.Wallets;
|
using BTCPayServer.Services.Wallets;
|
||||||
using BTCPayServer.Authentication;
|
using BTCPayServer.Authentication;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
|
||||||
namespace BTCPayServer.Hosting
|
namespace BTCPayServer.Hosting
|
||||||
{
|
{
|
||||||
@@ -137,7 +138,10 @@ namespace BTCPayServer.Hosting
|
|||||||
else
|
else
|
||||||
return new Bitpay(new Key(), new Uri("https://test.bitpay.com/"));
|
return new Bitpay(new Key(), new Uri("https://test.bitpay.com/"));
|
||||||
});
|
});
|
||||||
services.TryAddSingleton<IRateProvider, BitpayRateProvider>();
|
services.TryAddSingleton<IRateProvider>(o =>
|
||||||
|
{
|
||||||
|
return new CachedRateProvider(new CoinAverageRateProvider(), o.GetRequiredService<IMemoryCache>()) { CacheSpan = TimeSpan.FromMinutes(1.0) };
|
||||||
|
});
|
||||||
services.TryAddSingleton<InvoiceWatcher>();
|
services.TryAddSingleton<InvoiceWatcher>();
|
||||||
services.TryAddSingleton<InvoiceNotificationManager>();
|
services.TryAddSingleton<InvoiceNotificationManager>();
|
||||||
services.TryAddSingleton<IHostedService>(o => o.GetRequiredService<InvoiceWatcher>());
|
services.TryAddSingleton<IHostedService>(o => o.GetRequiredService<InvoiceWatcher>());
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ namespace BTCPayServer.Hosting
|
|||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.ConfigureBTCPayServer(Configuration);
|
services.ConfigureBTCPayServer(Configuration);
|
||||||
|
services.AddMemoryCache();
|
||||||
services.AddIdentity<ApplicationUser, IdentityRole>()
|
services.AddIdentity<ApplicationUser, IdentityRole>()
|
||||||
.AddEntityFrameworkStores<ApplicationDbContext>()
|
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||||
.AddDefaultTokenProviders();
|
.AddDefaultTokenProviders();
|
||||||
|
|||||||
54
BTCPayServer/Services/Rates/CachedRateProvider.cs
Normal file
54
BTCPayServer/Services/Rates/CachedRateProvider.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Services.Rates;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Services.Rates
|
||||||
|
{
|
||||||
|
public class CachedRateProvider : IRateProvider
|
||||||
|
{
|
||||||
|
private IRateProvider _Inner;
|
||||||
|
private IMemoryCache _MemoryCache;
|
||||||
|
|
||||||
|
public CachedRateProvider(IRateProvider inner, IMemoryCache memoryCache)
|
||||||
|
{
|
||||||
|
if(inner == null)
|
||||||
|
throw new ArgumentNullException(nameof(inner));
|
||||||
|
if(memoryCache == null)
|
||||||
|
throw new ArgumentNullException(nameof(memoryCache));
|
||||||
|
this._Inner = inner;
|
||||||
|
this._MemoryCache = memoryCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan CacheSpan
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
} = TimeSpan.FromMinutes(1.0);
|
||||||
|
|
||||||
|
public Task<decimal> GetRateAsync(string currency)
|
||||||
|
{
|
||||||
|
return _MemoryCache.GetOrCreateAsync("CURR_" + currency, (ICacheEntry entry) =>
|
||||||
|
{
|
||||||
|
entry.AbsoluteExpiration = DateTimeOffset.UtcNow + CacheSpan;
|
||||||
|
return _Inner.GetRateAsync(currency);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetFromCache(string key, out object obj)
|
||||||
|
{
|
||||||
|
obj = _MemoryCache.Get(key);
|
||||||
|
return obj != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ICollection<Rate>> GetRatesAsync()
|
||||||
|
{
|
||||||
|
return _MemoryCache.GetOrCreateAsync("GLOBAL_RATES", (ICacheEntry entry) =>
|
||||||
|
{
|
||||||
|
entry.AbsoluteExpiration = DateTimeOffset.UtcNow + CacheSpan;
|
||||||
|
return _Inner.GetRatesAsync();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
116
BTCPayServer/Services/Rates/CoinAverageRateProvider.cs
Normal file
116
BTCPayServer/Services/Rates/CoinAverageRateProvider.cs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Services.Rates
|
||||||
|
{
|
||||||
|
public class CoinAverageException : Exception
|
||||||
|
{
|
||||||
|
public CoinAverageException(string message) : base(message)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class CoinAverageRateProvider : IRateProvider
|
||||||
|
{
|
||||||
|
public class RatesJson
|
||||||
|
{
|
||||||
|
public class RateJson
|
||||||
|
{
|
||||||
|
public string Code
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
public decimal Rate
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonProperty("rates")]
|
||||||
|
public JObject RatesInternal
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
[JsonIgnore]
|
||||||
|
public List<RateJson> Rates
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public Dictionary<string, decimal> RatesByCurrency
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public decimal GetRate(string currency)
|
||||||
|
{
|
||||||
|
if(!RatesByCurrency.TryGetValue(currency.ToUpperInvariant(), out decimal currUSD))
|
||||||
|
throw new RateUnavailableException(currency);
|
||||||
|
|
||||||
|
if(!RatesByCurrency.TryGetValue("BTC", out decimal btcUSD))
|
||||||
|
throw new RateUnavailableException(currency);
|
||||||
|
|
||||||
|
return currUSD / btcUSD;
|
||||||
|
}
|
||||||
|
public void CalculateDictionary()
|
||||||
|
{
|
||||||
|
RatesByCurrency = new Dictionary<string, decimal>();
|
||||||
|
Rates = new List<RateJson>();
|
||||||
|
foreach(var rate in RatesInternal.OfType<JProperty>())
|
||||||
|
{
|
||||||
|
var rateJson = new RateJson();
|
||||||
|
rateJson.Code = rate.Name;
|
||||||
|
rateJson.Rate = rate.Value["rate"].Value<decimal>();
|
||||||
|
RatesByCurrency.Add(rate.Name, rateJson.Rate);
|
||||||
|
Rates.Add(rateJson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static HttpClient _Client = new HttpClient();
|
||||||
|
|
||||||
|
public string Market
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
} = "global";
|
||||||
|
public async Task<decimal> GetRateAsync(string currency)
|
||||||
|
{
|
||||||
|
RatesJson rates = await GetRatesCore();
|
||||||
|
return rates.GetRate(currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<RatesJson> GetRatesCore()
|
||||||
|
{
|
||||||
|
var resp = await _Client.GetAsync("https://apiv2.bitcoinaverage.com/constants/exchangerates/" + Market);
|
||||||
|
using(resp)
|
||||||
|
{
|
||||||
|
|
||||||
|
if((int)resp.StatusCode == 401)
|
||||||
|
throw new CoinAverageException("Unauthorized access to the API");
|
||||||
|
if((int)resp.StatusCode == 429)
|
||||||
|
throw new CoinAverageException("Exceed API limits");
|
||||||
|
if((int)resp.StatusCode == 403)
|
||||||
|
throw new CoinAverageException("Unauthorized access to the API, premium plan needed");
|
||||||
|
resp.EnsureSuccessStatusCode();
|
||||||
|
var rates = JsonConvert.DeserializeObject<RatesJson>(await resp.Content.ReadAsStringAsync());
|
||||||
|
rates.CalculateDictionary();
|
||||||
|
return rates;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ICollection<Rate>> GetRatesAsync()
|
||||||
|
{
|
||||||
|
RatesJson rates = await GetRatesCore();
|
||||||
|
return rates.Rates.Select(o => new Rate()
|
||||||
|
{
|
||||||
|
Currency = o.Code,
|
||||||
|
Value = rates.GetRate(o.Code)
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user