Compare commits
88 Commits
Author | SHA1 | Date | |
---|---|---|---|
6d6b9e2ba6 | |||
fd915fdc5c | |||
59be813fe9 | |||
465fbdd47f | |||
f220abb716 | |||
db46ca87d7 | |||
d873a1a545 | |||
a464a8702b | |||
63722b932a | |||
698b3c46cd | |||
df81051d07 | |||
ac70a77361 | |||
59a2432af9 | |||
ea4fa8d5d4 | |||
ade3eff75c | |||
db2a2a2b6c | |||
579dcb5af8 | |||
69247dee8a | |||
7b9541b8e9 | |||
a12e4d7f64 | |||
897da9b07a | |||
293525d480 | |||
198e810355 | |||
fe25e00c94 | |||
8b129ab2e5 | |||
770bed54d1 | |||
774817d4ac | |||
b8068b2ae8 | |||
3007a6bbc8 | |||
1c0c8fece2 | |||
c52eee47f0 | |||
f88c98b9d9 | |||
b0e9e10f7e | |||
39d47e33f6 | |||
4b7b6c6327 | |||
a59edc5e8c | |||
5ba322ea6a | |||
b47b4b10cb | |||
c88f391935 | |||
26d3178e93 | |||
1ad27c7827 | |||
4200a8eed5 | |||
daceb7af8e | |||
f703b53bce | |||
2762224f0f | |||
86fc64d184 | |||
726cd6fd49 | |||
be1c4666e0 | |||
b75dfc4191 | |||
97815f8daf | |||
af16e1db69 | |||
5f6913b3a2 | |||
2b31af80cb | |||
3f9889d374 | |||
f8189c64a4 | |||
c9b5f89f17 | |||
ecb82f2cc9 | |||
f340c6eb7f | |||
ba0e080816 | |||
bb3d107309 | |||
8517b222bf | |||
aed32204b5 | |||
6b4eeff3f1 | |||
e3cc589ebb | |||
4a152e8ffc | |||
d54a9474d1 | |||
98472211fc | |||
099c9fa1f9 | |||
5226b77ffc | |||
290779ee39 | |||
4f39a8060c | |||
92caa98dfb | |||
df7bb9e2f8 | |||
02a039d695 | |||
b5e4c803aa | |||
2b7c70622f | |||
88779e7129 | |||
6beb7abfd2 | |||
a1ebedc0d1 | |||
d5ad0cdb39 | |||
39fb8dbb6a | |||
58194cb060 | |||
ef165e15bf | |||
bf871c46ec | |||
52331e057f | |||
b59021a0be | |||
8596e16feb | |||
223558c01d |
@ -45,21 +45,13 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
public Uri LTCNBXplorerUri { get; set; }
|
||||
public string CookieFile
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Uri ServerUri
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public ExtKey HDPrivateKey
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Postgres
|
||||
{
|
||||
get; set;
|
||||
@ -75,29 +67,42 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
if (!Directory.Exists(_Directory))
|
||||
Directory.CreateDirectory(_Directory);
|
||||
string chain = ChainType.Regtest.ToNetwork().Name;
|
||||
string chainDirectory = Path.Combine(_Directory, chain);
|
||||
if (!Directory.Exists(chainDirectory))
|
||||
Directory.CreateDirectory(chainDirectory);
|
||||
|
||||
|
||||
HDPrivateKey = new ExtKey();
|
||||
StringBuilder config = new StringBuilder();
|
||||
config.AppendLine($"regtest=1");
|
||||
config.AppendLine($"{chain.ToLowerInvariant()}=1");
|
||||
config.AppendLine($"port={Port}");
|
||||
config.AppendLine($"explorer.url={NBXplorerUri.AbsoluteUri}");
|
||||
config.AppendLine($"chains=btc,ltc");
|
||||
|
||||
config.AppendLine($"btc.explorer.url={NBXplorerUri.AbsoluteUri}");
|
||||
config.AppendLine($"btc.explorer.cookiefile=0");
|
||||
|
||||
config.AppendLine($"ltc.explorer.url={LTCNBXplorerUri.AbsoluteUri}");
|
||||
config.AppendLine($"explorer.cookiefile={CookieFile}");
|
||||
config.AppendLine($"ltc.explorer.cookiefile={CookieFile}");
|
||||
config.AppendLine($"hdpubkey={HDPrivateKey.Neuter().ToString(Network.RegTest)}");
|
||||
config.AppendLine($"ltc.explorer.cookiefile=0");
|
||||
|
||||
if (Postgres != null)
|
||||
config.AppendLine($"postgres=" + Postgres);
|
||||
File.WriteAllText(Path.Combine(_Directory, "settings.config"), config.ToString());
|
||||
var confPath = Path.Combine(chainDirectory, "settings.config");
|
||||
File.WriteAllText(confPath, config.ToString());
|
||||
|
||||
ServerUri = new Uri("http://" + HostName + ":" + Port + "/");
|
||||
|
||||
var conf = new DefaultConfiguration() { Logger = Logs.LogProvider.CreateLogger("Console") }.CreateConfiguration(new[] { "--datadir", _Directory });
|
||||
var conf = new DefaultConfiguration() { Logger = Logs.LogProvider.CreateLogger("Console") }.CreateConfiguration(new[] { "--datadir", _Directory, "--conf", confPath });
|
||||
|
||||
_Host = new WebHostBuilder()
|
||||
.UseConfiguration(conf)
|
||||
.ConfigureServices(s =>
|
||||
{
|
||||
s.AddSingleton<IRateProvider>(new MockRateProvider(new Rate("USD", 5000m)));
|
||||
var mockRates = new MockRateProviderFactory();
|
||||
var btc = new MockRateProvider("BTC", new Rate("USD", 5000m));
|
||||
var ltc = new MockRateProvider("LTC", new Rate("USD", 500m));
|
||||
mockRates.AddMock(btc);
|
||||
mockRates.AddMock(ltc);
|
||||
s.AddSingleton<IRateProviderFactory>(mockRates);
|
||||
s.AddLogging(l =>
|
||||
{
|
||||
l.SetMinimumLevel(LogLevel.Information)
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM microsoft/dotnet:2.0.0-sdk
|
||||
FROM microsoft/dotnet:2.0.5-sdk-2.1.4
|
||||
WORKDIR /app
|
||||
# caches restore result by copying csproj file separately
|
||||
COPY BTCPayServer.Tests/BTCPayServer.Tests.csproj BTCPayServer.Tests/BTCPayServer.Tests.csproj
|
||||
|
@ -12,7 +12,7 @@ namespace BTCPayServer.Tests
|
||||
public EclairTester(ServerTester parent, string environmentName, string defaultRPC, string defaultHost)
|
||||
{
|
||||
this.parent = parent;
|
||||
RPC = new EclairRPCClient(new Uri(parent.GetEnvironment(environmentName, defaultRPC)), parent.Network);
|
||||
//RPC = new EclairRPCClient(new Uri(parent.GetEnvironment(environmentName, defaultRPC)), parent.Network);
|
||||
P2PHost = parent.GetEnvironment(environmentName + "_HOST", defaultHost);
|
||||
}
|
||||
|
||||
|
@ -47,12 +47,12 @@ namespace BTCPayServer.Tests
|
||||
Directory.CreateDirectory(_Directory);
|
||||
|
||||
|
||||
var network = new BTCPayNetworkProvider(Network);
|
||||
ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_RPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), network.GetNetwork("BTC").NBitcoinNetwork);
|
||||
LTCExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_LTCRPCCONNECTION", "server=http://127.0.0.1:43783;ceiwHEbqWI83:DwubwWsoo3")), network.GetNetwork("LTC").NBitcoinNetwork);
|
||||
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);
|
||||
|
||||
ExplorerClient = new ExplorerClient(Network, new Uri(GetEnvironment("TESTS_NBXPLORERURL", "http://127.0.0.1:32838/")));
|
||||
LTCExplorerClient = new ExplorerClient(Network, new Uri(GetEnvironment("TESTS_LTCNBXPLORERURL", "http://127.0.0.1:32839/")));
|
||||
ExplorerClient = new ExplorerClient(NetworkProvider.GetNetwork("BTC").NBXplorerNetwork, new Uri(GetEnvironment("TESTS_BTCNBXPLORERURL", "http://127.0.0.1:32838/")));
|
||||
LTCExplorerClient = new ExplorerClient(NetworkProvider.GetNetwork("LTC").NBXplorerNetwork, new Uri(GetEnvironment("TESTS_LTCNBXPLORERURL", "http://127.0.0.1:32838/")));
|
||||
|
||||
PayTester = new BTCPayServerTester(Path.Combine(_Directory, "pay"))
|
||||
{
|
||||
@ -108,6 +108,8 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
return new TestAccount(this);
|
||||
}
|
||||
|
||||
public BTCPayNetworkProvider NetworkProvider { get; private set; }
|
||||
public RPCClient ExplorerNode
|
||||
{
|
||||
get; set;
|
||||
@ -224,12 +226,6 @@ namespace BTCPayServer.Tests
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Network Network
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = Network.RegTest;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (PayTester != null)
|
||||
|
@ -46,31 +46,37 @@ namespace BTCPayServer.Tests
|
||||
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
|
||||
await store.Pair(pairingCode.ToString(), StoreId);
|
||||
}
|
||||
public StoresController CreateStore()
|
||||
public StoresController CreateStore(string cryptoCode = null)
|
||||
{
|
||||
return CreateStoreAsync().GetAwaiter().GetResult();
|
||||
return CreateStoreAsync(cryptoCode).GetAwaiter().GetResult();
|
||||
}
|
||||
public async Task<StoresController> CreateStoreAsync()
|
||||
|
||||
public string CryptoCode { get; set; } = "BTC";
|
||||
public async Task<StoresController> CreateStoreAsync(string cryptoCode = null)
|
||||
{
|
||||
ExtKey = new ExtKey().GetWif(parent.Network);
|
||||
cryptoCode = cryptoCode ?? CryptoCode;
|
||||
SupportedNetwork = parent.NetworkProvider.GetNetwork(cryptoCode);
|
||||
ExtKey = new ExtKey().GetWif(SupportedNetwork.NBitcoinNetwork);
|
||||
var store = parent.PayTester.GetController<StoresController>(UserId);
|
||||
await store.CreateStore(new CreateStoreViewModel() { Name = "Test Store" });
|
||||
StoreId = store.CreatedStoreId;
|
||||
DerivationScheme = new DerivationStrategyFactory(parent.Network).Parse(ExtKey.Neuter().ToString() + "-[legacy]");
|
||||
await store.UpdateStore(StoreId, new StoreViewModel()
|
||||
{
|
||||
SpeedPolicy = SpeedPolicy.MediumSpeed
|
||||
});
|
||||
DerivationScheme = new DerivationStrategyFactory(SupportedNetwork.NBitcoinNetwork).Parse(ExtKey.Neuter().ToString() + "-[legacy]");
|
||||
var vm = (StoreViewModel)((ViewResult)await store.UpdateStore(StoreId)).Model;
|
||||
vm.SpeedPolicy = SpeedPolicy.MediumSpeed;
|
||||
await store.UpdateStore(StoreId, vm);
|
||||
|
||||
await store.AddDerivationScheme(StoreId, new DerivationSchemeViewModel()
|
||||
{
|
||||
CryptoCurrency = "BTC",
|
||||
CryptoCurrency = cryptoCode,
|
||||
DerivationSchemeFormat = "BTCPay",
|
||||
DerivationScheme = DerivationScheme.ToString(),
|
||||
Confirmation = true
|
||||
}, "Save");
|
||||
return store;
|
||||
}
|
||||
|
||||
public BTCPayNetwork SupportedNetwork { get; set; }
|
||||
|
||||
public void RegisterDerivationScheme(string crytoCode)
|
||||
{
|
||||
RegisterDerivationSchemeAsync(crytoCode).GetAwaiter().GetResult();
|
||||
@ -85,6 +91,7 @@ namespace BTCPayServer.Tests
|
||||
CryptoCurrency = crytoCode,
|
||||
DerivationSchemeFormat = crytoCode,
|
||||
DerivationScheme = derivation.ToString(),
|
||||
Confirmation = true
|
||||
}, "Save");
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,8 @@ using BTCPayServer.Services.Rates;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using BTCPayServer.Eclair;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -43,11 +45,19 @@ namespace BTCPayServer.Tests
|
||||
entity.TxFee = Money.Coins(0.1m);
|
||||
entity.Rate = 5000;
|
||||
|
||||
var cryptoData = entity.GetCryptoData("BTC", null);
|
||||
Assert.NotNull(cryptoData); // Should use legacy data to build itself
|
||||
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
||||
|
||||
// Some check that handling legacy stuff does not break things
|
||||
var cryptoData = entity.GetCryptoData("BTC", null, true);
|
||||
cryptoData.Calculate();
|
||||
Assert.NotNull(cryptoData);
|
||||
Assert.Null(entity.GetCryptoData("BTC", null, false));
|
||||
entity.SetCryptoData(new CryptoData() { ParentEntity = entity, Rate = entity.Rate, CryptoCode = "BTC", TxFee = entity.TxFee });
|
||||
Assert.NotNull(entity.GetCryptoData("BTC", null, false));
|
||||
Assert.NotNull(entity.GetCryptoData("BTC", null, true));
|
||||
////////////////////
|
||||
|
||||
var accounting = cryptoData.Calculate();
|
||||
Assert.Equal(Money.Coins(1.1m), accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.1m), accounting.TotalDue);
|
||||
@ -270,7 +280,8 @@ namespace BTCPayServer.Tests
|
||||
OrderId = "orderId",
|
||||
NotificationURL = callbackServer.GetUri().AbsoluteUri,
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
FullNotifications = true,
|
||||
ExtendedNotifications = true
|
||||
});
|
||||
BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21);
|
||||
tester.ExplorerNode.SendToAddress(url.Address, url.Amount);
|
||||
@ -330,13 +341,13 @@ namespace BTCPayServer.Tests
|
||||
false, //subtractfeefromamount
|
||||
true, //replaceable
|
||||
}).ResultString);
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, tester.Network);
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, user.SupportedNetwork.NBitcoinNetwork);
|
||||
|
||||
Eventually(() =>
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.Equal(payment1, invoice.BtcPaid);
|
||||
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, tester.Network);
|
||||
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, user.SupportedNetwork.NBitcoinNetwork);
|
||||
});
|
||||
|
||||
var tx = tester.ExplorerNode.GetRawTransaction(new uint256(tx1));
|
||||
@ -351,7 +362,7 @@ namespace BTCPayServer.Tests
|
||||
change.Value -= (payment2 - payment1) * 2; //Add more fees
|
||||
var replaced = tester.ExplorerNode.SignRawTransaction(tx);
|
||||
tester.ExplorerNode.SendRawTransaction(replaced);
|
||||
var test = tester.ExplorerClient.Sync(user.DerivationScheme, null);
|
||||
var test = tester.ExplorerClient.GetUTXOs(user.DerivationScheme, null);
|
||||
Eventually(() =>
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
@ -384,6 +395,109 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void CanTweakRate()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
|
||||
|
||||
// First we try payment with a merchant having only BTC
|
||||
var invoice1 = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Price = 5000.0,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
|
||||
var storeController = tester.PayTester.GetController<StoresController>(user.UserId);
|
||||
var vm = (StoreViewModel)((ViewResult)storeController.UpdateStore(user.StoreId).Result).Model;
|
||||
Assert.Equal(1.0, vm.RateMultiplier);
|
||||
vm.RateMultiplier = 0.5;
|
||||
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);
|
||||
|
||||
Assert.True(invoice2.BtcPrice.Almost(invoice1.BtcPrice * 2, 0.00001m));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanHaveLTCOnlyStore()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var user = tester.NewAccount();
|
||||
user.CryptoCode = "LTC";
|
||||
user.GrantAccess();
|
||||
|
||||
// First we try payment with a merchant having only BTC
|
||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Price = 500,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
Assert.Single(invoice.CryptoInfo);
|
||||
Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode);
|
||||
var cashCow = tester.LTCExplorerNode;
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
|
||||
var firstPayment = Money.Coins(0.1m);
|
||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
Eventually(() =>
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.Equal(firstPayment, invoice.CryptoInfo[0].Paid);
|
||||
});
|
||||
|
||||
Assert.Single(invoice.CryptoInfo); // Only BTC should be presented
|
||||
|
||||
var controller = tester.PayTester.GetController<InvoiceController>(null);
|
||||
var checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null).GetAwaiter().GetResult()).Value;
|
||||
Assert.Single(checkout.AvailableCryptos);
|
||||
Assert.Equal("LTC", checkout.CryptoCode);
|
||||
|
||||
//////////////////////
|
||||
|
||||
// Despite it is called BitcoinAddress it should be LTC because BTC is not available
|
||||
Assert.Null(invoice.BitcoinAddress);
|
||||
Assert.NotEqual(1.0, invoice.Rate);
|
||||
Assert.NotEqual(invoice.BtcDue, invoice.CryptoInfo[0].Due); // Should be BTC rate
|
||||
cashCow.SendToAddress(invoiceAddress, invoice.CryptoInfo[0].Due);
|
||||
|
||||
Eventually(() =>
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.Equal("paid", invoice.Status);
|
||||
checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null).GetAwaiter().GetResult()).Value;
|
||||
Assert.Equal("paid", checkout.Status);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanPayWithTwoCurrencies()
|
||||
{
|
||||
@ -405,6 +519,7 @@ namespace BTCPayServer.Tests
|
||||
}, Facade.Merchant);
|
||||
|
||||
var cashCow = tester.ExplorerNode;
|
||||
cashCow.Generate(2); // get some money in case
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
var firstPayment = Money.Coins(0.04m);
|
||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
@ -439,6 +554,7 @@ namespace BTCPayServer.Tests
|
||||
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
firstPayment = Money.Coins(0.04m);
|
||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
Logs.Tester.LogInformation("First payment sent to " + invoiceAddress);
|
||||
Eventually(() =>
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
@ -452,7 +568,7 @@ namespace BTCPayServer.Tests
|
||||
var secondPayment = Money.Coins(decimal.Parse(ltcCryptoInfo.Due));
|
||||
cashCow.Generate(2); // LTC is not worth a lot, so just to make sure we have money...
|
||||
cashCow.SendToAddress(invoiceAddress, secondPayment);
|
||||
|
||||
Logs.Tester.LogInformation("Second payment sent to " + invoiceAddress);
|
||||
Eventually(() =>
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
@ -637,6 +753,10 @@ namespace BTCPayServer.Tests
|
||||
var b = cached.GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||
//Manually check that cache get hit after 10 sec
|
||||
var c = cached.GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||
|
||||
var bitstamp = new CoinAverageRateProvider("BTC") { Exchange = "bitstamp" };
|
||||
var bitstampRate = bitstamp.GetRateAsync("USD").GetAwaiter().GetResult();
|
||||
Assert.Throws<RateUnavailableException>(() => bitstamp.GetRateAsync("XXXXX").GetAwaiter().GetResult());
|
||||
}
|
||||
|
||||
private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx)
|
||||
@ -647,7 +767,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
private void Eventually(Action act)
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
||||
CancellationTokenSource cts = new CancellationTokenSource(20000);
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
|
@ -13,25 +13,27 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
// Unit test that generates temorary checkout Bitpay page
|
||||
// https://forkbitpay.slack.com/archives/C7M093Z55/p1508293682000217
|
||||
[Fact]
|
||||
public void BitpayCheckout()
|
||||
{
|
||||
var key = new Key(Encoders.Hex.DecodeData("7b70a06f35562873e3dcb46005ed0fe78e1991ad906e56adaaafa40ba861e056"));
|
||||
var url = new Uri("https://test.bitpay.com/");
|
||||
var btcpay = new Bitpay(key, url);
|
||||
var invoice = btcpay.CreateInvoice(new Invoice()
|
||||
{
|
||||
|
||||
Price = 5.0,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "cdfd8a5f-6928-4c3b-ba9b-ddf438029e73",
|
||||
ItemDesc = "Hello from the otherside"
|
||||
}, Facade.Merchant);
|
||||
// Testnet of Bitpay down
|
||||
//[Fact]
|
||||
//public void BitpayCheckout()
|
||||
//{
|
||||
// var key = new Key(Encoders.Hex.DecodeData("7b70a06f35562873e3dcb46005ed0fe78e1991ad906e56adaaafa40ba861e056"));
|
||||
// var url = new Uri("https://test.bitpay.com/");
|
||||
// var btcpay = new Bitpay(key, url);
|
||||
// var invoice = btcpay.CreateInvoice(new Invoice()
|
||||
// {
|
||||
|
||||
// go to invoice.Url
|
||||
Console.WriteLine(invoice.Url);
|
||||
}
|
||||
// Price = 5.0,
|
||||
// Currency = "USD",
|
||||
// PosData = "posData",
|
||||
// OrderId = "cdfd8a5f-6928-4c3b-ba9b-ddf438029e73",
|
||||
// ItemDesc = "Hello from the otherside"
|
||||
// }, Facade.Merchant);
|
||||
|
||||
// // go to invoice.Url
|
||||
// Console.WriteLine(invoice.Url);
|
||||
//}
|
||||
|
||||
// Generating Extended public key to use on http://localhost:14142/stores/{storeId}
|
||||
[Fact]
|
||||
|
@ -10,18 +10,17 @@ services:
|
||||
context: ..
|
||||
dockerfile: BTCPayServer.Tests/Dockerfile
|
||||
environment:
|
||||
TESTS_RPCCONNECTION: server=http://bitcoind:43782;ceiwHEbqWI83:DwubwWsoo3
|
||||
TESTS_BTCRPCCONNECTION: server=http://bitcoind:43782;ceiwHEbqWI83:DwubwWsoo3
|
||||
TESTS_LTCRPCCONNECTION: server=http://litecoind:43782;ceiwHEbqWI83:DwubwWsoo3
|
||||
TESTS_NBXPLORERURL: http://bitcoin-nbxplorer:32838/
|
||||
TESTS_LTCNBXPLORERURL: http://litecoin-nbxplorer:32839/
|
||||
TESTS_BTCNBXPLORERURL: http://nbxplorer:32838/
|
||||
TESTS_LTCNBXPLORERURL: http://nbxplorer:32838/
|
||||
TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver
|
||||
TESTS_PORT: 80
|
||||
TESTS_HOSTNAME: tests
|
||||
expose:
|
||||
- "80"
|
||||
links:
|
||||
- bitcoin-nbxplorer
|
||||
- litecoin-nbxplorer
|
||||
- nbxplorer
|
||||
- postgres
|
||||
extra_hosts:
|
||||
- "tests:127.0.0.1"
|
||||
@ -34,45 +33,32 @@ services:
|
||||
regtest=1
|
||||
connect=bitcoind:39388
|
||||
links:
|
||||
- bitcoin-nbxplorer
|
||||
- litecoin-nbxplorer
|
||||
- nbxplorer
|
||||
- postgres
|
||||
|
||||
bitcoin-nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:1.0.0.45
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:1.0.1.11
|
||||
ports:
|
||||
- "32838:32838"
|
||||
expose:
|
||||
- "32838"
|
||||
environment:
|
||||
NBXPLORER_NETWORK: regtest
|
||||
NBXPLORER_RPCURL: http://bitcoind:43782/
|
||||
NBXPLORER_RPCUSER: ceiwHEbqWI83
|
||||
NBXPLORER_RPCPASSWORD: DwubwWsoo3
|
||||
NBXPLORER_NODEENDPOINT: bitcoind:39388
|
||||
NBXPLORER_CHAINS: "btc,ltc"
|
||||
NBXPLORER_BTCRPCURL: http://bitcoind:43782/
|
||||
NBXPLORER_BTCNODEENDPOINT: bitcoind:39388
|
||||
NBXPLORER_BTCRPCUSER: ceiwHEbqWI83
|
||||
NBXPLORER_BTCRPCPASSWORD: DwubwWsoo3
|
||||
NBXPLORER_LTCRPCURL: http://litecoind:43782/
|
||||
NBXPLORER_LTCNODEENDPOINT: litecoind:39388
|
||||
NBXPLORER_LTCRPCUSER: ceiwHEbqWI83
|
||||
NBXPLORER_LTCRPCPASSWORD: DwubwWsoo3
|
||||
NBXPLORER_BIND: 0.0.0.0:32838
|
||||
NBXPLORER_VERBOSE: 1
|
||||
NBXPLORER_NOAUTH: 1
|
||||
links:
|
||||
- bitcoind
|
||||
|
||||
litecoin-nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:1.0.0.45
|
||||
ports:
|
||||
- "32839:32839"
|
||||
expose:
|
||||
- "32839"
|
||||
environment:
|
||||
NBXPLORER_NETWORK: ltc-regtest
|
||||
NBXPLORER_RPCURL: http://litecoind:43782/
|
||||
NBXPLORER_RPCUSER: ceiwHEbqWI83
|
||||
NBXPLORER_RPCPASSWORD: DwubwWsoo3
|
||||
NBXPLORER_NODEENDPOINT: litecoind:39388
|
||||
NBXPLORER_BIND: 0.0.0.0:32839
|
||||
NBXPLORER_VERBOSE: 1
|
||||
NBXPLORER_NOAUTH: 1
|
||||
links:
|
||||
- litecoind
|
||||
- litecoind
|
||||
|
||||
bitcoind:
|
||||
container_name: btcpayserver_dev_bitcoind
|
||||
@ -86,15 +72,11 @@ services:
|
||||
rpcport=43782
|
||||
port=39388
|
||||
whitelist=0.0.0.0/0
|
||||
zmqpubrawblock=tcp://0.0.0.0:29000
|
||||
zmqpubrawtx=tcp://0.0.0.0:29000
|
||||
txindex=1
|
||||
ports:
|
||||
- "43782:43782"
|
||||
expose:
|
||||
- "43782" # RPC
|
||||
- "39388" # P2P
|
||||
- "29000" # zmq
|
||||
|
||||
litecoind:
|
||||
container_name: btcpayserver_dev_litecoind
|
||||
@ -108,15 +90,11 @@ services:
|
||||
rpcport=43782
|
||||
port=39388
|
||||
whitelist=0.0.0.0/0
|
||||
zmqpubrawblock=tcp://0.0.0.0:29000
|
||||
zmqpubrawtx=tcp://0.0.0.0:29000
|
||||
txindex=1
|
||||
ports:
|
||||
- "43783:43782"
|
||||
expose:
|
||||
- "43782" # RPC
|
||||
- "39388" # P2P
|
||||
- "29000" # zmq
|
||||
|
||||
postgres:
|
||||
image: postgres:9.6.5
|
||||
|
@ -1,12 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class BTCPayDefaultSettings
|
||||
{
|
||||
static BTCPayDefaultSettings()
|
||||
{
|
||||
_Settings = new Dictionary<ChainType, BTCPayDefaultSettings>();
|
||||
foreach (var chainType in new[] { ChainType.Main, ChainType.Test, ChainType.Regtest })
|
||||
{
|
||||
var btcNetwork = (chainType == ChainType.Main ? Network.Main :
|
||||
chainType == ChainType.Regtest ? Network.RegTest :
|
||||
chainType == ChainType.Test ? Network.TestNet : throw new NotSupportedException(chainType.ToString()));
|
||||
|
||||
var settings = new BTCPayDefaultSettings();
|
||||
_Settings.Add(chainType, settings);
|
||||
settings.ChainType = chainType;
|
||||
settings.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", btcNetwork.Name);
|
||||
settings.DefaultConfigurationFile = Path.Combine(settings.DefaultDataDirectory, "settings.config");
|
||||
settings.DefaultPort = (chainType == ChainType.Main ? 23000 :
|
||||
chainType == ChainType.Regtest ? 23002 :
|
||||
chainType == ChainType.Test ? 23001 : throw new NotSupportedException(chainType.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
static Dictionary<ChainType, BTCPayDefaultSettings> _Settings;
|
||||
|
||||
public static BTCPayDefaultSettings GetDefaultSettings(ChainType chainType)
|
||||
{
|
||||
return _Settings[chainType];
|
||||
}
|
||||
|
||||
public string DefaultDataDirectory { get; set; }
|
||||
public string DefaultConfigurationFile { get; set; }
|
||||
public ChainType ChainType { get; internal set; }
|
||||
public int DefaultPort { get; set; }
|
||||
}
|
||||
public class BTCPayNetwork
|
||||
{
|
||||
public Network NBitcoinNetwork { get; set; }
|
||||
@ -25,6 +61,10 @@ namespace BTCPayServer
|
||||
}
|
||||
|
||||
public string CryptoImagePath { get; set; }
|
||||
public NBXplorer.NBXplorerNetwork NBXplorerNetwork { get; set; }
|
||||
|
||||
|
||||
public BTCPayDefaultSettings DefaultSettings { get; set; }
|
||||
public override string ToString()
|
||||
{
|
||||
return CryptoCode;
|
||||
|
33
BTCPayServer/BTCPayNetworkProvider.Bitcoin.cs
Normal file
33
BTCPayServer/BTCPayNetworkProvider.Bitcoin.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using NBXplorer;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public partial class BTCPayNetworkProvider
|
||||
{
|
||||
public void InitBitcoin()
|
||||
{
|
||||
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("BTC");
|
||||
var coinaverage = new CoinAverageRateProvider("BTC");
|
||||
var bitpay = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/")));
|
||||
var btcRate = new FallbackRateProvider(new IRateProvider[] { coinaverage, bitpay });
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
BlockExplorerLink = NBXplorerNetworkProvider.ChainType == ChainType.Main? "https://www.smartbit.com.au/tx/{0}" : "https://testnet.smartbit.com.au/tx/{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "bitcoin",
|
||||
DefaultRateProvider = btcRate,
|
||||
CryptoImagePath = "imlegacy/bitcoin-symbol.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NBXplorerNetworkProvider.ChainType)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
31
BTCPayServer/BTCPayNetworkProvider.Litecoin.cs
Normal file
31
BTCPayServer/BTCPayNetworkProvider.Litecoin.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using NBXplorer;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public partial class BTCPayNetworkProvider
|
||||
{
|
||||
public void InitLitecoin()
|
||||
{
|
||||
NBXplorer.Altcoins.Litecoin.Networks.EnsureRegistered();
|
||||
var ltcRate = new CoinAverageRateProvider("LTC");
|
||||
|
||||
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("LTC");
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
BlockExplorerLink = NBXplorerNetworkProvider.ChainType == ChainType.Main ? "https://live.blockcypher.com/ltc/tx/{0}/" : "http://explorer.litecointools.com/tx/{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "litecoin",
|
||||
DefaultRateProvider = ltcRate,
|
||||
CryptoImagePath = "imlegacy/litecoin-symbol.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NBXplorerNetworkProvider.ChainType)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,93 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using NBXplorer;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class BTCPayNetworkProvider
|
||||
public partial class BTCPayNetworkProvider
|
||||
{
|
||||
static BTCPayNetworkProvider()
|
||||
{
|
||||
NBXplorer.Altcoins.Litecoin.Networks.EnsureRegistered();
|
||||
}
|
||||
Dictionary<string, BTCPayNetwork> _Networks = new Dictionary<string, BTCPayNetwork>();
|
||||
public BTCPayNetworkProvider(Network network)
|
||||
|
||||
|
||||
private readonly NBXplorerNetworkProvider _NBXplorerNetworkProvider;
|
||||
public NBXplorerNetworkProvider NBXplorerNetworkProvider
|
||||
{
|
||||
var coinaverage = new CoinAverageRateProvider("BTC");
|
||||
var bitpay = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/")));
|
||||
var btcRate = new FallbackRateProvider(new IRateProvider[] { coinaverage, bitpay });
|
||||
|
||||
var ltcRate = new CoinAverageRateProvider("LTC");
|
||||
if (network == Network.Main)
|
||||
get
|
||||
{
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = "BTC",
|
||||
BlockExplorerLink = "https://www.smartbit.com.au/tx/{0}",
|
||||
NBitcoinNetwork = Network.Main,
|
||||
UriScheme = "bitcoin",
|
||||
DefaultRateProvider = btcRate,
|
||||
CryptoImagePath = "imlegacy/bitcoin-symbol.svg"
|
||||
});
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = "LTC",
|
||||
BlockExplorerLink = "https://live.blockcypher.com/ltc/tx/{0}/",
|
||||
NBitcoinNetwork = NBXplorer.Altcoins.Litecoin.Networks.Mainnet,
|
||||
UriScheme = "litecoin",
|
||||
DefaultRateProvider = ltcRate,
|
||||
CryptoImagePath = "imlegacy/litecoin-symbol.svg"
|
||||
});
|
||||
return _NBXplorerNetworkProvider;
|
||||
}
|
||||
}
|
||||
|
||||
if (network == Network.TestNet)
|
||||
{
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = "BTC",
|
||||
BlockExplorerLink = "https://testnet.smartbit.com.au/tx/{0}",
|
||||
NBitcoinNetwork = Network.TestNet,
|
||||
UriScheme = "bitcoin",
|
||||
DefaultRateProvider = btcRate,
|
||||
CryptoImagePath = "imlegacy/bitcoin-symbol.svg"
|
||||
});
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = "LTC",
|
||||
BlockExplorerLink = "http://explorer.litecointools.com/tx/{0}",
|
||||
NBitcoinNetwork = NBXplorer.Altcoins.Litecoin.Networks.Testnet,
|
||||
UriScheme = "litecoin",
|
||||
DefaultRateProvider = ltcRate,
|
||||
CryptoImagePath = "imlegacy/litecoin-symbol.svg"
|
||||
});
|
||||
}
|
||||
|
||||
if (network == Network.RegTest)
|
||||
{
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = "BTC",
|
||||
BlockExplorerLink = "https://testnet.smartbit.com.au/tx/{0}",
|
||||
NBitcoinNetwork = Network.RegTest,
|
||||
UriScheme = "bitcoin",
|
||||
DefaultRateProvider = btcRate,
|
||||
CryptoImagePath = "imlegacy/bitcoin-symbol.svg"
|
||||
});
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = "LTC",
|
||||
BlockExplorerLink = "http://explorer.litecointools.com/tx/{0}",
|
||||
NBitcoinNetwork = NBXplorer.Altcoins.Litecoin.Networks.Regtest,
|
||||
UriScheme = "litecoin",
|
||||
DefaultRateProvider = ltcRate,
|
||||
CryptoImagePath = "imlegacy/litecoin-symbol.svg"
|
||||
});
|
||||
}
|
||||
public BTCPayNetworkProvider(ChainType chainType)
|
||||
{
|
||||
_NBXplorerNetworkProvider = new NBXplorerNetworkProvider(chainType);
|
||||
InitBitcoin();
|
||||
InitLitecoin();
|
||||
}
|
||||
|
||||
[Obsolete("To use only for legacy stuff")]
|
||||
|
@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<Version>1.0.0.65</Version>
|
||||
<Version>1.0.1.21</Version>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Build\dockerfiles\**" />
|
||||
@ -21,22 +21,22 @@
|
||||
<PackageReference Include="Hangfire.MemoryStorage" Version="1.5.2" />
|
||||
<PackageReference Include="Hangfire.PostgreSql" Version="1.4.8.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
|
||||
<PackageReference Include="NBitcoin" Version="4.0.0.51" />
|
||||
<PackageReference Include="NBitcoin" Version="4.0.0.54" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.16" />
|
||||
<PackageReference Include="DBreeze" Version="1.87.0" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="1.0.0.28" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="1.0.1.8" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.1" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.2" />
|
||||
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.13" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.0.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.0.1" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
|
||||
<PackageReference Include="System.Xml.XmlSerializer" Version="4.0.11" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.1" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.1" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.2" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -13,9 +13,16 @@ using NBXplorer;
|
||||
|
||||
namespace BTCPayServer.Configuration
|
||||
{
|
||||
public class NBXplorerConnectionSetting
|
||||
{
|
||||
public string CryptoCode { get; internal set; }
|
||||
public Uri ExplorerUri { get; internal set; }
|
||||
public string CookieFile { get; internal set; }
|
||||
}
|
||||
|
||||
public class BTCPayServerOptions
|
||||
{
|
||||
public Network Network
|
||||
public ChainType ChainType
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
@ -35,56 +42,44 @@ namespace BTCPayServer.Configuration
|
||||
set;
|
||||
}
|
||||
|
||||
public List<NBXplorerConnectionSetting> NBXplorerConnectionSettings
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = new List<NBXplorerConnectionSetting>();
|
||||
|
||||
public void LoadArgs(IConfiguration conf)
|
||||
{
|
||||
var networkInfo = DefaultConfiguration.GetNetwork(conf);
|
||||
Network = networkInfo?.Network;
|
||||
if (Network == null)
|
||||
throw new ConfigException("Invalid network");
|
||||
ChainType = DefaultConfiguration.GetChainType(conf);
|
||||
var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(ChainType);
|
||||
DataDir = conf.GetOrDefault<string>("datadir", defaultSettings.DefaultDataDirectory);
|
||||
Logs.Configuration.LogInformation("Network: " + ChainType.ToString());
|
||||
|
||||
DataDir = conf.GetOrDefault<string>("datadir", networkInfo.DefaultDataDirectory);
|
||||
Logs.Configuration.LogInformation("Network: " + Network);
|
||||
|
||||
|
||||
bool btcHandled = false;
|
||||
foreach (var net in new BTCPayNetworkProvider(Network).GetAll())
|
||||
var supportedChains = conf.GetOrDefault<string>("chains", "btc")
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(t => t.ToUpperInvariant());
|
||||
var validChains = new List<string>();
|
||||
foreach (var net in new BTCPayNetworkProvider(ChainType).GetAll())
|
||||
{
|
||||
var nbxplorer = NBXplorer.Configuration.NetworkInformation.GetNetworkByName(net.NBitcoinNetwork.Name);
|
||||
var explorer = conf.GetOrDefault<Uri>($"{net.CryptoCode}.explorer.url", null);
|
||||
var cookieFile = conf.GetOrDefault<string>($"{net.CryptoCode}.explorer.cookiefile", nbxplorer.GetDefaultCookieFile());
|
||||
if (explorer != null)
|
||||
if (supportedChains.Contains(net.CryptoCode))
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
if (net.IsBTC)
|
||||
btcHandled = true;
|
||||
#pragma warning restore CS0618
|
||||
ExplorerFactories.Add(net.CryptoCode, (n) => CreateExplorerClient(n, explorer, cookieFile));
|
||||
validChains.Add(net.CryptoCode);
|
||||
NBXplorerConnectionSetting setting = new NBXplorerConnectionSetting();
|
||||
setting.CryptoCode = net.CryptoCode;
|
||||
setting.ExplorerUri = conf.GetOrDefault<Uri>($"{net.CryptoCode}.explorer.url", net.NBXplorerNetwork.DefaultSettings.DefaultUrl);
|
||||
setting.CookieFile = conf.GetOrDefault<string>($"{net.CryptoCode}.explorer.cookiefile", net.NBXplorerNetwork.DefaultSettings.DefaultCookieFile);
|
||||
NBXplorerConnectionSettings.Add(setting);
|
||||
}
|
||||
}
|
||||
var invalidChains = String.Join(',', supportedChains.Where(s => !validChains.Contains(s)).ToArray());
|
||||
if(!string.IsNullOrEmpty(invalidChains))
|
||||
throw new ConfigException($"Invalid chains {invalidChains}");
|
||||
|
||||
// Handle legacy explorer.url and explorer.cookiefile
|
||||
if (!btcHandled)
|
||||
{
|
||||
var nbxplorer = NBXplorer.Configuration.NetworkInformation.GetNetworkByName(Network.Name); // Will get BTC info
|
||||
var explorer = conf.GetOrDefault<Uri>($"explorer.url", new Uri(nbxplorer.GetDefaultExplorerUrl(), UriKind.Absolute));
|
||||
var cookieFile = conf.GetOrDefault<string>($"explorer.cookiefile", nbxplorer.GetDefaultCookieFile());
|
||||
ExplorerFactories.Add("BTC", (n) => CreateExplorerClient(n, explorer, cookieFile));
|
||||
}
|
||||
//////
|
||||
|
||||
Logs.Configuration.LogInformation("Supported chains: " + String.Join(',', supportedChains.ToArray()));
|
||||
PostgresConnectionString = conf.GetOrDefault<string>("postgres", null);
|
||||
ExternalUrl = conf.GetOrDefault<Uri>("externalurl", null);
|
||||
}
|
||||
|
||||
private static ExplorerClient CreateExplorerClient(BTCPayNetwork n, Uri uri, string cookieFile)
|
||||
{
|
||||
var explorer = new ExplorerClient(n.NBitcoinNetwork, uri);
|
||||
if (!explorer.SetCookieAuth(cookieFile))
|
||||
explorer.SetNoAuth();
|
||||
return explorer;
|
||||
}
|
||||
|
||||
public Dictionary<string, Func<BTCPayNetwork, ExplorerClient>> ExplorerFactories = new Dictionary<string, Func<BTCPayNetwork, ExplorerClient>>();
|
||||
public string PostgresConnectionString
|
||||
{
|
||||
get;
|
||||
|
@ -10,6 +10,7 @@ using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
using System.Text;
|
||||
using CommandLine;
|
||||
using NBXplorer;
|
||||
|
||||
namespace BTCPayServer.Configuration
|
||||
{
|
||||
@ -17,20 +18,24 @@ namespace BTCPayServer.Configuration
|
||||
{
|
||||
protected override CommandLineApplication CreateCommandLineApplicationCore()
|
||||
{
|
||||
var provider = new BTCPayNetworkProvider(ChainType.Main);
|
||||
var chains = string.Join(",", provider.GetAll().Select(n => n.CryptoCode.ToLowerInvariant()).ToArray());
|
||||
CommandLineApplication app = new CommandLineApplication(true)
|
||||
{
|
||||
FullName = "BTCPay\r\nOpen source, self-hosted payment processor.",
|
||||
Name = "BTCPay"
|
||||
};
|
||||
app.HelpOption("-? | -h | --help");
|
||||
app.Option("-n | --network", $"Set the network among ({NetworkInformation.ToStringAll()}) (default: {Network.Main.ToString()})", CommandOptionType.SingleValue);
|
||||
app.Option("--testnet | -testnet", $"Use testnet", CommandOptionType.BoolValue);
|
||||
app.Option("--regtest | -regtest", $"Use regtest", CommandOptionType.BoolValue);
|
||||
app.Option("-n | --network", $"Set the network among (mainnet,testnet,regtest) (default: mainnet)", CommandOptionType.SingleValue);
|
||||
app.Option("--testnet | -testnet", $"Use testnet (Deprecated, use --network instead)", CommandOptionType.BoolValue);
|
||||
app.Option("--regtest | -regtest", $"Use regtest (Deprecated, use --network instead)", CommandOptionType.BoolValue);
|
||||
app.Option("--chains | -c", $"Chains to support comma separated (default: btc, available: {chains})", CommandOptionType.SingleValue);
|
||||
app.Option("--postgres", $"Connection string to postgres database (default: sqlite is used)", CommandOptionType.SingleValue);
|
||||
foreach (var network in new BTCPayNetworkProvider(Network.Main).GetAll())
|
||||
foreach (var network in provider.GetAll())
|
||||
{
|
||||
app.Option($"--{network.CryptoCode}explorerurl", $"Url of the NBxplorer for {network.CryptoCode} (default: If no explorer is specified, the default for Bitcoin will be selected)", CommandOptionType.SingleValue);
|
||||
app.Option($"--{network.CryptoCode}explorercookiefile", $"Path to the cookie file (default: Default setting of NBXplorer for the network)", CommandOptionType.SingleValue);
|
||||
var crypto = network.CryptoCode.ToLowerInvariant();
|
||||
app.Option($"--{crypto}explorerurl", $"Url of the NBxplorer for {network.CryptoCode} (default: {network.NBXplorerNetwork.DefaultSettings.DefaultUrl})", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}explorercookiefile", $"Path to the cookie file (default: {network.NBXplorerNetwork.DefaultSettings.DefaultCookieFile})", CommandOptionType.SingleValue);
|
||||
}
|
||||
app.Option("--externalurl", $"The expected external url of this service, to use if BTCPay is behind a reverse proxy (default: empty, use the incoming HTTP request to figure out)", CommandOptionType.SingleValue);
|
||||
return app;
|
||||
@ -40,57 +45,64 @@ namespace BTCPayServer.Configuration
|
||||
|
||||
protected override string GetDefaultDataDir(IConfiguration conf)
|
||||
{
|
||||
return GetNetwork(conf).DefaultDataDirectory;
|
||||
return BTCPayDefaultSettings.GetDefaultSettings(GetChainType(conf)).DefaultDataDirectory;
|
||||
}
|
||||
|
||||
protected override string GetDefaultConfigurationFile(IConfiguration conf)
|
||||
{
|
||||
var network = GetNetwork(conf);
|
||||
var network = BTCPayDefaultSettings.GetDefaultSettings(GetChainType(conf));
|
||||
var dataDir = conf["datadir"];
|
||||
if (dataDir == null)
|
||||
return network.DefaultConfigurationFile;
|
||||
var fileName = Path.GetFileName(network.DefaultConfigurationFile);
|
||||
return Path.Combine(dataDir, fileName);
|
||||
var chainDir = Path.GetFileName(Path.GetDirectoryName(network.DefaultConfigurationFile));
|
||||
chainDir = Path.Combine(dataDir, chainDir);
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(chainDir))
|
||||
Directory.CreateDirectory(chainDir);
|
||||
}
|
||||
catch { }
|
||||
return Path.Combine(chainDir, fileName);
|
||||
}
|
||||
|
||||
public static NetworkInformation GetNetwork(IConfiguration conf)
|
||||
public static ChainType GetChainType(IConfiguration conf)
|
||||
{
|
||||
var network = conf.GetOrDefault<string>("network", null);
|
||||
if (network != null)
|
||||
{
|
||||
var info = NetworkInformation.GetNetworkByName(network);
|
||||
if (info == null)
|
||||
throw new ConfigException($"Invalid network name {network}");
|
||||
return info;
|
||||
var n = Network.GetNetwork(network);
|
||||
if (n == null)
|
||||
{
|
||||
throw new ConfigException($"Invalid network parameter '{network}'");
|
||||
}
|
||||
return n.ToChainType();
|
||||
}
|
||||
var net = conf.GetOrDefault<bool>("regtest", false) ? ChainType.Regtest :
|
||||
conf.GetOrDefault<bool>("testnet", false) ? ChainType.Test : ChainType.Main;
|
||||
|
||||
var net = conf.GetOrDefault<bool>("regtest", false) ? Network.RegTest :
|
||||
conf.GetOrDefault<bool>("testnet", false) ? Network.TestNet : Network.Main;
|
||||
|
||||
return NetworkInformation.GetNetworkByName(net.Name);
|
||||
return net;
|
||||
}
|
||||
|
||||
protected override string GetDefaultConfigurationFileTemplate(IConfiguration conf)
|
||||
{
|
||||
var network = GetNetwork(conf);
|
||||
var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(GetChainType(conf));
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.AppendLine("### Global settings ###");
|
||||
builder.AppendLine("#testnet=0");
|
||||
builder.AppendLine("#regtest=0");
|
||||
builder.AppendLine("#network=mainnet");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("### Server settings ###");
|
||||
builder.AppendLine("#port=" + network.DefaultPort);
|
||||
builder.AppendLine("#port=" + defaultSettings.DefaultPort);
|
||||
builder.AppendLine("#bind=127.0.0.1");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("### Database ###");
|
||||
builder.AppendLine("#postgres=User ID=root;Password=myPassword;Host=localhost;Port=5432;Database=myDataBase;");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("### NBXplorer settings ###");
|
||||
foreach (var n in new BTCPayNetworkProvider(network.Network).GetAll())
|
||||
foreach (var n in new BTCPayNetworkProvider(defaultSettings.ChainType).GetAll())
|
||||
{
|
||||
var nbxplorer = NBXplorer.Configuration.NetworkInformation.GetNetworkByName(n.NBitcoinNetwork.ToString());
|
||||
builder.AppendLine($"#{n.CryptoCode}.explorer.url={nbxplorer.GetDefaultExplorerUrl()}");
|
||||
builder.AppendLine($"#{n.CryptoCode}.explorer.cookiefile={ nbxplorer.GetDefaultCookieFile()}");
|
||||
builder.AppendLine($"#{n.CryptoCode}.explorer.url={n.NBXplorerNetwork.DefaultSettings.DefaultUrl}");
|
||||
builder.AppendLine($"#{n.CryptoCode}.explorer.cookiefile={ n.NBXplorerNetwork.DefaultSettings.DefaultCookieFile}");
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
@ -99,7 +111,7 @@ namespace BTCPayServer.Configuration
|
||||
|
||||
protected override IPEndPoint GetDefaultEndpoint(IConfiguration conf)
|
||||
{
|
||||
return new IPEndPoint(IPAddress.Parse("127.0.0.1"), GetNetwork(conf).DefaultPort);
|
||||
return new IPEndPoint(IPAddress.Parse("127.0.0.1"), BTCPayDefaultSettings.GetDefaultSettings(GetChainType(conf)).DefaultPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,83 +0,0 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Configuration
|
||||
{
|
||||
public class NetworkInformation
|
||||
{
|
||||
static NetworkInformation()
|
||||
{
|
||||
_Networks = new Dictionary<string, NetworkInformation>();
|
||||
foreach (var network in new[] { Network.Main, Network.TestNet, Network.RegTest })
|
||||
{
|
||||
NetworkInformation info = new NetworkInformation();
|
||||
info.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", network.Name);
|
||||
info.DefaultConfigurationFile = Path.Combine(info.DefaultDataDirectory, "settings.config");
|
||||
info.Network = network;
|
||||
info.DefaultPort = 23002;
|
||||
_Networks.Add(network.Name, info);
|
||||
if (network == Network.Main)
|
||||
{
|
||||
info.DefaultPort = 23000;
|
||||
}
|
||||
if (network == Network.TestNet)
|
||||
{
|
||||
info.DefaultPort = 23001;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Dictionary<string, NetworkInformation> _Networks;
|
||||
public static NetworkInformation GetNetworkByName(string name)
|
||||
{
|
||||
var value = _Networks.TryGet(name);
|
||||
if (value != null)
|
||||
return value;
|
||||
|
||||
//Maybe alias ?
|
||||
var network = Network.GetNetwork(name);
|
||||
if (network != null)
|
||||
{
|
||||
value = _Networks.TryGet(network.Name);
|
||||
if (value != null)
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Network Network
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string DefaultConfigurationFile
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string DefaultDataDirectory
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public int DefaultPort
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Network.ToString();
|
||||
}
|
||||
|
||||
public static string ToStringAll()
|
||||
{
|
||||
return string.Join(", ", _Networks.Select(n => n.Key).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ using BTCPayServer.Models.AccountViewModels;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Mails;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Logging;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -25,10 +26,10 @@ namespace BTCPayServer.Controllers
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly IEmailSender _emailSender;
|
||||
private readonly ILogger _logger;
|
||||
StoreRepository storeRepository;
|
||||
RoleManager<IdentityRole> _RoleManager;
|
||||
SettingsRepository _SettingsRepository;
|
||||
ILogger _logger;
|
||||
|
||||
public AccountController(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
@ -36,16 +37,15 @@ namespace BTCPayServer.Controllers
|
||||
StoreRepository storeRepository,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IEmailSender emailSender,
|
||||
SettingsRepository settingsRepository,
|
||||
ILogger<AccountController> logger)
|
||||
SettingsRepository settingsRepository)
|
||||
{
|
||||
this.storeRepository = storeRepository;
|
||||
_userManager = userManager;
|
||||
_signInManager = signInManager;
|
||||
_emailSender = emailSender;
|
||||
_logger = logger;
|
||||
_RoleManager = roleManager;
|
||||
_SettingsRepository = settingsRepository;
|
||||
_logger = Logs.PayServer;
|
||||
}
|
||||
|
||||
[TempData]
|
||||
@ -259,12 +259,10 @@ namespace BTCPayServer.Controllers
|
||||
var result = await _userManager.CreateAsync(user, model.Password);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
_logger.LogInformation("User created a new account with password.");
|
||||
|
||||
var admin = await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin);
|
||||
Logs.PayServer.LogInformation($"A new user just registered {user.Email} {(admin.Count == 0 ? "(admin)" : "")}");
|
||||
if (admin.Count == 0)
|
||||
{
|
||||
_logger.LogInformation("Admin created.");
|
||||
await _RoleManager.CreateAsync(new IdentityRole(Roles.ServerAdmin));
|
||||
await _userManager.AddToRoleAsync(user, Roles.ServerAdmin);
|
||||
}
|
||||
@ -291,7 +289,7 @@ namespace BTCPayServer.Controllers
|
||||
return View(model);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// Test property
|
||||
/// </summary>
|
||||
public string RegisteredUserId
|
||||
|
@ -77,7 +77,7 @@ namespace BTCPayServer.Controllers
|
||||
return NotFound();
|
||||
var payment = PaymentMessage.Load(Request.Body);
|
||||
var unused = wallet.BroadcastTransactionsAsync(payment.Transactions);
|
||||
await _InvoiceRepository.AddRefundsAsync(invoiceId, payment.RefundTo.Select(p => new TxOut(p.Amount, p.Script)).ToArray());
|
||||
await _InvoiceRepository.AddRefundsAsync(invoiceId, payment.RefundTo.Select(p => new TxOut(p.Amount, p.Script)).ToArray(), network.NBitcoinNetwork);
|
||||
return new PaymentAckActionResult(payment.CreateACK(invoiceId + " is currently processing, thanks for your purchase..."));
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,8 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
UserId = GetUserId(),
|
||||
InvoiceId = invoiceId,
|
||||
IncludeAddresses = true
|
||||
IncludeAddresses = true,
|
||||
IncludeEvents = true
|
||||
})).FirstOrDefault();
|
||||
if (invoice == null)
|
||||
return NotFound();
|
||||
@ -47,6 +48,7 @@ namespace BTCPayServer.Controllers
|
||||
StoreLink = Url.Action(nameof(StoresController.UpdateStore), "Stores", new { storeId = store.Id }),
|
||||
Id = invoice.Id,
|
||||
Status = invoice.Status,
|
||||
TransactionSpeed = invoice.SpeedPolicy == SpeedPolicy.HighSpeed ? "high" : invoice.SpeedPolicy == SpeedPolicy.MediumSpeed ? "medium" : "low",
|
||||
RefundEmail = invoice.RefundMail,
|
||||
CreatedDate = invoice.InvoiceTime,
|
||||
ExpirationDate = invoice.ExpirationTime,
|
||||
@ -55,8 +57,10 @@ namespace BTCPayServer.Controllers
|
||||
BuyerInformation = invoice.BuyerInformation,
|
||||
Fiat = FormatCurrency((decimal)dto.Price, dto.Currency),
|
||||
NotificationUrl = invoice.NotificationURL,
|
||||
RedirectUrl = invoice.RedirectURL,
|
||||
ProductInformation = invoice.ProductInformation,
|
||||
StatusException = invoice.ExceptionStatus
|
||||
StatusException = invoice.ExceptionStatus,
|
||||
Events = invoice.Events
|
||||
};
|
||||
|
||||
foreach (var data in invoice.GetCryptoData(null))
|
||||
@ -123,11 +127,22 @@ namespace BTCPayServer.Controllers
|
||||
if (invoice == null)
|
||||
return null;
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
bool isDefaultCrypto = false;
|
||||
if (cryptoCode == null)
|
||||
{
|
||||
cryptoCode = store.GetDefaultCrypto();
|
||||
isDefaultCrypto = true;
|
||||
}
|
||||
var network = _NetworkProvider.GetNetwork(cryptoCode);
|
||||
if (invoice == null || network == null || !invoice.Support(network))
|
||||
if (invoice == null || network == null)
|
||||
return null;
|
||||
|
||||
if(!invoice.Support(network))
|
||||
{
|
||||
if(!isDefaultCrypto)
|
||||
return null;
|
||||
network = invoice.GetCryptoData(_NetworkProvider).First().Value.Network;
|
||||
}
|
||||
var cryptoData = invoice.GetCryptoData(network, _NetworkProvider);
|
||||
|
||||
var dto = invoice.EntityToDTO(_NetworkProvider);
|
||||
@ -147,6 +162,7 @@ namespace BTCPayServer.Controllers
|
||||
CustomerEmail = invoice.RefundMail,
|
||||
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
|
||||
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
|
||||
MaxTimeMinutes = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalMinutes,
|
||||
ItemDesc = invoice.ProductInformation.ItemDesc,
|
||||
Rate = FormatCurrency(cryptoData),
|
||||
MerchantRefLink = invoice.RedirectURL ?? "/",
|
||||
@ -223,8 +239,7 @@ namespace BTCPayServer.Controllers
|
||||
try
|
||||
{
|
||||
leases.Add(_EventAggregator.Subscribe<Events.InvoiceDataChangedEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId)));
|
||||
leases.Add(_EventAggregator.Subscribe<Events.InvoicePaymentEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId)));
|
||||
leases.Add(_EventAggregator.Subscribe<Events.InvoiceStatusChangedEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId)));
|
||||
leases.Add(_EventAggregator.Subscribe<Events.InvoiceEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId)));
|
||||
while (true)
|
||||
{
|
||||
var message = await webSocket.ReceiveAsync(DummyBuffer, default(CancellationToken));
|
||||
@ -251,7 +266,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
await webSocket.SendAsync(DummyBuffer, WebSocketMessageType.Binary, true, cts.Token);
|
||||
}
|
||||
catch { await CloseSocket(webSocket); }
|
||||
catch { try { webSocket.Dispose(); } catch { } }
|
||||
}
|
||||
|
||||
private static async Task CloseSocket(WebSocket webSocket)
|
||||
@ -266,7 +281,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
finally { webSocket.Dispose(); }
|
||||
finally { try { webSocket.Dispose(); } catch { } }
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
@ -400,6 +415,7 @@ namespace BTCPayServer.Controllers
|
||||
public async Task<IActionResult> InvalidatePaidInvoice(string invoiceId)
|
||||
{
|
||||
await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoiceId);
|
||||
_EventAggregator.Publish(new InvoiceEvent(invoiceId, 1008, "invoice_markedInvalid"));
|
||||
return RedirectToAction(nameof(ListInvoices));
|
||||
}
|
||||
|
||||
|
@ -78,11 +78,11 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
|
||||
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl, double expiryMinutes = 15)
|
||||
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl)
|
||||
{
|
||||
var derivationStrategies = store.GetDerivationStrategies(_NetworkProvider).ToList();
|
||||
var derivationStrategies = store.GetDerivationStrategies(_NetworkProvider).Where(c => _ExplorerClients.IsAvailable(c.Network.CryptoCode)).ToList();
|
||||
if (derivationStrategies.Count == 0)
|
||||
throw new BitpayHttpException(400, "This store has not configured the derivation strategy");
|
||||
throw new BitpayHttpException(400, "No derivation strategy are available now for this store");
|
||||
var entity = new InvoiceEntity
|
||||
{
|
||||
InvoiceTime = DateTimeOffset.UtcNow
|
||||
@ -94,7 +94,7 @@ namespace BTCPayServer.Controllers
|
||||
if (notificationUri == null || (notificationUri.Scheme != "http" && notificationUri.Scheme != "https")) //TODO: Filer non routable addresses ?
|
||||
notificationUri = null;
|
||||
EmailAddressAttribute emailValidator = new EmailAddressAttribute();
|
||||
entity.ExpirationTime = entity.InvoiceTime.AddMinutes(expiryMinutes);
|
||||
entity.ExpirationTime = entity.InvoiceTime.AddMinutes(storeBlob.InvoiceExpiration);
|
||||
entity.MonitoringExpiration = entity.ExpirationTime + TimeSpan.FromMinutes(storeBlob.MonitoringExpiration);
|
||||
entity.OrderId = invoice.OrderId;
|
||||
entity.ServerUrl = serverUrl;
|
||||
@ -116,13 +116,13 @@ namespace BTCPayServer.Controllers
|
||||
entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);
|
||||
|
||||
var queries = derivationStrategies
|
||||
.Select(derivationStrategy => ( Wallet: _WalletProvider.GetWallet(derivationStrategy.Network),
|
||||
.Select(derivationStrategy => (Wallet: _WalletProvider.GetWallet(derivationStrategy.Network),
|
||||
DerivationStrategy: derivationStrategy.DerivationStrategyBase,
|
||||
Network: derivationStrategy.Network,
|
||||
RateProvider: _RateProviders.GetRateProvider(derivationStrategy.Network),
|
||||
RateProvider: _RateProviders.GetRateProvider(derivationStrategy.Network, false),
|
||||
FeeRateProvider: _FeeProviderFactory.CreateFeeProvider(derivationStrategy.Network)))
|
||||
.Where(_ => _.Wallet != null &&
|
||||
_.FeeRateProvider != null &&
|
||||
.Where(_ => _.Wallet != null &&
|
||||
_.FeeRateProvider != null &&
|
||||
_.RateProvider != null)
|
||||
.Select(_ =>
|
||||
{
|
||||
@ -130,24 +130,26 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
network = _.Network,
|
||||
getFeeRate = _.FeeRateProvider.GetFeeRateAsync(),
|
||||
getRate = _.RateProvider.GetRateAsync(invoice.Currency),
|
||||
getRate = storeBlob.ApplyRateRules(_.Network, _.RateProvider).GetRateAsync(invoice.Currency),
|
||||
getAddress = _.Wallet.ReserveAddressAsync(_.DerivationStrategy)
|
||||
};
|
||||
});
|
||||
|
||||
bool legacyBTCisSet = false;
|
||||
var cryptoDatas = new Dictionary<string, CryptoData>();
|
||||
foreach (var q in queries)
|
||||
{
|
||||
CryptoData cryptoData = new CryptoData();
|
||||
cryptoData.CryptoCode = q.network.CryptoCode;
|
||||
cryptoData.FeeRate = (await q.getFeeRate);
|
||||
cryptoData.TxFee = storeBlob.NetworkFeeDisabled ? Money.Zero : cryptoData.FeeRate.GetFee(100); // assume price for 100 bytes
|
||||
cryptoData.TxFee = GetTxFee(storeBlob, cryptoData.FeeRate); // assume price for 100 bytes
|
||||
cryptoData.Rate = await q.getRate;
|
||||
cryptoData.DepositAddress = (await q.getAddress).ToString();
|
||||
|
||||
#pragma warning disable CS0618
|
||||
if (q.network.IsBTC)
|
||||
{
|
||||
legacyBTCisSet = true;
|
||||
entity.TxFee = cryptoData.TxFee;
|
||||
entity.Rate = cryptoData.Rate;
|
||||
entity.DepositAddress = cryptoData.DepositAddress;
|
||||
@ -155,14 +157,37 @@ namespace BTCPayServer.Controllers
|
||||
#pragma warning restore CS0618
|
||||
cryptoDatas.Add(cryptoData.CryptoCode, cryptoData);
|
||||
}
|
||||
|
||||
if (!legacyBTCisSet)
|
||||
{
|
||||
// Legacy Bitpay clients expect information for BTC information, even if the store do not support it
|
||||
#pragma warning disable CS0618
|
||||
var btc = _NetworkProvider.BTC;
|
||||
var feeProvider = _FeeProviderFactory.CreateFeeProvider(btc);
|
||||
var rateProvider = storeBlob.ApplyRateRules(btc, _RateProviders.GetRateProvider(btc, false));
|
||||
if (feeProvider != null && rateProvider != null)
|
||||
{
|
||||
var gettingFee = feeProvider.GetFeeRateAsync();
|
||||
var gettingRate = rateProvider.GetRateAsync(invoice.Currency);
|
||||
entity.TxFee = GetTxFee(storeBlob, await gettingFee);
|
||||
entity.Rate = await gettingRate;
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
entity.SetCryptoData(cryptoDatas);
|
||||
entity.PosData = invoice.PosData;
|
||||
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, _NetworkProvider);
|
||||
_EventAggregator.Publish(new Events.InvoiceCreatedEvent(entity.Id));
|
||||
_EventAggregator.Publish(new Events.InvoiceEvent(entity, 1001, "invoice_created"));
|
||||
var resp = entity.EntityToDTO(_NetworkProvider);
|
||||
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
|
||||
}
|
||||
|
||||
private static Money GetTxFee(StoreBlob storeBlob, FeeRate feeRate)
|
||||
{
|
||||
return storeBlob.NetworkFeeDisabled ? Money.Zero : feeRate.GetFee(100);
|
||||
}
|
||||
|
||||
private SpeedPolicy ParseSpeedPolicy(string transactionSpeed, SpeedPolicy defaultPolicy)
|
||||
{
|
||||
if (transactionSpeed == null)
|
||||
|
@ -33,7 +33,6 @@ namespace BTCPayServer.Controllers
|
||||
private readonly ILogger _logger;
|
||||
private readonly UrlEncoder _urlEncoder;
|
||||
TokenRepository _TokenRepository;
|
||||
private readonly BTCPayWallet _Wallet;
|
||||
IHostingEnvironment _Env;
|
||||
StoreRepository _StoreRepository;
|
||||
|
||||
@ -47,7 +46,7 @@ namespace BTCPayServer.Controllers
|
||||
ILogger<ManageController> logger,
|
||||
UrlEncoder urlEncoder,
|
||||
TokenRepository tokenRepository,
|
||||
BTCPayWallet wallet,
|
||||
BTCPayWalletProvider walletProvider,
|
||||
StoreRepository storeRepository,
|
||||
IHostingEnvironment env)
|
||||
{
|
||||
@ -57,7 +56,6 @@ namespace BTCPayServer.Controllers
|
||||
_logger = logger;
|
||||
_urlEncoder = urlEncoder;
|
||||
_TokenRepository = tokenRepository;
|
||||
_Wallet = wallet;
|
||||
_Env = env;
|
||||
_StoreRepository = storeRepository;
|
||||
}
|
||||
|
@ -7,34 +7,68 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public class RateController : Controller
|
||||
{
|
||||
IRateProvider _RateProvider;
|
||||
IRateProviderFactory _RateProviderFactory;
|
||||
BTCPayNetworkProvider _NetworkProvider;
|
||||
CurrencyNameTable _CurrencyNameTable;
|
||||
public RateController(IRateProvider rateProvider, CurrencyNameTable currencyNameTable)
|
||||
StoreRepository _StoreRepo;
|
||||
public RateController(
|
||||
IRateProviderFactory rateProviderFactory,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
StoreRepository storeRepo,
|
||||
CurrencyNameTable currencyNameTable)
|
||||
{
|
||||
_RateProvider = rateProvider ?? throw new ArgumentNullException(nameof(rateProvider));
|
||||
_RateProviderFactory = rateProviderFactory ?? throw new ArgumentNullException(nameof(rateProviderFactory));
|
||||
_NetworkProvider = networkProvider;
|
||||
_StoreRepo = storeRepo;
|
||||
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
||||
}
|
||||
|
||||
[Route("rates")]
|
||||
[HttpGet]
|
||||
[BitpayAPIConstraint]
|
||||
public async Task<DataWrapper<NBitpayClient.Rate[]>> GetRates()
|
||||
public async Task<IActionResult> GetRates(string cryptoCode = null, string storeId = null)
|
||||
{
|
||||
var allRates = (await _RateProvider.GetRatesAsync());
|
||||
return new DataWrapper<NBitpayClient.Rate[]>
|
||||
(allRates.Select(r =>
|
||||
var result = await GetRates2(cryptoCode, storeId);
|
||||
var rates = (result as JsonResult)?.Value as NBitpayClient.Rate[];
|
||||
if(rates == null)
|
||||
return result;
|
||||
return Json(new DataWrapper<NBitpayClient.Rate[]>(rates));
|
||||
}
|
||||
|
||||
[Route("api/rates")]
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetRates2(string cryptoCode = null, string storeId = null)
|
||||
{
|
||||
cryptoCode = cryptoCode ?? "BTC";
|
||||
var network = _NetworkProvider.GetNetwork(cryptoCode);
|
||||
if (network == null)
|
||||
return NotFound();
|
||||
var rateProvider = _RateProviderFactory.GetRateProvider(network, true);
|
||||
if (rateProvider == null)
|
||||
return NotFound();
|
||||
|
||||
if (storeId != null)
|
||||
{
|
||||
var store = await _StoreRepo.FindStore(storeId);
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
rateProvider = store.GetStoreBlob().ApplyRateRules(network, rateProvider);
|
||||
}
|
||||
|
||||
var allRates = (await rateProvider.GetRatesAsync());
|
||||
return Json(allRates.Select(r =>
|
||||
new NBitpayClient.Rate()
|
||||
{
|
||||
Code = r.Currency,
|
||||
Name = _CurrencyNameTable.GetCurrencyData(r.Currency)?.Name,
|
||||
Value = r.Value
|
||||
}).Where(n => n.Name != null).ToArray());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ using NBXplorer.DerivationStrategy;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
@ -95,7 +96,7 @@ namespace BTCPayServer.Controllers
|
||||
var stores = await _Repo.GetStoresByUserId(GetUserId());
|
||||
var balances = stores
|
||||
.Select(s => s.GetDerivationStrategies(_NetworkProvider)
|
||||
.Select(d => (Wallet: _WalletProvider.GetWallet(d.Network),
|
||||
.Select(d => (Wallet: _WalletProvider.GetWallet(d.Network),
|
||||
DerivationStrategy: d.DerivationStrategyBase))
|
||||
.Where(_ => _.Wallet != null)
|
||||
.Select(async _ => (await _.Wallet.GetBalance(_.DerivationStrategy)).ToString() + " " + _.Wallet.Network.CryptoCode))
|
||||
@ -163,6 +164,9 @@ namespace BTCPayServer.Controllers
|
||||
AddDerivationSchemes(store, vm);
|
||||
vm.StatusMessage = StatusMessage;
|
||||
vm.MonitoringExpiration = storeBlob.MonitoringExpiration;
|
||||
vm.InvoiceExpiration = storeBlob.InvoiceExpiration;
|
||||
vm.RateMultiplier = (double)storeBlob.GetRateMultiplier();
|
||||
vm.PreferredExchange = storeBlob.PreferredExchange.IsCoinAverage() ? "coinaverage" : storeBlob.PreferredExchange;
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
@ -199,7 +203,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/derivations")]
|
||||
public async Task<IActionResult> AddDerivationScheme(string storeId, DerivationSchemeViewModel vm, string command, string selectedScheme = null)
|
||||
public async Task<IActionResult> AddDerivationScheme(string storeId, DerivationSchemeViewModel vm, string selectedScheme = null)
|
||||
{
|
||||
selectedScheme = selectedScheme ?? "BTC";
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
@ -220,16 +224,31 @@ namespace BTCPayServer.Controllers
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
if (command == "Save")
|
||||
|
||||
DerivationStrategyBase strategy = null;
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(vm.DerivationScheme))
|
||||
{
|
||||
strategy = ParseDerivationStrategy(vm.DerivationScheme, vm.DerivationSchemeFormat, network);
|
||||
vm.DerivationScheme = strategy.ToString();
|
||||
}
|
||||
store.SetDerivationStrategy(network, vm.DerivationScheme);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid Derivation Scheme");
|
||||
vm.Confirmation = false;
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
|
||||
if (strategy == null || vm.Confirmation)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(vm.DerivationScheme))
|
||||
{
|
||||
var strategy = ParseDerivationStrategy(vm.DerivationScheme, vm.DerivationSchemeFormat, network);
|
||||
if (strategy != null)
|
||||
await wallet.TrackAsync(strategy);
|
||||
vm.DerivationScheme = strategy.ToString();
|
||||
}
|
||||
store.SetDerivationStrategy(network, vm.DerivationScheme);
|
||||
}
|
||||
catch
|
||||
@ -239,29 +258,22 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
await _Repo.UpdateStore(store);
|
||||
StatusMessage = $"Derivation scheme for {vm.CryptoCurrency} has been modified.";
|
||||
StatusMessage = $"Derivation scheme for {network.CryptoCode} has been modified.";
|
||||
return RedirectToAction(nameof(UpdateStore), new { storeId = storeId });
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(vm.DerivationScheme))
|
||||
{
|
||||
try
|
||||
{
|
||||
var scheme = ParseDerivationStrategy(vm.DerivationScheme, vm.DerivationSchemeFormat, network);
|
||||
var line = scheme.GetLineFor(DerivationFeature.Deposit);
|
||||
var line = strategy.GetLineFor(DerivationFeature.Deposit);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var address = line.Derive((uint)i);
|
||||
vm.AddressSamples.Add((line.Path.Derive((uint)i).ToString(), address.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork).ToString()));
|
||||
}
|
||||
}
|
||||
catch
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid Derivation Scheme");
|
||||
var address = line.Derive((uint)i);
|
||||
vm.AddressSamples.Add((DerivationStrategyBase.GetKeyPath(DerivationFeature.Deposit).Derive((uint)i).ToString(), address.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork).ToString()));
|
||||
}
|
||||
}
|
||||
vm.Confirmation = true;
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
@ -274,6 +286,8 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
if (model.PreferredExchange != null)
|
||||
model.PreferredExchange = model.PreferredExchange.Trim().ToLowerInvariant();
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
@ -306,12 +320,31 @@ namespace BTCPayServer.Controllers
|
||||
var blob = store.GetStoreBlob();
|
||||
blob.NetworkFeeDisabled = !model.NetworkFee;
|
||||
blob.MonitoringExpiration = model.MonitoringExpiration;
|
||||
blob.InvoiceExpiration = model.InvoiceExpiration;
|
||||
|
||||
bool newExchange = blob.PreferredExchange != model.PreferredExchange;
|
||||
blob.PreferredExchange = model.PreferredExchange;
|
||||
|
||||
blob.SetRateMultiplier(model.RateMultiplier);
|
||||
|
||||
if (store.SetStoreBlob(blob))
|
||||
{
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
if (!blob.PreferredExchange.IsCoinAverage() && newExchange)
|
||||
{
|
||||
using (HttpClient client = new HttpClient())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needUpdate)
|
||||
{
|
||||
await _Repo.UpdateStore(store);
|
||||
@ -346,7 +379,7 @@ namespace BTCPayServer.Controllers
|
||||
var prefix = Utils.ToUInt32(data, false);
|
||||
if (!electrumMapping.TryGetValue(prefix, out string[] labels))
|
||||
throw new FormatException("!electrumMapping.TryGetValue(prefix, out string[] labels)");
|
||||
var standardPrefix = Utils.ToBytes(network.NBitcoinNetwork == Network.Main ? 0x0488b21eU : 0x043587cf, false);
|
||||
var standardPrefix = Utils.ToBytes(network.NBXplorerNetwork.DefaultSettings.ChainType == NBXplorer.ChainType.Main ? 0x0488b21eU : 0x043587cf, false);
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
data[i] = standardPrefix[i];
|
||||
|
@ -26,6 +26,11 @@ namespace BTCPayServer.Data
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DbSet<InvoiceEventData> InvoiceEvents
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DbSet<HistoricalAddressInvoiceData> HistoricalAddressInvoices
|
||||
{
|
||||
get; set;
|
||||
@ -132,6 +137,15 @@ namespace BTCPayServer.Data
|
||||
o.InvoiceDataId,
|
||||
#pragma warning disable CS0618
|
||||
o.Address
|
||||
#pragma warning restore CS0618
|
||||
});
|
||||
|
||||
builder.Entity<InvoiceEventData>()
|
||||
.HasKey(o => new
|
||||
{
|
||||
o.InvoiceDataId,
|
||||
#pragma warning disable CS0618
|
||||
o.UniqueId
|
||||
#pragma warning restore CS0618
|
||||
});
|
||||
}
|
||||
|
@ -32,6 +32,11 @@ namespace BTCPayServer.Data
|
||||
get; set;
|
||||
}
|
||||
|
||||
public List<InvoiceEventData> Events
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public List<RefundAddressesData> RefundAddresses
|
||||
{
|
||||
get; set;
|
||||
|
22
BTCPayServer/Data/InvoiceEventData.cs
Normal file
22
BTCPayServer/Data/InvoiceEventData.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class InvoiceEventData
|
||||
{
|
||||
public string InvoiceDataId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string UniqueId { get; internal set; }
|
||||
public DateTimeOffset Timestamp
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ using System.Threading.Tasks;
|
||||
using System.ComponentModel;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using BTCPayServer.Services.Rates;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
@ -99,10 +100,10 @@ namespace BTCPayServer.Data
|
||||
|
||||
if (!existing && string.IsNullOrEmpty(derivationScheme))
|
||||
{
|
||||
if(network.IsBTC)
|
||||
if (network.IsBTC)
|
||||
DerivationStrategy = null;
|
||||
}
|
||||
else if(!existing)
|
||||
else if (!existing)
|
||||
strategies.Add(new JProperty(network.CryptoCode, new JValue(derivationScheme)));
|
||||
// This is deprecated so we don't have to set anymore
|
||||
//if (network.IsBTC)
|
||||
@ -173,10 +174,27 @@ namespace BTCPayServer.Data
|
||||
}
|
||||
}
|
||||
|
||||
public class RateRule
|
||||
{
|
||||
public RateRule()
|
||||
{
|
||||
RuleName = "Multiplier";
|
||||
}
|
||||
public string RuleName { get; set; }
|
||||
|
||||
public double Multiplier { get; set; }
|
||||
|
||||
public decimal Apply(BTCPayNetwork network, decimal rate)
|
||||
{
|
||||
return rate * (decimal)Multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
public class StoreBlob
|
||||
{
|
||||
public StoreBlob()
|
||||
{
|
||||
InvoiceExpiration = 15;
|
||||
MonitoringExpiration = 60;
|
||||
}
|
||||
public bool NetworkFeeDisabled
|
||||
@ -190,5 +208,59 @@ namespace BTCPayServer.Data
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[DefaultValue(15)]
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
public int InvoiceExpiration
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public void SetRateMultiplier(double rate)
|
||||
{
|
||||
RateRules = new List<RateRule>();
|
||||
RateRules.Add(new RateRule() { Multiplier = rate });
|
||||
}
|
||||
public decimal GetRateMultiplier()
|
||||
{
|
||||
decimal rate = 1.0m;
|
||||
if (RateRules == null || RateRules.Count == 0)
|
||||
return rate;
|
||||
foreach (var rule in RateRules)
|
||||
{
|
||||
rate = rule.Apply(null, rate);
|
||||
}
|
||||
return rate;
|
||||
}
|
||||
|
||||
public List<RateRule> RateRules { get; set; } = new List<RateRule>();
|
||||
public string PreferredExchange { get; set; }
|
||||
|
||||
public IRateProvider ApplyRateRules(BTCPayNetwork network, IRateProvider rateProvider)
|
||||
{
|
||||
if (!PreferredExchange.IsCoinAverage())
|
||||
{
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ namespace BTCPayServer
|
||||
}
|
||||
}
|
||||
|
||||
Logs.Events.LogInformation($"New event: {evt.ToString()}");
|
||||
Logs.Events.LogInformation(evt.ToString());
|
||||
foreach (var sub in actionList)
|
||||
{
|
||||
try
|
||||
|
@ -8,10 +8,19 @@ namespace BTCPayServer.Events
|
||||
public class InvoiceDataChangedEvent
|
||||
{
|
||||
public string InvoiceId { get; set; }
|
||||
public string Status { get; internal set; }
|
||||
public string ExceptionStatus { get; internal set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Invoice {InvoiceId} data changed";
|
||||
if (string.IsNullOrEmpty(ExceptionStatus) || ExceptionStatus == "false")
|
||||
{
|
||||
return $"Invoice status is {Status}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"Invoice status is {Status} (Exception status: {ExceptionStatus})";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
31
BTCPayServer/Events/InvoiceEvent.cs
Normal file
31
BTCPayServer/Events/InvoiceEvent.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
|
||||
namespace BTCPayServer.Events
|
||||
{
|
||||
public class InvoiceEvent
|
||||
{
|
||||
public InvoiceEvent(InvoiceEntity invoice, int code, string name) : this(invoice.Id, code, name)
|
||||
{
|
||||
|
||||
}
|
||||
public InvoiceEvent(string invoiceId, int code, string name)
|
||||
{
|
||||
InvoiceId = invoiceId;
|
||||
EventCode = code;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string InvoiceId { get; set; }
|
||||
public int EventCode { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Invoice {InvoiceId} new event: {Name} ({EventCode})";
|
||||
}
|
||||
}
|
||||
}
|
35
BTCPayServer/Events/InvoiceIPNEvent.cs
Normal file
35
BTCPayServer/Events/InvoiceIPNEvent.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Events
|
||||
{
|
||||
public class InvoiceIPNEvent
|
||||
{
|
||||
public InvoiceIPNEvent(string invoiceId, int? eventCode, string name)
|
||||
{
|
||||
InvoiceId = invoiceId;
|
||||
EventCode = eventCode;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public int? EventCode { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public string InvoiceId { get; set; }
|
||||
public string Error { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string ipnType = "IPN";
|
||||
if(EventCode.HasValue)
|
||||
{
|
||||
ipnType = $"IPN ({EventCode.Value} {Name})";
|
||||
}
|
||||
if (Error == null)
|
||||
return $"{ipnType} sent for invoice {InvoiceId}";
|
||||
return $"Error while sending {ipnType}: {Error}";
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
|
||||
namespace BTCPayServer.Events
|
||||
{
|
||||
public class InvoicePaymentEvent
|
||||
{
|
||||
|
||||
public InvoicePaymentEvent(string invoiceId)
|
||||
{
|
||||
InvoiceId = invoiceId;
|
||||
}
|
||||
|
||||
public string InvoiceId { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Invoice {InvoiceId} received a payment";
|
||||
}
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
|
||||
namespace BTCPayServer.Events
|
||||
{
|
||||
public class InvoiceStatusChangedEvent
|
||||
{
|
||||
public InvoiceStatusChangedEvent()
|
||||
{
|
||||
|
||||
}
|
||||
public InvoiceStatusChangedEvent(InvoiceEntity invoice, string newState)
|
||||
{
|
||||
OldState = invoice.Status;
|
||||
InvoiceId = invoice.Id;
|
||||
NewState = newState;
|
||||
}
|
||||
public string InvoiceId { get; set; }
|
||||
public string OldState { get; set; }
|
||||
public string NewState { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Invoice {InvoiceId} changed status: {OldState} => {NewState}";
|
||||
}
|
||||
}
|
||||
}
|
@ -5,18 +5,12 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Events
|
||||
{
|
||||
public class InvoiceCreatedEvent
|
||||
public class InvoiceStopWatchedEvent
|
||||
{
|
||||
public InvoiceCreatedEvent(string id)
|
||||
{
|
||||
InvoiceId = id;
|
||||
}
|
||||
|
||||
public string InvoiceId { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Invoice {InvoiceId} created";
|
||||
return $"Invoice {InvoiceId} is not monitored anymore.";
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,21 @@ using BTCPayServer.HostedServices;
|
||||
|
||||
namespace BTCPayServer.Events
|
||||
{
|
||||
public class NBXplorerErrorEvent
|
||||
{
|
||||
public NBXplorerErrorEvent(BTCPayNetwork network, string errorMessage)
|
||||
{
|
||||
Message = errorMessage;
|
||||
Network = network;
|
||||
}
|
||||
public string Message { get; set; }
|
||||
public BTCPayNetwork Network { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Network.CryptoCode}: NBXplorer error `{Message}`";
|
||||
}
|
||||
}
|
||||
public class NBXplorerStateChangedEvent
|
||||
{
|
||||
public NBXplorerStateChangedEvent(BTCPayNetwork network, NBXplorerState old, NBXplorerState newState)
|
||||
|
@ -7,9 +7,10 @@ namespace BTCPayServer.Events
|
||||
{
|
||||
public class NewBlockEvent
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
public override string ToString()
|
||||
{
|
||||
return "New block";
|
||||
return $"{CryptoCode}: New block";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Logging;
|
||||
using NBXplorer;
|
||||
using BTCPayServer.HostedServices;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
@ -13,23 +16,51 @@ namespace BTCPayServer
|
||||
BTCPayServerOptions _Options;
|
||||
|
||||
public BTCPayNetworkProvider NetworkProviders => _NetworkProviders;
|
||||
|
||||
public ExplorerClientProvider(BTCPayNetworkProvider networkProviders, BTCPayServerOptions options)
|
||||
NBXplorerDashboard _Dashboard;
|
||||
public ExplorerClientProvider(BTCPayNetworkProvider networkProviders, BTCPayServerOptions options, NBXplorerDashboard dashboard)
|
||||
{
|
||||
_Dashboard = dashboard;
|
||||
_NetworkProviders = networkProviders;
|
||||
_Options = options;
|
||||
|
||||
foreach (var setting in options.NBXplorerConnectionSettings)
|
||||
{
|
||||
var cookieFile = setting.CookieFile;
|
||||
if (cookieFile.Trim() == "0" || string.IsNullOrEmpty(cookieFile.Trim()))
|
||||
cookieFile = null;
|
||||
Logs.Configuration.LogInformation($"{setting.CryptoCode}: Explorer url is {(setting.ExplorerUri.AbsoluteUri ?? "not set")}");
|
||||
Logs.Configuration.LogInformation($"{setting.CryptoCode}: Cookie file is {(setting.CookieFile ?? "not set")}");
|
||||
if (setting.ExplorerUri != null)
|
||||
{
|
||||
_Clients.TryAdd(setting.CryptoCode, CreateExplorerClient(_NetworkProviders.GetNetwork(setting.CryptoCode), setting.ExplorerUri, setting.CookieFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ExplorerClient CreateExplorerClient(BTCPayNetwork n, Uri uri, string cookieFile)
|
||||
{
|
||||
var explorer = new ExplorerClient(n.NBXplorerNetwork, uri);
|
||||
if (cookieFile == null)
|
||||
{
|
||||
Logs.Configuration.LogWarning($"{n.CryptoCode}: Not using cookie authentication");
|
||||
explorer.SetNoAuth();
|
||||
}
|
||||
if(!explorer.SetCookieAuth(cookieFile))
|
||||
{
|
||||
Logs.Configuration.LogWarning($"{n.CryptoCode}: Using cookie auth against NBXplorer, but {cookieFile} is not found");
|
||||
}
|
||||
return explorer;
|
||||
}
|
||||
|
||||
Dictionary<string, ExplorerClient> _Clients = new Dictionary<string, ExplorerClient>();
|
||||
|
||||
public ExplorerClient GetExplorerClient(string cryptoCode)
|
||||
{
|
||||
var network = _NetworkProviders.GetNetwork(cryptoCode);
|
||||
if (network == null)
|
||||
return null;
|
||||
if (_Options.ExplorerFactories.TryGetValue(network.CryptoCode, out Func<BTCPayNetwork, ExplorerClient> factory))
|
||||
{
|
||||
return factory(network);
|
||||
}
|
||||
return null;
|
||||
_Clients.TryGetValue(network.CryptoCode, out ExplorerClient client);
|
||||
return client;
|
||||
}
|
||||
|
||||
public ExplorerClient GetExplorerClient(BTCPayNetwork network)
|
||||
@ -39,23 +70,33 @@ namespace BTCPayServer
|
||||
return GetExplorerClient(network.CryptoCode);
|
||||
}
|
||||
|
||||
public bool IsAvailable(BTCPayNetwork network)
|
||||
{
|
||||
return IsAvailable(network.CryptoCode);
|
||||
}
|
||||
|
||||
public bool IsAvailable(string cryptoCode)
|
||||
{
|
||||
return _Clients.ContainsKey(cryptoCode) && _Dashboard.IsFullySynched(cryptoCode);
|
||||
}
|
||||
|
||||
public BTCPayNetwork GetNetwork(string cryptoCode)
|
||||
{
|
||||
var network = _NetworkProviders.GetNetwork(cryptoCode);
|
||||
if (network == null)
|
||||
return null;
|
||||
if (_Options.ExplorerFactories.ContainsKey(network.CryptoCode))
|
||||
if (_Clients.ContainsKey(network.CryptoCode))
|
||||
return network;
|
||||
return null;
|
||||
}
|
||||
|
||||
public IEnumerable<(BTCPayNetwork, ExplorerClient)> GetAll()
|
||||
{
|
||||
foreach(var net in _NetworkProviders.GetAll())
|
||||
foreach (var net in _NetworkProviders.GetAll())
|
||||
{
|
||||
if(_Options.ExplorerFactories.TryGetValue(net.CryptoCode, out Func<BTCPayNetwork, ExplorerClient> factory))
|
||||
if (_Clients.TryGetValue(net.CryptoCode, out ExplorerClient explorer))
|
||||
{
|
||||
yield return (net, factory(net));
|
||||
yield return (net, explorer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,24 +19,24 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using System.IO;
|
||||
using BTCPayServer.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static string GetDefaultExplorerUrl(this NBXplorer.Configuration.NetworkInformation networkInfo)
|
||||
{
|
||||
return $"http://127.0.0.1:{networkInfo.DefaultExplorerPort}/";
|
||||
}
|
||||
public static string GetDefaultCookieFile(this NBXplorer.Configuration.NetworkInformation networkInfo)
|
||||
{
|
||||
return Path.Combine(networkInfo.DefaultDataDirectory, ".cookie");
|
||||
}
|
||||
public static bool SupportDropColumn(this Microsoft.EntityFrameworkCore.Migrations.Migration migration, string activeProvider)
|
||||
{
|
||||
return activeProvider != "Microsoft.EntityFrameworkCore.Sqlite";
|
||||
}
|
||||
|
||||
public static bool IsCoinAverage(this string exchangeName)
|
||||
{
|
||||
string[] coinAverages = new[] { "coinaverage", "bitcoinaverage" };
|
||||
return String.IsNullOrWhiteSpace(exchangeName) ? true : coinAverages.Contains(exchangeName, StringComparer.OrdinalIgnoreCase) ? true : false;
|
||||
}
|
||||
|
||||
public static async Task<Dictionary<uint256, TransactionResult>> GetTransactions(this BTCPayWallet client, uint256[] hashes, CancellationToken cts = default(CancellationToken))
|
||||
{
|
||||
hashes = hashes.Distinct().ToArray();
|
||||
|
@ -37,6 +37,9 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public int? EventCode { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public ILogger Logger
|
||||
@ -63,18 +66,32 @@ namespace BTCPayServer.HostedServices
|
||||
_NetworkProvider = networkProvider;
|
||||
}
|
||||
|
||||
async Task Notify(InvoiceEntity invoice)
|
||||
async Task Notify(InvoiceEntity invoice, int? eventCode = null, string name = null)
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
||||
try
|
||||
{
|
||||
await SendNotification(invoice, cts.Token);
|
||||
if (string.IsNullOrEmpty(invoice.NotificationURL))
|
||||
return;
|
||||
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(invoice.Id, eventCode, name));
|
||||
await SendNotification(invoice, eventCode, name, cts.Token);
|
||||
return;
|
||||
}
|
||||
catch // It fails, it is OK because we try with hangfire after
|
||||
catch (OperationCanceledException) when (cts.IsCancellationRequested)
|
||||
{
|
||||
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(invoice.Id, eventCode, name)
|
||||
{
|
||||
Error = "Timeout"
|
||||
});
|
||||
}
|
||||
var invoiceStr = NBitcoin.JsonConverters.Serializer.ToString(new ScheduledJob() { TryCount = 0, Invoice = invoice });
|
||||
catch (Exception ex) // It fails, it is OK because we try with hangfire after
|
||||
{
|
||||
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(invoice.Id, eventCode, name)
|
||||
{
|
||||
Error = ex.Message
|
||||
});
|
||||
}
|
||||
var invoiceStr = NBitcoin.JsonConverters.Serializer.ToString(new ScheduledJob() { TryCount = 0, Invoice = invoice, EventCode = eventCode, Message = name });
|
||||
if (!string.IsNullOrEmpty(invoice.NotificationURL))
|
||||
_JobClient.Schedule(() => NotifyHttp(invoiceStr), TimeSpan.Zero);
|
||||
}
|
||||
@ -93,14 +110,45 @@ namespace BTCPayServer.HostedServices
|
||||
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = await SendNotification(job.Invoice, cts.Token);
|
||||
HttpResponseMessage response = await SendNotification(job.Invoice, job.EventCode, job.Message, cts.Token);
|
||||
reschedule = response.StatusCode != System.Net.HttpStatusCode.OK;
|
||||
Logger.LogInformation("Job " + jobId + " returned " + response.StatusCode);
|
||||
|
||||
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(job.Invoice.Id, job.EventCode, job.Message)
|
||||
{
|
||||
Error = reschedule ? $"Unexpected return code: {(int)response.StatusCode}" : null
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (OperationCanceledException) when (cts.IsCancellationRequested)
|
||||
{
|
||||
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(job.Invoice.Id, job.EventCode, job.Message)
|
||||
{
|
||||
Error = "Timeout"
|
||||
});
|
||||
reschedule = true;
|
||||
Logger.LogInformation("Job " + jobId + " threw exception " + ex.Message);
|
||||
Logger.LogInformation("Job " + jobId + " timed out");
|
||||
}
|
||||
catch (Exception ex) // It fails, it is OK because we try with hangfire after
|
||||
{
|
||||
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(job.Invoice.Id, job.EventCode, job.Message)
|
||||
{
|
||||
Error = ex.Message
|
||||
});
|
||||
reschedule = true;
|
||||
|
||||
List<string> messages = new List<string>();
|
||||
while(ex != null)
|
||||
{
|
||||
messages.Add(ex.Message);
|
||||
ex = ex.InnerException;
|
||||
}
|
||||
string message = String.Join(',', messages.ToArray());
|
||||
Logger.LogInformation("Job " + jobId + " threw exception " + message);
|
||||
|
||||
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(job.Invoice.Id, job.EventCode, job.Message)
|
||||
{
|
||||
Error = $"Unexpected error: {message}"
|
||||
});
|
||||
}
|
||||
finally { cts.Dispose(); _Executing.TryRemove(jobId, out jobId); }
|
||||
|
||||
@ -115,8 +163,23 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
}
|
||||
|
||||
public class InvoicePaymentNotificationEvent
|
||||
{
|
||||
[JsonProperty("code")]
|
||||
public int Code { get; set; }
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
public class InvoicePaymentNotificationEventWrapper
|
||||
{
|
||||
[JsonProperty("event")]
|
||||
public InvoicePaymentNotificationEvent Event { get; set; }
|
||||
[JsonProperty("data")]
|
||||
public InvoicePaymentNotification Data { get; set; }
|
||||
}
|
||||
|
||||
Encoding UTF8 = new UTF8Encoding(false);
|
||||
private async Task<HttpResponseMessage> SendNotification(InvoiceEntity invoice, CancellationToken cancellation)
|
||||
private async Task<HttpResponseMessage> SendNotification(InvoiceEntity invoice, int? eventCode, string name, CancellationToken cancellation)
|
||||
{
|
||||
var request = new HttpRequestMessage();
|
||||
request.Method = HttpMethod.Post;
|
||||
@ -139,7 +202,7 @@ namespace BTCPayServer.HostedServices
|
||||
// We keep backward compatibility with bitpay by passing BTC info to the notification
|
||||
// we don't pass other info, as it is a bad idea to use IPN data for logic processing (can be faked)
|
||||
var btcCryptoInfo = dto.CryptoInfo.FirstOrDefault(c => c.CryptoCode == "BTC");
|
||||
if(btcCryptoInfo != null)
|
||||
if (btcCryptoInfo != null)
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
notification.Rate = (double)dto.Rate;
|
||||
@ -149,8 +212,22 @@ namespace BTCPayServer.HostedServices
|
||||
notification.BTCPrice = dto.BTCPrice;
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
string notificationString = null;
|
||||
if (eventCode.HasValue)
|
||||
{
|
||||
var wrapper = new InvoicePaymentNotificationEventWrapper();
|
||||
wrapper.Data = notification;
|
||||
wrapper.Event = new InvoicePaymentNotificationEvent() { Code = eventCode.Value, Name = name };
|
||||
notificationString = JsonConvert.SerializeObject(wrapper);
|
||||
}
|
||||
else
|
||||
{
|
||||
notificationString = JsonConvert.SerializeObject(notification);
|
||||
}
|
||||
|
||||
request.RequestUri = new Uri(invoice.NotificationURL, UriKind.Absolute);
|
||||
request.Content = new StringContent(JsonConvert.SerializeObject(notification), UTF8, "application/json");
|
||||
request.Content = new StringContent(notificationString, UTF8, "application/json");
|
||||
var response = await _Client.SendAsync(request, cancellation);
|
||||
return response;
|
||||
}
|
||||
@ -165,29 +242,60 @@ namespace BTCPayServer.HostedServices
|
||||
CompositeDisposable leases = new CompositeDisposable();
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
leases.Add(_EventAggregator.Subscribe<InvoiceStatusChangedEvent>(async e =>
|
||||
leases.Add(_EventAggregator.Subscribe<InvoiceEvent>(async e =>
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, e.InvoiceId);
|
||||
await SaveEvent(invoice.Id, e);
|
||||
|
||||
// we need to use the status in the event and not in the invoice. The invoice might now be in another status.
|
||||
if (invoice.FullNotifications)
|
||||
{
|
||||
if (e.NewState == "expired" ||
|
||||
e.NewState == "paid" ||
|
||||
e.NewState == "invalid" ||
|
||||
e.NewState == "complete"
|
||||
if (e.Name == "invoice_expired" ||
|
||||
e.Name == "invoice_paidInFull" ||
|
||||
e.Name == "invoice_failedToConfirm" ||
|
||||
e.Name == "invoice_markedInvalid" ||
|
||||
e.Name == "invoice_failedToConfirm" ||
|
||||
e.Name == "invoice_completed"
|
||||
)
|
||||
await Notify(invoice);
|
||||
}
|
||||
|
||||
if(e.NewState == "confirmed")
|
||||
|
||||
if (e.Name == "invoice_confirmed")
|
||||
{
|
||||
await Notify(invoice);
|
||||
}
|
||||
|
||||
if (invoice.ExtendedNotifications)
|
||||
{
|
||||
await Notify(invoice, e.EventCode, e.Name);
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
leases.Add(_EventAggregator.Subscribe<InvoiceDataChangedEvent>(async e =>
|
||||
{
|
||||
await SaveEvent(e.InvoiceId, e);
|
||||
}));
|
||||
|
||||
|
||||
leases.Add(_EventAggregator.Subscribe<InvoiceStopWatchedEvent>(async e =>
|
||||
{
|
||||
await SaveEvent(e.InvoiceId, e);
|
||||
}));
|
||||
|
||||
leases.Add(_EventAggregator.Subscribe<InvoiceIPNEvent>(async e =>
|
||||
{
|
||||
await SaveEvent(e.InvoiceId, e);
|
||||
}));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task SaveEvent(string invoiceId, object evt)
|
||||
{
|
||||
return _InvoiceRepository.AddInvoiceEvent(invoiceId, evt);
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
leases.Dispose();
|
||||
|
@ -17,6 +17,7 @@ using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Events;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services;
|
||||
|
||||
namespace BTCPayServer.HostedServices
|
||||
{
|
||||
@ -67,7 +68,11 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoiceIdFromScriptPubKey(scriptPubKey, network.CryptoCode);
|
||||
if (invoice != null)
|
||||
{
|
||||
String address = scriptPubKey.GetDestinationAddress(network.NBitcoinNetwork)?.ToString() ?? scriptPubKey.ToString();
|
||||
Logs.PayServer.LogInformation($"{address} is mapping to invoice {invoice}");
|
||||
_WatchRequests.Add(invoice);
|
||||
}
|
||||
}
|
||||
|
||||
async Task NotifyBlock()
|
||||
@ -81,8 +86,11 @@ namespace BTCPayServer.HostedServices
|
||||
private async Task UpdateInvoice(string invoiceId, CancellationToken cancellation)
|
||||
{
|
||||
Dictionary<BTCPayNetwork, KnownState> changes = new Dictionary<BTCPayNetwork, KnownState>();
|
||||
while (!cancellation.IsCancellationRequested)
|
||||
int maxLoop = 5;
|
||||
int loopCount = -1;
|
||||
while (!cancellation.IsCancellationRequested && loopCount < maxLoop)
|
||||
{
|
||||
loopCount++;
|
||||
try
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, true).ConfigureAwait(false);
|
||||
@ -98,7 +106,7 @@ namespace BTCPayServer.HostedServices
|
||||
if (updateContext.Dirty)
|
||||
{
|
||||
await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.Status, invoice.ExceptionStatus).ConfigureAwait(false);
|
||||
_EventAggregator.Publish(new InvoiceDataChangedEvent() { InvoiceId = invoice.Id });
|
||||
updateContext.Events.Add(new InvoiceDataChangedEvent() { Status = invoice.Status, ExceptionStatus = invoice.ExceptionStatus, InvoiceId = invoice.Id });
|
||||
}
|
||||
|
||||
var changed = stateBefore != invoice.Status;
|
||||
@ -117,11 +125,11 @@ namespace BTCPayServer.HostedServices
|
||||
((invoice.Status == "invalid" || invoice.Status == "expired") && invoice.MonitoringExpiration < DateTimeOffset.UtcNow))
|
||||
{
|
||||
if (await _InvoiceRepository.RemovePendingInvoice(invoice.Id).ConfigureAwait(false))
|
||||
Logs.PayServer.LogInformation("Stopped watching invoice " + invoiceId);
|
||||
_EventAggregator.Publish<InvoiceStopWatchedEvent>(new InvoiceStopWatchedEvent() { InvoiceId = invoice.Id });
|
||||
break;
|
||||
}
|
||||
|
||||
if (!changed || cancellation.IsCancellationRequested)
|
||||
if (updateContext.Events.Count == 0 || cancellation.IsCancellationRequested)
|
||||
break;
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellation.IsCancellationRequested)
|
||||
@ -145,12 +153,12 @@ namespace BTCPayServer.HostedServices
|
||||
context.MarkDirty();
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
|
||||
context.Events.Add(new InvoiceStatusChangedEvent(invoice, "expired"));
|
||||
context.Events.Add(new InvoiceEvent(invoice, 1004, "invoice_expired"));
|
||||
invoice.Status = "expired";
|
||||
}
|
||||
|
||||
var derivationStrategies = invoice.GetDerivationStrategies(_NetworkProvider).ToArray();
|
||||
var payments = await GetPaymentsWithTransaction(null, derivationStrategies, invoice);
|
||||
var payments = await GetPaymentsWithTransaction(derivationStrategies, invoice);
|
||||
foreach (Task<NetworkCoins> coinsAsync in GetCoinsPerNetwork(context, invoice, derivationStrategies))
|
||||
{
|
||||
var coins = await coinsAsync;
|
||||
@ -168,12 +176,12 @@ namespace BTCPayServer.HostedServices
|
||||
invoice.Payments.Add(payment);
|
||||
#pragma warning restore CS0618
|
||||
alreadyAccounted.Add(coin.Coin.Outpoint);
|
||||
context.Events.Add(new InvoicePaymentEvent(invoice.Id));
|
||||
context.Events.Add(new InvoiceEvent(invoice, 1002, "invoice_receivedPayment"));
|
||||
dirtyAddress = true;
|
||||
}
|
||||
if (dirtyAddress)
|
||||
{
|
||||
payments = await GetPaymentsWithTransaction(payments, derivationStrategies, invoice);
|
||||
payments = await GetPaymentsWithTransaction(derivationStrategies, invoice);
|
||||
}
|
||||
var network = coins.Wallet.Network;
|
||||
var cryptoData = invoice.GetCryptoData(network, _NetworkProvider);
|
||||
@ -187,26 +195,20 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
if (invoice.Status == "new")
|
||||
{
|
||||
context.Events.Add(new InvoiceStatusChangedEvent(invoice, "paid"));
|
||||
context.Events.Add(new InvoiceEvent(invoice, 1003, "invoice_paidInFull"));
|
||||
invoice.Status = "paid";
|
||||
invoice.ExceptionStatus = null;
|
||||
invoice.ExceptionStatus = totalPaid > accounting.TotalDue ? "paidOver" : null;
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
context.MarkDirty();
|
||||
}
|
||||
else if (invoice.Status == "expired")
|
||||
else if (invoice.Status == "expired" && invoice.ExceptionStatus != "paidLate")
|
||||
{
|
||||
invoice.ExceptionStatus = "paidLate";
|
||||
context.Events.Add(new InvoiceEvent(invoice, 1009, "invoice_paidAfterExpiration"));
|
||||
context.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
if (totalPaid > accounting.TotalDue && invoice.ExceptionStatus != "paidOver")
|
||||
{
|
||||
invoice.ExceptionStatus = "paidOver";
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
context.MarkDirty();
|
||||
}
|
||||
|
||||
if (totalPaid < accounting.TotalDue && invoice.GetPayments().Count != 0 && invoice.ExceptionStatus != "paidPartial")
|
||||
{
|
||||
invoice.ExceptionStatus = "paidPartial";
|
||||
@ -245,14 +247,14 @@ namespace BTCPayServer.HostedServices
|
||||
(totalConfirmed < accounting.TotalDue))
|
||||
{
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
context.Events.Add(new InvoiceStatusChangedEvent(invoice, "invalid"));
|
||||
context.Events.Add(new InvoiceEvent(invoice, 1013, "invoice_failedToConfirm"));
|
||||
invoice.Status = "invalid";
|
||||
context.MarkDirty();
|
||||
}
|
||||
else if (totalConfirmed >= accounting.TotalDue)
|
||||
{
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
context.Events.Add(new InvoiceStatusChangedEvent(invoice, "confirmed"));
|
||||
context.Events.Add(new InvoiceEvent(invoice, 1005, "invoice_confirmed"));
|
||||
invoice.Status = "confirmed";
|
||||
context.MarkDirty();
|
||||
}
|
||||
@ -265,7 +267,7 @@ namespace BTCPayServer.HostedServices
|
||||
var totalConfirmed = transactions.Select(t => t.Payment.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum();
|
||||
if (totalConfirmed >= accounting.TotalDue)
|
||||
{
|
||||
context.Events.Add(new InvoiceStatusChangedEvent(invoice, "complete"));
|
||||
context.Events.Add(new InvoiceEvent(invoice, 1006, "invoice_completed"));
|
||||
invoice.Status = "complete";
|
||||
context.MarkDirty();
|
||||
}
|
||||
@ -276,7 +278,7 @@ namespace BTCPayServer.HostedServices
|
||||
private IEnumerable<Task<NetworkCoins>> GetCoinsPerNetwork(UpdateInvoiceContext context, InvoiceEntity invoice, DerivationStrategy[] strategies)
|
||||
{
|
||||
return strategies
|
||||
.Select(d => (Wallet: _WalletProvider.GetWallet(d.Network),
|
||||
.Select(d => (Wallet: _WalletProvider.IsAvailable(d.Network) ? _WalletProvider.GetWallet(d.Network) : null,
|
||||
Network: d.Network,
|
||||
Strategy: d.DerivationStrategyBase))
|
||||
.Where(d => d.Wallet != null)
|
||||
@ -293,58 +295,23 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
|
||||
|
||||
class AccountedPaymentEntities : List<AccountedPaymentEntity>
|
||||
{
|
||||
public AccountedPaymentEntities(AccountedPaymentEntities existing)
|
||||
{
|
||||
if (existing != null)
|
||||
_Transactions = existing._Transactions;
|
||||
}
|
||||
|
||||
Dictionary<uint256, TransactionResult> _Transactions = new Dictionary<uint256, TransactionResult>();
|
||||
|
||||
public void AddToCache(IEnumerable<TransactionResult> transactions)
|
||||
{
|
||||
foreach (var tx in transactions)
|
||||
_Transactions.TryAdd(tx.Transaction.GetHash(), tx);
|
||||
}
|
||||
public TransactionResult GetTransaction(uint256 txId)
|
||||
{
|
||||
_Transactions.TryGetValue(txId, out TransactionResult result);
|
||||
return result;
|
||||
}
|
||||
|
||||
internal IEnumerable<TransactionResult> GetTransactions()
|
||||
{
|
||||
return _Transactions.Values;
|
||||
}
|
||||
}
|
||||
private async Task<AccountedPaymentEntities> GetPaymentsWithTransaction(AccountedPaymentEntities previous, DerivationStrategy[] derivations, InvoiceEntity invoice)
|
||||
private async Task<IEnumerable<AccountedPaymentEntity>> GetPaymentsWithTransaction(DerivationStrategy[] derivations, InvoiceEntity invoice)
|
||||
{
|
||||
List<PaymentEntity> updatedPaymentEntities = new List<PaymentEntity>();
|
||||
AccountedPaymentEntities accountedPayments = new AccountedPaymentEntities(previous);
|
||||
List<AccountedPaymentEntity> accountedPayments = new List<AccountedPaymentEntity>();
|
||||
foreach (var network in derivations.Select(d => d.Network))
|
||||
{
|
||||
var wallet = _WalletProvider.GetWallet(network);
|
||||
if (wallet == null)
|
||||
continue;
|
||||
|
||||
var hashesToFetch = new HashSet<uint256>(invoice
|
||||
.GetPayments(network)
|
||||
var transactions = await wallet.GetTransactions(invoice.GetPayments(wallet.Network)
|
||||
.Select(t => t.Outpoint.Hash)
|
||||
.Where(h => accountedPayments?.GetTransaction(h) == null)
|
||||
.ToList());
|
||||
|
||||
|
||||
if (hashesToFetch.Count > 0)
|
||||
{
|
||||
accountedPayments.AddToCache((await wallet.GetTransactions(hashesToFetch.ToArray())).Select(t => t.Value));
|
||||
}
|
||||
var conflicts = GetConflicts(accountedPayments.GetTransactions());
|
||||
.ToArray());
|
||||
var conflicts = GetConflicts(transactions.Select(t => t.Value));
|
||||
foreach (var payment in invoice.GetPayments(network))
|
||||
{
|
||||
TransactionResult tx = accountedPayments.GetTransaction(payment.Outpoint.Hash);
|
||||
if (tx == null)
|
||||
if (!transactions.TryGetValue(payment.Outpoint.Hash, out TransactionResult tx))
|
||||
continue;
|
||||
|
||||
AccountedPaymentEntity accountedPayment = new AccountedPaymentEntity()
|
||||
@ -370,7 +337,6 @@ namespace BTCPayServer.HostedServices
|
||||
return accountedPayments;
|
||||
}
|
||||
|
||||
|
||||
class TransactionConflict
|
||||
{
|
||||
public Dictionary<uint256, TransactionResult> Transactions { get; set; } = new Dictionary<uint256, TransactionResult>();
|
||||
@ -443,11 +409,23 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
}
|
||||
|
||||
private void Watch(string invoiceId)
|
||||
private async Task Watch(string invoiceId)
|
||||
{
|
||||
if (invoiceId == null)
|
||||
throw new ArgumentNullException(nameof(invoiceId));
|
||||
_WatchRequests.Add(invoiceId);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
try
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
if (invoice.ExpirationTime > now)
|
||||
{
|
||||
await Task.Delay(invoice.ExpirationTime - now, _Cts.Token);
|
||||
_WatchRequests.Add(invoiceId);
|
||||
}
|
||||
}
|
||||
catch when (_Cts.IsCancellationRequested)
|
||||
{ }
|
||||
}
|
||||
|
||||
BlockingCollection<string> _WatchRequests = new BlockingCollection<string>(new ConcurrentQueue<string>());
|
||||
@ -465,7 +443,13 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
leases.Add(_EventAggregator.Subscribe<Events.NewBlockEvent>(async b => { await NotifyBlock(); }));
|
||||
leases.Add(_EventAggregator.Subscribe<Events.TxOutReceivedEvent>(async b => { await NotifyReceived(b.ScriptPubKey, b.Network); }));
|
||||
leases.Add(_EventAggregator.Subscribe<Events.InvoiceCreatedEvent>(b => { Watch(b.InvoiceId); }));
|
||||
leases.Add(_EventAggregator.Subscribe<Events.InvoiceEvent>(async b =>
|
||||
{
|
||||
if (b.Name == "invoice_created")
|
||||
{
|
||||
await Watch(b.InvoiceId);
|
||||
}
|
||||
}));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@ -499,24 +483,32 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
Logs.PayServer.LogInformation("Start watching invoices");
|
||||
await Task.Delay(1).ConfigureAwait(false); // Small hack so that the caller does not block on GetConsumingEnumerable
|
||||
ConcurrentDictionary<string, Task> executing = new ConcurrentDictionary<string, Task>();
|
||||
ConcurrentDictionary<string, Lazy<Task>> executing = new ConcurrentDictionary<string, Lazy<Task>>();
|
||||
try
|
||||
{
|
||||
// This loop just make sure an invoice will not be updated at the same time by two tasks.
|
||||
// If an update is happening while a request come, then the update is deferred when the executing task is over
|
||||
foreach (var item in _WatchRequests.GetConsumingEnumerable(cancellation))
|
||||
{
|
||||
var task = executing.GetOrAdd(item, async i =>
|
||||
var localItem = item;
|
||||
var toExecute = new Lazy<Task>(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await UpdateInvoice(i, cancellation);
|
||||
await UpdateInvoice(localItem, cancellation);
|
||||
}
|
||||
catch (Exception ex) when (!cancellation.IsCancellationRequested)
|
||||
finally
|
||||
{
|
||||
Logs.PayServer.LogCritical(ex, $"Error in the InvoiceWatcher loop (Invoice {item})");
|
||||
await Task.Delay(2000, cancellation);
|
||||
executing.TryRemove(localItem, out Lazy<Task> unused);
|
||||
}
|
||||
finally { executing.TryRemove(item, out Task useless); }
|
||||
});
|
||||
}, false);
|
||||
var executingTask = executing.GetOrAdd(item, toExecute);
|
||||
executingTask.Value.GetAwaiter(); // Make sure it run
|
||||
if (executingTask != toExecute)
|
||||
{
|
||||
// What was planned can't run for now, rebook it when the executingTask finish
|
||||
var unused = executingTask.Value.ContinueWith(t => _WatchRequests.Add(localItem));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch when (cancellation.IsCancellationRequested)
|
||||
@ -524,7 +516,7 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Task.WhenAll(executing.Values);
|
||||
await Task.WhenAll(executing.Values.Select(v => v.Value).ToArray());
|
||||
}
|
||||
Logs.PayServer.LogInformation("Stop watching invoices");
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ using NBXplorer;
|
||||
using System.Collections.Concurrent;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Services;
|
||||
|
||||
namespace BTCPayServer.HostedServices
|
||||
{
|
||||
@ -24,9 +25,11 @@ namespace BTCPayServer.HostedServices
|
||||
private TaskCompletionSource<bool> _RunningTask;
|
||||
private CancellationTokenSource _Cts;
|
||||
NBXplorerDashboard _Dashboards;
|
||||
TransactionCacheProvider _TxCache;
|
||||
|
||||
public NBXplorerListener(ExplorerClientProvider explorerClients,
|
||||
NBXplorerDashboard dashboard,
|
||||
TransactionCacheProvider cacheProvider,
|
||||
InvoiceRepository invoiceRepository,
|
||||
EventAggregator aggregator, IApplicationLifetime lifetime)
|
||||
{
|
||||
@ -36,6 +39,7 @@ namespace BTCPayServer.HostedServices
|
||||
_ExplorerClients = explorerClients;
|
||||
_Aggregator = aggregator;
|
||||
_Lifetime = lifetime;
|
||||
_TxCache = cacheProvider;
|
||||
}
|
||||
|
||||
CompositeDisposable leases = new CompositeDisposable();
|
||||
@ -83,19 +87,22 @@ namespace BTCPayServer.HostedServices
|
||||
}, null, 0, (int)PollInterval.TotalMilliseconds);
|
||||
leases.Add(_ListenPoller);
|
||||
|
||||
leases.Add(_Aggregator.Subscribe<Events.InvoiceCreatedEvent>(async inv =>
|
||||
leases.Add(_Aggregator.Subscribe<Events.InvoiceEvent>(async inv =>
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, inv.InvoiceId);
|
||||
List<Task> listeningDerivations = new List<Task>();
|
||||
foreach (var notificationSessions in _Sessions)
|
||||
if (inv.Name == "invoice_created")
|
||||
{
|
||||
var derivationStrategy = GetStrategy(notificationSessions.Key, invoice);
|
||||
if (derivationStrategy != null)
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, inv.InvoiceId);
|
||||
List<Task> listeningDerivations = new List<Task>();
|
||||
foreach (var notificationSessions in _Sessions)
|
||||
{
|
||||
listeningDerivations.Add(notificationSessions.Value.ListenDerivationSchemesAsync(new[] { derivationStrategy }, _Cts.Token));
|
||||
var derivationStrategy = GetStrategy(notificationSessions.Key, invoice);
|
||||
if (derivationStrategy != null)
|
||||
{
|
||||
listeningDerivations.Add(notificationSessions.Value.ListenDerivationSchemesAsync(new[] { derivationStrategy }, _Cts.Token));
|
||||
}
|
||||
}
|
||||
await Task.WhenAll(listeningDerivations.ToArray()).ConfigureAwait(false);
|
||||
}
|
||||
await Task.WhenAll(listeningDerivations.ToArray()).ConfigureAwait(false);
|
||||
}));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@ -130,11 +137,13 @@ namespace BTCPayServer.HostedServices
|
||||
switch (newEvent)
|
||||
{
|
||||
case NBXplorer.Models.NewBlockEvent evt:
|
||||
_Aggregator.Publish(new Events.NewBlockEvent());
|
||||
_TxCache.GetTransactionCache(network).NewBlock(evt.Hash, evt.PreviousBlockHash);
|
||||
_Aggregator.Publish(new Events.NewBlockEvent() { CryptoCode = evt.CryptoCode });
|
||||
break;
|
||||
case NBXplorer.Models.NewTransactionEvent evt:
|
||||
foreach (var txout in evt.Match.Outputs)
|
||||
foreach (var txout in evt.Outputs)
|
||||
{
|
||||
_TxCache.GetTransactionCache(network).AddToCache(evt.TransactionData);
|
||||
_Aggregator.Publish(new Events.TxOutReceivedEvent()
|
||||
{
|
||||
Network = network,
|
||||
|
@ -27,11 +27,12 @@ namespace BTCPayServer.HostedServices
|
||||
public BTCPayNetwork Network { get; set; }
|
||||
public NBXplorerState State { get; set; }
|
||||
public StatusResult Status { get; set; }
|
||||
public string Error { get; set; }
|
||||
}
|
||||
ConcurrentDictionary<string, NBXplorerSummary> _Summaries = new ConcurrentDictionary<string, NBXplorerSummary>();
|
||||
public void Publish(BTCPayNetwork network, NBXplorerState state, StatusResult status)
|
||||
public void Publish(BTCPayNetwork network, NBXplorerState state, StatusResult status, string error)
|
||||
{
|
||||
var summary = new NBXplorerSummary() { Network = network, State = state, Status = status };
|
||||
var summary = new NBXplorerSummary() { Network = network, State = state, Status = status, Error = error };
|
||||
_Summaries.AddOrUpdate(network.CryptoCode, summary, (k, v) => summary);
|
||||
}
|
||||
|
||||
@ -40,6 +41,11 @@ namespace BTCPayServer.HostedServices
|
||||
return _Summaries.All(s => s.Value.Status != null && s.Value.Status.IsFullySynched);
|
||||
}
|
||||
|
||||
public bool IsFullySynched(string cryptoCode)
|
||||
{
|
||||
return _Summaries.Any(s => s.Key.Equals(cryptoCode, StringComparison.OrdinalIgnoreCase) && s.Value.Status != null && s.Value.Status.IsFullySynched);
|
||||
}
|
||||
|
||||
public IEnumerable<NBXplorerSummary> GetAll()
|
||||
{
|
||||
return _Summaries.Values;
|
||||
@ -120,6 +126,7 @@ namespace BTCPayServer.HostedServices
|
||||
private async Task<bool> StepAsync(CancellationToken cancellation)
|
||||
{
|
||||
var oldState = State;
|
||||
string error = null;
|
||||
StatusResult status = null;
|
||||
try
|
||||
{
|
||||
@ -164,15 +171,28 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
|
||||
}
|
||||
catch when (cancellation.IsCancellationRequested)
|
||||
catch (Exception ex) when (!cancellation.IsCancellationRequested)
|
||||
{
|
||||
|
||||
error = ex.Message;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
|
||||
if(status == null && error == null)
|
||||
error = $"{_Network.CryptoCode}: NBXplorer does not support this cryptocurrency";
|
||||
|
||||
if(status != null && error == null)
|
||||
{
|
||||
if(status.ChainType != _Network.NBXplorerNetwork.DefaultSettings.ChainType)
|
||||
error = $"{_Network.CryptoCode}: NBXplorer is on a different ChainType (actual: {status.ChainType}, expected: {_Network.NBXplorerNetwork.DefaultSettings.ChainType})";
|
||||
}
|
||||
|
||||
if (error != null)
|
||||
{
|
||||
State = NBXplorerState.NotConnected;
|
||||
Logs.PayServer.LogError(ex, $"Error while trying to connect to NBXplorer ({_Network.CryptoCode})");
|
||||
status = null;
|
||||
_Aggregator.Publish(new NBXplorerErrorEvent(_Network, error));
|
||||
}
|
||||
|
||||
if (oldState != State)
|
||||
{
|
||||
if (State == NBXplorerState.Synching)
|
||||
@ -185,7 +205,7 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
_Aggregator.Publish(new NBXplorerStateChangedEvent(_Network, oldState, State));
|
||||
}
|
||||
_Dashboard.Publish(_Network, State, status);
|
||||
_Dashboard.Publish(_Network, State, status, error);
|
||||
return oldState != State;
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ namespace BTCPayServer.Hosting
|
||||
var dbpath = Path.Combine(opts.DataDir, "InvoiceDB");
|
||||
if (!Directory.Exists(dbpath))
|
||||
Directory.CreateDirectory(dbpath);
|
||||
return new InvoiceRepository(dbContext, dbpath, opts.Network);
|
||||
return new InvoiceRepository(dbContext, dbpath);
|
||||
});
|
||||
services.AddSingleton<BTCPayServerEnvironment>();
|
||||
services.TryAddSingleton<TokenRepository>();
|
||||
@ -129,7 +129,7 @@ namespace BTCPayServer.Hosting
|
||||
services.TryAddSingleton<BTCPayNetworkProvider>(o =>
|
||||
{
|
||||
var opts = o.GetRequiredService<BTCPayServerOptions>();
|
||||
return new BTCPayNetworkProvider(opts.Network);
|
||||
return new BTCPayNetworkProvider(opts.ChainType);
|
||||
});
|
||||
|
||||
services.TryAddSingleton<NBXplorerDashboard>();
|
||||
@ -142,6 +142,8 @@ namespace BTCPayServer.Hosting
|
||||
BlockTarget = 20
|
||||
});
|
||||
|
||||
services.AddSingleton<TransactionCacheProvider>();
|
||||
|
||||
services.AddSingleton<IHostedService, NBXplorerWaiters>();
|
||||
services.AddSingleton<IHostedService, NBXplorerListener>();
|
||||
services.AddSingleton<IHostedService, InvoiceNotificationManager>();
|
||||
@ -150,7 +152,7 @@ namespace BTCPayServer.Hosting
|
||||
services.TryAddSingleton<ExplorerClientProvider>();
|
||||
services.TryAddSingleton<Bitpay>(o =>
|
||||
{
|
||||
if (o.GetRequiredService<BTCPayServerOptions>().Network == Network.Main)
|
||||
if (o.GetRequiredService<BTCPayServerOptions>().ChainType == ChainType.Main)
|
||||
return new Bitpay(new Key(), new Uri("https://bitpay.com/"));
|
||||
else
|
||||
return new Bitpay(new Key(), new Uri("https://test.bitpay.com/"));
|
||||
|
@ -12,7 +12,11 @@ namespace BTCPayServer.Logging
|
||||
{
|
||||
public class CustomConsoleLogProvider : ILoggerProvider
|
||||
{
|
||||
ConsoleLoggerProcessor _Processor = new ConsoleLoggerProcessor();
|
||||
ConsoleLoggerProcessor _Processor;
|
||||
public CustomConsoleLogProvider(ConsoleLoggerProcessor processor)
|
||||
{
|
||||
_Processor = processor;
|
||||
}
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return new CustomConsoleLogger(categoryName, (a, b) => true, false, _Processor);
|
||||
|
508
BTCPayServer/Migrations/20180114123253_events.Designer.cs
generated
Normal file
508
BTCPayServer/Migrations/20180114123253_events.Designer.cs
generated
Normal file
@ -0,0 +1,508 @@
|
||||
// <auto-generated />
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20180114123253_events")]
|
||||
partial class events
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.0.1-rtm-125");
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Address")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTimeOffset?>("CreatedTime");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Address");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("AddressInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.Property<string>("Address");
|
||||
|
||||
b.Property<DateTimeOffset>("Assigned");
|
||||
|
||||
b.Property<string>("CryptoCode");
|
||||
|
||||
b.Property<DateTimeOffset?>("UnAssigned");
|
||||
|
||||
b.HasKey("InvoiceDataId", "Address");
|
||||
|
||||
b.ToTable("HistoricalAddressInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<DateTimeOffset>("Created");
|
||||
|
||||
b.Property<string>("CustomerEmail");
|
||||
|
||||
b.Property<string>("ExceptionStatus");
|
||||
|
||||
b.Property<string>("ItemCode");
|
||||
|
||||
b.Property<string>("OrderId");
|
||||
|
||||
b.Property<string>("Status");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("Invoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceEventData", b =>
|
||||
{
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.Property<string>("UniqueId");
|
||||
|
||||
b.Property<string>("Message");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp");
|
||||
|
||||
b.HasKey("InvoiceDataId", "UniqueId");
|
||||
|
||||
b.ToTable("InvoiceEvents");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Facade");
|
||||
|
||||
b.Property<string>("Label");
|
||||
|
||||
b.Property<DateTimeOffset>("PairingTime");
|
||||
|
||||
b.Property<string>("SIN");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SIN");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("PairedSINData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairingCodeData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("DateCreated");
|
||||
|
||||
b.Property<DateTimeOffset>("Expiration");
|
||||
|
||||
b.Property<string>("Facade");
|
||||
|
||||
b.Property<string>("Label");
|
||||
|
||||
b.Property<string>("SIN");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<string>("TokenValue");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PairingCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<bool>("Accounted");
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("Payments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PendingInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("RefundAddresses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.SettingData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("DefaultCrypto");
|
||||
|
||||
b.Property<string>("DerivationStrategies");
|
||||
|
||||
b.Property<string>("DerivationStrategy");
|
||||
|
||||
b.Property<int>("SpeedPolicy");
|
||||
|
||||
b.Property<byte[]>("StoreBlob");
|
||||
|
||||
b.Property<byte[]>("StoreCertificate");
|
||||
|
||||
b.Property<string>("StoreName");
|
||||
|
||||
b.Property<string>("StoreWebsite");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Stores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
|
||||
{
|
||||
b.Property<string>("ApplicationUserId");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<string>("Role");
|
||||
|
||||
b.HasKey("ApplicationUserId", "StoreDataId");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("UserStore");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Models.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("AccessFailedCount");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<bool>("EmailConfirmed");
|
||||
|
||||
b.Property<bool>("LockoutEnabled");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("PasswordHash");
|
||||
|
||||
b.Property<string>("PhoneNumber");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed");
|
||||
|
||||
b.Property<bool>("RequiresEmailConfirmation");
|
||||
|
||||
b.Property<string>("SecurityStamp");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("ProviderKey");
|
||||
|
||||
b.Property<string>("ProviderDisplayName");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("RoleId");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("AddressInvoices")
|
||||
.HasForeignKey("InvoiceDataId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData")
|
||||
.WithMany("HistoricalAddressInvoices")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany()
|
||||
.HasForeignKey("StoreDataId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceEventData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData")
|
||||
.WithMany("Events")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("Payments")
|
||||
.HasForeignKey("InvoiceDataId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("RefundAddresses")
|
||||
.HasForeignKey("InvoiceDataId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("UserStores")
|
||||
.HasForeignKey("ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("UserStores")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
38
BTCPayServer/Migrations/20180114123253_events.cs
Normal file
38
BTCPayServer/Migrations/20180114123253_events.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
public partial class events : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "InvoiceEvents",
|
||||
columns: table => new
|
||||
{
|
||||
InvoiceDataId = table.Column<string>(nullable: false),
|
||||
UniqueId = table.Column<string>(nullable: false),
|
||||
Message = table.Column<string>(nullable: true),
|
||||
Timestamp = table.Column<DateTimeOffset>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_InvoiceEvents", x => new { x.InvoiceDataId, x.UniqueId });
|
||||
table.ForeignKey(
|
||||
name: "FK_InvoiceEvents_Invoices_InvoiceDataId",
|
||||
column: x => x.InvoiceDataId,
|
||||
principalTable: "Invoices",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "InvoiceEvents");
|
||||
}
|
||||
}
|
||||
}
|
@ -81,6 +81,21 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("Invoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceEventData", b =>
|
||||
{
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.Property<string>("UniqueId");
|
||||
|
||||
b.Property<string>("Message");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp");
|
||||
|
||||
b.HasKey("InvoiceDataId", "UniqueId");
|
||||
|
||||
b.ToTable("InvoiceEvents");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -407,6 +422,14 @@ namespace BTCPayServer.Migrations
|
||||
.HasForeignKey("StoreDataId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceEventData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData")
|
||||
.WithMany("Events")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
|
@ -97,6 +97,8 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string TransactionSpeed { get; set; }
|
||||
public object StoreName
|
||||
{
|
||||
get;
|
||||
@ -112,6 +114,8 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
|
||||
public string RedirectUrl { get; set; }
|
||||
public string Fiat
|
||||
{
|
||||
get;
|
||||
@ -124,5 +128,6 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
}
|
||||
public HistoricalAddressInvoiceData[] Addresses { get; set; }
|
||||
public DateTimeOffset MonitoringDate { get; internal set; }
|
||||
public List<Data.InvoiceEventData> Events { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
@ -39,5 +39,6 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public string OrderId { get; set; }
|
||||
public string CryptoImage { get; set; }
|
||||
public string NetworkFeeDescription { get; internal set; }
|
||||
public int MaxTimeMinutes { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +48,8 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
set;
|
||||
}
|
||||
|
||||
public bool Confirmation { get; set; }
|
||||
|
||||
public SelectList CryptoCurrencies { get; set; }
|
||||
public SelectList DerivationSchemeFormats { get; set; }
|
||||
|
||||
|
@ -47,7 +47,34 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
|
||||
public List<StoreViewModel.DerivationScheme> DerivationSchemes { get; set; } = new List<StoreViewModel.DerivationScheme>();
|
||||
|
||||
[Display(Name = "Payment invalid if transactions fails to confirm after ... minutes")]
|
||||
[Display(Name = "Preferred price source (eg. bitfinex, bitstamp...)")]
|
||||
public string PreferredExchange { get; set; }
|
||||
|
||||
public string RateSource
|
||||
{
|
||||
get
|
||||
{
|
||||
return PreferredExchange.IsCoinAverage() ? "https://apiv2.bitcoinaverage.com/indices/global/ticker/short" : $"https://apiv2.bitcoinaverage.com/exchanges/{PreferredExchange}";
|
||||
}
|
||||
}
|
||||
|
||||
[Display(Name = "Multiply the original rate by ...")]
|
||||
[Range(0.01, 10.0)]
|
||||
public double RateMultiplier
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Display(Name = "Invoice expires if the full amount has not been paid after ... minutes")]
|
||||
[Range(1, 60 * 24 * 31)]
|
||||
public int InvoiceExpiration
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Display(Name = "Payment invalid if transactions fails to confirm ... minutes after invoice expiration")]
|
||||
[Range(10, 60 * 24 * 31)]
|
||||
public int MonitoringExpiration
|
||||
{
|
||||
|
@ -24,7 +24,8 @@ namespace BTCPayServer
|
||||
{
|
||||
ServicePointManager.DefaultConnectionLimit = 100;
|
||||
IWebHost host = null;
|
||||
CustomConsoleLogProvider loggerProvider = new CustomConsoleLogProvider();
|
||||
var processor = new ConsoleLoggerProcessor();
|
||||
CustomConsoleLogProvider loggerProvider = new CustomConsoleLogProvider(processor);
|
||||
|
||||
var loggerFactory = new LoggerFactory();
|
||||
loggerFactory.AddProvider(loggerProvider);
|
||||
@ -44,7 +45,8 @@ namespace BTCPayServer
|
||||
.ConfigureLogging(l =>
|
||||
{
|
||||
l.AddFilter("Microsoft", LogLevel.Error);
|
||||
l.AddProvider(new CustomConsoleLogProvider());
|
||||
l.AddFilter("Microsoft.AspNetCore.Antiforgery.Internal", LogLevel.Critical);
|
||||
l.AddProvider(new CustomConsoleLogProvider(processor));
|
||||
})
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
@ -61,13 +63,9 @@ namespace BTCPayServer
|
||||
if (!string.IsNullOrEmpty(ex.Message))
|
||||
Logs.Configuration.LogError(ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
logger.LogError("Exception thrown while running the server");
|
||||
logger.LogError(exception.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
processor.Dispose();
|
||||
if (host != null)
|
||||
host.Dispose();
|
||||
loggerProvider.Dispose();
|
||||
|
@ -1,27 +1,29 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:14139/",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"Default": {
|
||||
"commandName": "Project"
|
||||
},
|
||||
"Docker-Regtest": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/",
|
||||
"BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32839/",
|
||||
"BTCPAY_NETWORK": "regtest",
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver"
|
||||
},
|
||||
"applicationUrl": "http://localhost:14142/"
|
||||
}
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:14139/",
|
||||
"sslPort": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"Default": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "--network testnet --chains ltc --ltcexplorerurl http://127.0.0.1:2727/"
|
||||
},
|
||||
"Docker-Regtest": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"BTCPAY_NETWORK": "regtest",
|
||||
"BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32838/",
|
||||
"BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/",
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"BTCPAY_CHAINS": "btc,ltc",
|
||||
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver"
|
||||
},
|
||||
"applicationUrl": "http://localhost:14142/"
|
||||
}
|
||||
}
|
||||
}
|
@ -5,12 +5,13 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text;
|
||||
using NBXplorer;
|
||||
|
||||
namespace BTCPayServer.Services
|
||||
{
|
||||
public class BTCPayServerEnvironment
|
||||
{
|
||||
public BTCPayServerEnvironment(IHostingEnvironment env)
|
||||
public BTCPayServerEnvironment(IHostingEnvironment env, BTCPayNetworkProvider provider)
|
||||
{
|
||||
Version = typeof(BTCPayServerEnvironment).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyFileVersionAttribute>().Version;
|
||||
#if DEBUG
|
||||
@ -19,11 +20,13 @@ namespace BTCPayServer.Services
|
||||
Build = "Release";
|
||||
#endif
|
||||
Environment = env;
|
||||
ChainType = provider.NBXplorerNetworkProvider.ChainType;
|
||||
}
|
||||
public IHostingEnvironment Environment
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public ChainType ChainType { get; set; }
|
||||
public string Version
|
||||
{
|
||||
get; set;
|
||||
|
@ -299,6 +299,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
set;
|
||||
}
|
||||
public bool ExtendedNotifications { get; set; }
|
||||
public List<InvoiceEventData> Events { get; internal set; }
|
||||
|
||||
public bool IsExpired()
|
||||
{
|
||||
@ -323,7 +324,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
};
|
||||
|
||||
dto.CryptoInfo = new List<NBitpayClient.InvoiceCryptoInfo>();
|
||||
foreach (var info in this.GetCryptoData(networkProvider).Values)
|
||||
foreach (var info in this.GetCryptoData(networkProvider, true).Values)
|
||||
{
|
||||
var accounting = info.Calculate();
|
||||
var cryptoInfo = new NBitpayClient.InvoiceCryptoInfo();
|
||||
@ -369,8 +370,8 @@ namespace BTCPayServer.Services.Invoices
|
||||
dto.PaymentUrls = cryptoInfo.PaymentUrls;
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
|
||||
dto.CryptoInfo.Add(cryptoInfo);
|
||||
if(!info.IsPhantomBTC)
|
||||
dto.CryptoInfo.Add(cryptoInfo);
|
||||
}
|
||||
|
||||
Populate(ProductInformation, dto);
|
||||
@ -395,9 +396,9 @@ namespace BTCPayServer.Services.Invoices
|
||||
return rates.TryGetValue(network.CryptoCode, out var data);
|
||||
}
|
||||
|
||||
public CryptoData GetCryptoData(string cryptoCode, BTCPayNetworkProvider networkProvider)
|
||||
public CryptoData GetCryptoData(string cryptoCode, BTCPayNetworkProvider networkProvider, bool alwaysIncludeBTC = false)
|
||||
{
|
||||
GetCryptoData(networkProvider).TryGetValue(cryptoCode, out var data);
|
||||
GetCryptoData(networkProvider, alwaysIncludeBTC).TryGetValue(cryptoCode, out var data);
|
||||
return data;
|
||||
}
|
||||
|
||||
@ -407,26 +408,30 @@ namespace BTCPayServer.Services.Invoices
|
||||
return data;
|
||||
}
|
||||
|
||||
public Dictionary<string, CryptoData> GetCryptoData(BTCPayNetworkProvider networkProvider)
|
||||
public Dictionary<string, CryptoData> GetCryptoData(BTCPayNetworkProvider networkProvider, bool alwaysIncludeBTC = false)
|
||||
{
|
||||
Dictionary<string, CryptoData> rates = new Dictionary<string, CryptoData>();
|
||||
var serializer = new Serializer(Dummy);
|
||||
CryptoData phantom = null;
|
||||
#pragma warning disable CS0618
|
||||
// Legacy
|
||||
if (Rate != 0.0m)
|
||||
if (alwaysIncludeBTC)
|
||||
{
|
||||
var btcNetwork = networkProvider?.GetNetwork("BTC");
|
||||
rates.TryAdd("BTC", new CryptoData() { ParentEntity = this, Rate = Rate, CryptoCode = "BTC", TxFee = TxFee, FeeRate = new FeeRate(TxFee, 100), DepositAddress = DepositAddress, Network = btcNetwork });
|
||||
phantom = new CryptoData() { ParentEntity = this, IsPhantomBTC = true, Rate = Rate, CryptoCode = "BTC", TxFee = TxFee, FeeRate = new FeeRate(TxFee, 100), DepositAddress = DepositAddress, Network = btcNetwork };
|
||||
rates.Add("BTC", phantom);
|
||||
}
|
||||
if (CryptoData != null)
|
||||
{
|
||||
foreach (var prop in CryptoData.Properties())
|
||||
{
|
||||
if (prop.Name == "BTC" && phantom != null)
|
||||
rates.Remove("BTC");
|
||||
var r = serializer.ToObject<CryptoData>(prop.Value.ToString());
|
||||
r.CryptoCode = prop.Name;
|
||||
r.ParentEntity = this;
|
||||
r.Network = networkProvider?.GetNetwork(r.CryptoCode);
|
||||
rates.TryAdd(r.CryptoCode, r);
|
||||
rates.Add(r.CryptoCode, r);
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
@ -507,9 +512,12 @@ namespace BTCPayServer.Services.Invoices
|
||||
[JsonProperty(PropertyName = "depositAddress")]
|
||||
public string DepositAddress { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsPhantomBTC { get; set; }
|
||||
|
||||
public CryptoDataAccounting Calculate()
|
||||
{
|
||||
var cryptoData = ParentEntity.GetCryptoData(null);
|
||||
var cryptoData = ParentEntity.GetCryptoData(null, IsPhantomBTC);
|
||||
var totalDue = Money.Coins(ParentEntity.ProductInformation.Price / Rate);
|
||||
var paid = Money.Zero;
|
||||
var cryptoPaid = Money.Zero;
|
||||
|
@ -1,4 +1,5 @@
|
||||
using DBreeze;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
@ -16,6 +17,7 @@ using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using System.Globalization;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Logging;
|
||||
|
||||
namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
@ -32,32 +34,18 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Network _Network;
|
||||
public Network Network
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Network;
|
||||
}
|
||||
set
|
||||
{
|
||||
_Network = value;
|
||||
}
|
||||
}
|
||||
|
||||
private ApplicationDbContextFactory _ContextFactory;
|
||||
private CustomThreadPool _IndexerThread;
|
||||
public InvoiceRepository(ApplicationDbContextFactory contextFactory, string dbreezePath, Network network)
|
||||
public InvoiceRepository(ApplicationDbContextFactory contextFactory, string dbreezePath)
|
||||
{
|
||||
_Engine = new DBreezeEngine(dbreezePath);
|
||||
_IndexerThread = new CustomThreadPool(1, "Invoice Indexer");
|
||||
_Network = network;
|
||||
_ContextFactory = contextFactory;
|
||||
}
|
||||
|
||||
public async Task<bool> RemovePendingInvoice(string invoiceId)
|
||||
{
|
||||
Logs.PayServer.LogInformation($"Remove pending invoice {invoiceId}");
|
||||
using (var ctx = _ContextFactory.CreateContext())
|
||||
{
|
||||
ctx.PendingInvoices.Remove(new PendingInvoiceData() { Id = invoiceId });
|
||||
@ -105,7 +93,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
public async Task<InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice, BTCPayNetworkProvider networkProvider)
|
||||
{
|
||||
List<string> textSearch = new List<string>();
|
||||
invoice = Clone(invoice);
|
||||
invoice = Clone(invoice, null);
|
||||
invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
|
||||
#pragma warning disable CS0618
|
||||
invoice.Payments = new List<PaymentEntity>();
|
||||
@ -118,7 +106,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
StoreDataId = storeId,
|
||||
Id = invoice.Id,
|
||||
Created = invoice.InvoiceTime,
|
||||
Blob = ToBytes(invoice),
|
||||
Blob = ToBytes(invoice, null),
|
||||
OrderId = invoice.OrderId,
|
||||
Status = invoice.Status,
|
||||
ItemCode = invoice.ProductInformation.ItemCode,
|
||||
@ -150,8 +138,8 @@ namespace BTCPayServer.Services.Invoices
|
||||
textSearch.Add(invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture));
|
||||
textSearch.Add(invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture));
|
||||
textSearch.Add(invoice.OrderId);
|
||||
textSearch.Add(ToString(invoice.BuyerInformation));
|
||||
textSearch.Add(ToString(invoice.ProductInformation));
|
||||
textSearch.Add(ToString(invoice.BuyerInformation, null));
|
||||
textSearch.Add(ToString(invoice.ProductInformation, null));
|
||||
textSearch.Add(invoice.StoreId);
|
||||
|
||||
AddToTextSearch(invoice.Id, textSearch.ToArray());
|
||||
@ -167,7 +155,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
if (invoice == null)
|
||||
return false;
|
||||
|
||||
var invoiceEntity = ToObject<InvoiceEntity>(invoice.Blob);
|
||||
var invoiceEntity = ToObject<InvoiceEntity>(invoice.Blob, network.NBitcoinNetwork);
|
||||
var currencyData = invoiceEntity.GetCryptoData(network, null);
|
||||
if (currencyData == null)
|
||||
return false;
|
||||
@ -186,10 +174,13 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
invoiceEntity.SetCryptoData(currencyData);
|
||||
invoice.Blob = ToBytes(invoiceEntity);
|
||||
invoice.Blob = ToBytes(invoiceEntity, network.NBitcoinNetwork);
|
||||
|
||||
context.AddressInvoices.Add(new AddressInvoiceData() {
|
||||
InvoiceDataId = invoiceId, CreatedTime = DateTimeOffset.UtcNow }
|
||||
context.AddressInvoices.Add(new AddressInvoiceData()
|
||||
{
|
||||
InvoiceDataId = invoiceId,
|
||||
CreatedTime = DateTimeOffset.UtcNow
|
||||
}
|
||||
.SetHash(bitcoinAddress.ScriptPubKey.Hash, network.CryptoCode));
|
||||
context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
|
||||
{
|
||||
@ -203,6 +194,21 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AddInvoiceEvent(string invoiceId, object evt)
|
||||
{
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
context.InvoiceEvents.Add(new InvoiceEventData()
|
||||
{
|
||||
InvoiceDataId = invoiceId,
|
||||
Message = evt.ToString(),
|
||||
Timestamp = DateTimeOffset.UtcNow,
|
||||
UniqueId = Encoders.Hex.EncodeData(RandomUtils.GetBytes(10))
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private static void MarkUnassigned(string invoiceId, InvoiceEntity entity, ApplicationDbContext context, string cryptoCode)
|
||||
{
|
||||
foreach (var address in entity.GetCryptoData(null))
|
||||
@ -211,7 +217,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
continue;
|
||||
var historical = new HistoricalAddressInvoiceData();
|
||||
historical.InvoiceDataId = invoiceId;
|
||||
historical.SetAddress(address.Value.DepositAddress, cryptoCode);
|
||||
historical.SetAddress(address.Value.DepositAddress, address.Value.CryptoCode);
|
||||
historical.UnAssigned = DateTimeOffset.UtcNow;
|
||||
context.Attach(historical);
|
||||
context.Entry(historical).Property(o => o.UnAssigned).IsModified = true;
|
||||
@ -225,7 +231,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||
if (invoiceData == null)
|
||||
return;
|
||||
var invoiceEntity = ToObject<InvoiceEntity>(invoiceData.Blob);
|
||||
var invoiceEntity = ToObject<InvoiceEntity>(invoiceData.Blob, null);
|
||||
MarkUnassigned(invoiceId, invoiceEntity, context, null);
|
||||
try
|
||||
{
|
||||
@ -308,11 +314,11 @@ namespace BTCPayServer.Services.Invoices
|
||||
|
||||
private InvoiceEntity ToEntity(InvoiceData invoice)
|
||||
{
|
||||
var entity = ToObject<InvoiceEntity>(invoice.Blob);
|
||||
var entity = ToObject<InvoiceEntity>(invoice.Blob, null);
|
||||
#pragma warning disable CS0618
|
||||
entity.Payments = invoice.Payments.Select(p =>
|
||||
{
|
||||
var paymentEntity = ToObject<PaymentEntity>(p.Blob);
|
||||
var paymentEntity = ToObject<PaymentEntity>(p.Blob, null);
|
||||
paymentEntity.Accounted = p.Accounted;
|
||||
return paymentEntity;
|
||||
}).ToList();
|
||||
@ -329,6 +335,10 @@ namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
entity.AvailableAddressHashes = invoice.AddressInvoices.Select(a => a.GetHash() + a.GetCryptoCode()).ToHashSet();
|
||||
}
|
||||
if(invoice.Events != null)
|
||||
{
|
||||
entity.Events = invoice.Events.OrderBy(c => c.Timestamp).ToList();
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
@ -341,8 +351,10 @@ namespace BTCPayServer.Services.Invoices
|
||||
.Invoices
|
||||
.Include(o => o.Payments)
|
||||
.Include(o => o.RefundAddresses);
|
||||
if(queryObject.IncludeAddresses)
|
||||
if (queryObject.IncludeAddresses)
|
||||
query = query.Include(o => o.HistoricalAddressInvoices).Include(o => o.AddressInvoices);
|
||||
if (queryObject.IncludeEvents)
|
||||
query = query.Include(o => o.Events);
|
||||
if (!string.IsNullOrEmpty(queryObject.InvoiceId))
|
||||
{
|
||||
query = query.Where(i => i.Id == queryObject.InvoiceId);
|
||||
@ -396,7 +408,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
|
||||
}
|
||||
|
||||
public async Task AddRefundsAsync(string invoiceId, TxOut[] outputs)
|
||||
public async Task AddRefundsAsync(string invoiceId, TxOut[] outputs, Network network)
|
||||
{
|
||||
if (outputs.Length == 0)
|
||||
return;
|
||||
@ -410,14 +422,14 @@ namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
Id = invoiceId + "-" + i,
|
||||
InvoiceDataId = invoiceId,
|
||||
Blob = ToBytes(output)
|
||||
Blob = ToBytes(output, network)
|
||||
});
|
||||
i++;
|
||||
}
|
||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var addresses = outputs.Select(o => o.ScriptPubKey.GetDestinationAddress(_Network)).Where(a => a != null).ToArray();
|
||||
var addresses = outputs.Select(o => o.ScriptPubKey.GetDestinationAddress(network)).Where(a => a != null).ToArray();
|
||||
AddToTextSearch(invoiceId, addresses.Select(a => a.ToString()).ToArray());
|
||||
}
|
||||
|
||||
@ -438,7 +450,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
PaymentData data = new PaymentData
|
||||
{
|
||||
Id = receivedCoin.Outpoint.ToString(),
|
||||
Blob = ToBytes(entity),
|
||||
Blob = ToBytes(entity, null),
|
||||
InvoiceDataId = invoiceId
|
||||
};
|
||||
|
||||
@ -468,24 +480,24 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
}
|
||||
|
||||
private T ToObject<T>(byte[] value)
|
||||
private T ToObject<T>(byte[] value, Network network)
|
||||
{
|
||||
return NBitcoin.JsonConverters.Serializer.ToObject<T>(ZipUtils.Unzip(value), Network);
|
||||
return NBitcoin.JsonConverters.Serializer.ToObject<T>(ZipUtils.Unzip(value), network);
|
||||
}
|
||||
|
||||
private byte[] ToBytes<T>(T obj)
|
||||
private byte[] ToBytes<T>(T obj, Network network)
|
||||
{
|
||||
return ZipUtils.Zip(NBitcoin.JsonConverters.Serializer.ToString(obj));
|
||||
return ZipUtils.Zip(NBitcoin.JsonConverters.Serializer.ToString(obj, network));
|
||||
}
|
||||
|
||||
private T Clone<T>(T invoice)
|
||||
private T Clone<T>(T invoice, Network network)
|
||||
{
|
||||
return NBitcoin.JsonConverters.Serializer.ToObject<T>(ToString(invoice), Network);
|
||||
return NBitcoin.JsonConverters.Serializer.ToObject<T>(ToString(invoice, network), network);
|
||||
}
|
||||
|
||||
private string ToString<T>(T data)
|
||||
private string ToString<T>(T data, Network network)
|
||||
{
|
||||
return NBitcoin.JsonConverters.Serializer.ToString(data, Network);
|
||||
return NBitcoin.JsonConverters.Serializer.ToString(data, network);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -551,5 +563,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
set;
|
||||
}
|
||||
public bool IncludeAddresses { get; set; }
|
||||
|
||||
public bool IncludeEvents { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
using Hangfire;
|
||||
using BTCPayServer.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Hangfire;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -20,10 +22,16 @@ namespace BTCPayServer.Services.Mails
|
||||
_JobClient = jobClient;
|
||||
_Repository = repository;
|
||||
}
|
||||
public Task SendEmailAsync(string email, string subject, string message)
|
||||
public async Task SendEmailAsync(string email, string subject, string message)
|
||||
{
|
||||
var settings = await _Repository.GetSettingAsync<EmailSettings>();
|
||||
if (settings == null)
|
||||
{
|
||||
Logs.Configuration.LogWarning("Should have sent email, but email settings are not configured");
|
||||
return;
|
||||
}
|
||||
_JobClient.Schedule(() => SendMailCore(email, subject, message), TimeSpan.Zero);
|
||||
return Task.CompletedTask;
|
||||
return;
|
||||
}
|
||||
|
||||
public async Task SendMailCore(string email, string subject, string message)
|
||||
|
@ -11,6 +11,16 @@ namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
IMemoryCache _Cache;
|
||||
ConcurrentDictionary<string, IRateProvider> _Providers = new ConcurrentDictionary<string, IRateProvider>();
|
||||
ConcurrentDictionary<string, IRateProvider> _LongCacheProviders = new ConcurrentDictionary<string, IRateProvider>();
|
||||
|
||||
public IMemoryCache Cache
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Cache;
|
||||
}
|
||||
}
|
||||
|
||||
public CachedDefaultRateProviderFactory(IMemoryCache cache)
|
||||
{
|
||||
if (cache == null)
|
||||
@ -18,10 +28,13 @@ namespace BTCPayServer.Services.Rates
|
||||
_Cache = cache;
|
||||
}
|
||||
|
||||
public IRateProvider RateProvider { get; set; }
|
||||
|
||||
public TimeSpan CacheSpan { get; set; } = TimeSpan.FromMinutes(1.0);
|
||||
public IRateProvider GetRateProvider(BTCPayNetwork network)
|
||||
public TimeSpan LongCacheSpan { get; set; } = TimeSpan.FromMinutes(15.0);
|
||||
public IRateProvider GetRateProvider(BTCPayNetwork network, bool longCache)
|
||||
{
|
||||
return _Providers.GetOrAdd(network.CryptoCode, new CachedRateProvider(network.CryptoCode, network.DefaultRateProvider, _Cache) { CacheSpan = CacheSpan });
|
||||
return (longCache ? _LongCacheProviders : _Providers).GetOrAdd(network.CryptoCode, new CachedRateProvider(network.CryptoCode, RateProvider ?? network.DefaultRateProvider, _Cache) { CacheSpan = longCache ? LongCacheSpan : CacheSpan, AdditionalScope = longCache ? "LONG" : "SHORT" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,19 +19,28 @@ namespace BTCPayServer.Services.Rates
|
||||
if (memoryCache == null)
|
||||
throw new ArgumentNullException(nameof(memoryCache));
|
||||
this._Inner = inner;
|
||||
this._MemoryCache = memoryCache;
|
||||
this.MemoryCache = memoryCache;
|
||||
this._CryptoCode = cryptoCode;
|
||||
}
|
||||
|
||||
public IRateProvider Inner
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Inner;
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan CacheSpan
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = TimeSpan.FromMinutes(1.0);
|
||||
public IMemoryCache MemoryCache { get => _MemoryCache; private set => _MemoryCache = value; }
|
||||
|
||||
public Task<decimal> GetRateAsync(string currency)
|
||||
{
|
||||
return _MemoryCache.GetOrCreateAsync("CURR_" + currency + "_" + _CryptoCode, (ICacheEntry entry) =>
|
||||
return MemoryCache.GetOrCreateAsync("CURR_" + currency + "_" + _CryptoCode + "_" + AdditionalScope, (ICacheEntry entry) =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.UtcNow + CacheSpan;
|
||||
return _Inner.GetRateAsync(currency);
|
||||
@ -40,11 +49,13 @@ namespace BTCPayServer.Services.Rates
|
||||
|
||||
public Task<ICollection<Rate>> GetRatesAsync()
|
||||
{
|
||||
return _MemoryCache.GetOrCreateAsync("GLOBAL_RATES", (ICacheEntry entry) =>
|
||||
return MemoryCache.GetOrCreateAsync("GLOBAL_RATES_" + _CryptoCode + "_" + AdditionalScope, (ICacheEntry entry) =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.UtcNow + CacheSpan;
|
||||
return _Inner.GetRatesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public string AdditionalScope { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
@ -24,6 +25,8 @@ namespace BTCPayServer.Services.Rates
|
||||
CryptoCode = cryptoCode ?? "BTC";
|
||||
}
|
||||
|
||||
public string Exchange { get; set; }
|
||||
|
||||
public string CryptoCode { get; set; }
|
||||
|
||||
public string Market
|
||||
@ -45,7 +48,15 @@ namespace BTCPayServer.Services.Rates
|
||||
|
||||
private async Task<Dictionary<string, decimal>> GetRatesCore()
|
||||
{
|
||||
var resp = await _Client.GetAsync($"https://apiv2.bitcoinaverage.com/indices/{Market}/ticker/short");
|
||||
HttpResponseMessage resp = null;
|
||||
if (Exchange == null)
|
||||
{
|
||||
resp = await _Client.GetAsync($"https://apiv2.bitcoinaverage.com/indices/{Market}/ticker/short");
|
||||
}
|
||||
else
|
||||
{
|
||||
resp = await _Client.GetAsync($"https://apiv2.bitcoinaverage.com/exchanges/{Exchange}");
|
||||
}
|
||||
using (resp)
|
||||
{
|
||||
|
||||
@ -57,15 +68,24 @@ namespace BTCPayServer.Services.Rates
|
||||
throw new CoinAverageException("Unauthorized access to the API, premium plan needed");
|
||||
resp.EnsureSuccessStatusCode();
|
||||
var rates = JObject.Parse(await resp.Content.ReadAsStringAsync());
|
||||
if(Exchange != null)
|
||||
{
|
||||
rates = (JObject)rates["symbols"];
|
||||
}
|
||||
return rates.Properties()
|
||||
.Where(p => p.Name.StartsWith(CryptoCode, StringComparison.OrdinalIgnoreCase))
|
||||
.ToDictionary(p => p.Name.Substring(CryptoCode.Length, p.Name.Length - CryptoCode.Length), p => ToDecimal(p.Value["last"]));
|
||||
.Where(p => p.Name.StartsWith(CryptoCode, StringComparison.OrdinalIgnoreCase) && TryToDecimal(p, out decimal unused))
|
||||
.ToDictionary(p => p.Name.Substring(CryptoCode.Length, p.Name.Length - CryptoCode.Length), p =>
|
||||
{
|
||||
TryToDecimal(p, out decimal v);
|
||||
return v;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private decimal ToDecimal(JToken token)
|
||||
private bool TryToDecimal(JProperty p, out decimal v)
|
||||
{
|
||||
return decimal.Parse(token.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint);
|
||||
JToken token = p.Value[Exchange == null ? "last" : "bid"];
|
||||
return decimal.TryParse(token.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out v);
|
||||
}
|
||||
|
||||
public async Task<ICollection<Rate>> GetRatesAsync()
|
||||
|
@ -7,6 +7,6 @@ namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
public interface IRateProviderFactory
|
||||
{
|
||||
IRateProvider GetRateProvider(BTCPayNetwork network);
|
||||
IRateProvider GetRateProvider(BTCPayNetwork network, bool longCache);
|
||||
}
|
||||
}
|
||||
|
@ -6,17 +6,38 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
public class MockRateProviderFactory : IRateProviderFactory
|
||||
{
|
||||
List<MockRateProvider> _Mocks = new List<MockRateProvider>();
|
||||
public MockRateProviderFactory()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void AddMock(MockRateProvider mock)
|
||||
{
|
||||
_Mocks.Add(mock);
|
||||
}
|
||||
public IRateProvider GetRateProvider(BTCPayNetwork network, bool longCache)
|
||||
{
|
||||
return _Mocks.FirstOrDefault(m => m.CryptoCode == network.CryptoCode);
|
||||
}
|
||||
}
|
||||
public class MockRateProvider : IRateProvider
|
||||
{
|
||||
List<Rate> _Rates;
|
||||
|
||||
public MockRateProvider(params Rate[] rates)
|
||||
public string CryptoCode { get; }
|
||||
|
||||
public MockRateProvider(string cryptoCode, params Rate[] rates)
|
||||
{
|
||||
_Rates = new List<Rate>(rates);
|
||||
CryptoCode = cryptoCode;
|
||||
}
|
||||
public MockRateProvider(List<Rate> rates)
|
||||
public MockRateProvider(string cryptoCode, List<Rate> rates)
|
||||
{
|
||||
_Rates = rates;
|
||||
CryptoCode = cryptoCode;
|
||||
}
|
||||
public Task<decimal> GetRateAsync(string currency)
|
||||
{
|
||||
|
53
BTCPayServer/Services/Rates/TweakRateProvider.cs
Normal file
53
BTCPayServer/Services/Rates/TweakRateProvider.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
|
||||
namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
public class TweakRateProvider : IRateProvider
|
||||
{
|
||||
private BTCPayNetwork network;
|
||||
private IRateProvider rateProvider;
|
||||
private List<RateRule> rateRules;
|
||||
|
||||
public TweakRateProvider(BTCPayNetwork network, IRateProvider rateProvider, List<RateRule> rateRules)
|
||||
{
|
||||
if (network == null)
|
||||
throw new ArgumentNullException(nameof(network));
|
||||
if (rateProvider == null)
|
||||
throw new ArgumentNullException(nameof(rateProvider));
|
||||
if (rateRules == null)
|
||||
throw new ArgumentNullException(nameof(rateRules));
|
||||
this.network = network;
|
||||
this.rateProvider = rateProvider;
|
||||
this.rateRules = rateRules;
|
||||
}
|
||||
|
||||
public async Task<decimal> GetRateAsync(string currency)
|
||||
{
|
||||
var rate = await rateProvider.GetRateAsync(currency);
|
||||
foreach(var rule in rateRules)
|
||||
{
|
||||
rate = rule.Apply(network, rate);
|
||||
}
|
||||
return rate;
|
||||
}
|
||||
|
||||
public async Task<ICollection<Rate>> GetRatesAsync()
|
||||
{
|
||||
List<Rate> rates = new List<Rate>();
|
||||
foreach (var rate in await rateProvider.GetRatesAsync())
|
||||
{
|
||||
var localRate = rate.Value;
|
||||
foreach (var rule in rateRules)
|
||||
{
|
||||
localRate = rule.Apply(network, localRate);
|
||||
}
|
||||
rates.Add(new Rate(rate.Currency, localRate));
|
||||
}
|
||||
return rates;
|
||||
}
|
||||
}
|
||||
}
|
@ -68,7 +68,8 @@ namespace BTCPayServer.Services.Stores
|
||||
StoreData store = new StoreData
|
||||
{
|
||||
Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(32)),
|
||||
StoreName = name
|
||||
StoreName = name,
|
||||
SpeedPolicy = Invoices.SpeedPolicy.MediumSpeed
|
||||
};
|
||||
var userStore = new UserStore
|
||||
{
|
||||
|
96
BTCPayServer/Services/TransactionCache.cs
Normal file
96
BTCPayServer/Services/TransactionCache.cs
Normal file
@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBXplorer.Models;
|
||||
|
||||
namespace BTCPayServer.Services
|
||||
{
|
||||
public class TransactionCacheProvider
|
||||
{
|
||||
IOptions<MemoryCacheOptions> _Options;
|
||||
public TransactionCacheProvider(IOptions<MemoryCacheOptions> options)
|
||||
{
|
||||
_Options = options;
|
||||
}
|
||||
|
||||
ConcurrentDictionary<string, TransactionCache> _TransactionCaches = new ConcurrentDictionary<string, TransactionCache>();
|
||||
public TransactionCache GetTransactionCache(BTCPayNetwork network)
|
||||
{
|
||||
if (network == null)
|
||||
throw new ArgumentNullException(nameof(network));
|
||||
return _TransactionCaches.GetOrAdd(network.CryptoCode, c => new TransactionCache(_Options, network));
|
||||
}
|
||||
}
|
||||
public class TransactionCache : IDisposable
|
||||
{
|
||||
//IOptions<MemoryCacheOptions> _Options;
|
||||
public TransactionCache(IOptions<MemoryCacheOptions> options, BTCPayNetwork network)
|
||||
{
|
||||
//if (network == null)
|
||||
// throw new ArgumentNullException(nameof(network));
|
||||
//_Options = options;
|
||||
//_MemoryCache = new MemoryCache(_Options);
|
||||
//Network = network;
|
||||
}
|
||||
|
||||
//uint256 _LastHash;
|
||||
//int _ConfOffset;
|
||||
//IMemoryCache _MemoryCache;
|
||||
|
||||
public void NewBlock(uint256 newHash, uint256 previousHash)
|
||||
{
|
||||
//if (_LastHash != previousHash)
|
||||
//{
|
||||
// var old = _MemoryCache;
|
||||
// _ConfOffset = 0;
|
||||
// _MemoryCache = new MemoryCache(_Options);
|
||||
// Thread.MemoryBarrier();
|
||||
// old.Dispose();
|
||||
//}
|
||||
//else
|
||||
// _ConfOffset++;
|
||||
//_LastHash = newHash;
|
||||
}
|
||||
|
||||
public TimeSpan CacheSpan { get; private set; } = TimeSpan.FromMinutes(60);
|
||||
|
||||
public BTCPayNetwork Network { get; private set; }
|
||||
|
||||
public void AddToCache(TransactionResult tx)
|
||||
{
|
||||
//Logging.Logs.PayServer.LogInformation($"ADD CACHE: {tx.Transaction.GetHash()} ({tx.Confirmations} conf)");
|
||||
//_MemoryCache.Set(tx.Transaction.GetHash(), tx, DateTimeOffset.UtcNow + CacheSpan);
|
||||
}
|
||||
|
||||
|
||||
public TransactionResult GetTransaction(uint256 txId)
|
||||
{
|
||||
//var ok = _MemoryCache.TryGetValue(txId.ToString(), out object tx);
|
||||
//Logging.Logs.PayServer.LogInformation($"GET CACHE: {txId} ({ok} plus {_ConfOffset})");
|
||||
|
||||
//var result = tx as TransactionResult;
|
||||
//var confOffset = _ConfOffset;
|
||||
//if (result != null && result.Confirmations > 0 && confOffset > 0)
|
||||
//{
|
||||
// var serializer = new NBXplorer.Serializer(Network.NBitcoinNetwork);
|
||||
// result = serializer.ToObject<TransactionResult>(serializer.ToString(result));
|
||||
// result.Confirmations += confOffset;
|
||||
// result.Height += confOffset;
|
||||
//}
|
||||
//return result;
|
||||
return null; // Does not work correctly yet
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
//_MemoryCache.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -15,8 +15,7 @@ namespace BTCPayServer.Services.Wallets
|
||||
{
|
||||
public class KnownState
|
||||
{
|
||||
public uint256 UnconfirmedHash { get; set; }
|
||||
public uint256 ConfirmedHash { get; set; }
|
||||
public UTXOChanges PreviousCall { get; set; }
|
||||
}
|
||||
public class NetworkCoins
|
||||
{
|
||||
@ -33,13 +32,14 @@ namespace BTCPayServer.Services.Wallets
|
||||
public class BTCPayWallet
|
||||
{
|
||||
private ExplorerClient _Client;
|
||||
|
||||
public BTCPayWallet(ExplorerClient client, BTCPayNetwork network)
|
||||
private TransactionCache _Cache;
|
||||
public BTCPayWallet(ExplorerClient client, TransactionCache cache, BTCPayNetwork network)
|
||||
{
|
||||
if (client == null)
|
||||
throw new ArgumentNullException(nameof(client));
|
||||
_Client = client;
|
||||
_Network = network;
|
||||
_Cache = cache;
|
||||
}
|
||||
|
||||
|
||||
@ -56,8 +56,16 @@ namespace BTCPayServer.Services.Wallets
|
||||
|
||||
public async Task<BitcoinAddress> ReserveAddressAsync(DerivationStrategyBase derivationStrategy)
|
||||
{
|
||||
if (derivationStrategy == null)
|
||||
throw new ArgumentNullException(nameof(derivationStrategy));
|
||||
var pathInfo = await _Client.GetUnusedAsync(derivationStrategy, DerivationFeature.Deposit, 0, true).ConfigureAwait(false);
|
||||
return pathInfo.ScriptPubKey.GetDestinationAddress(_Client.Network);
|
||||
// Might happen on some broken install
|
||||
if (pathInfo == null)
|
||||
{
|
||||
await _Client.TrackAsync(derivationStrategy).ConfigureAwait(false);
|
||||
pathInfo = await _Client.GetUnusedAsync(derivationStrategy, DerivationFeature.Deposit, 0, true).ConfigureAwait(false);
|
||||
}
|
||||
return pathInfo.ScriptPubKey.GetDestinationAddress(Network.NBitcoinNetwork);
|
||||
}
|
||||
|
||||
public async Task TrackAsync(DerivationStrategyBase derivationStrategy)
|
||||
@ -65,20 +73,25 @@ namespace BTCPayServer.Services.Wallets
|
||||
await _Client.TrackAsync(derivationStrategy);
|
||||
}
|
||||
|
||||
public Task<TransactionResult> GetTransactionAsync(uint256 txId, CancellationToken cancellation = default(CancellationToken))
|
||||
public async Task<TransactionResult> GetTransactionAsync(uint256 txId, CancellationToken cancellation = default(CancellationToken))
|
||||
{
|
||||
if (txId == null)
|
||||
throw new ArgumentNullException(nameof(txId));
|
||||
return _Client.GetTransactionAsync(txId, cancellation);
|
||||
var tx = _Cache.GetTransaction(txId);
|
||||
if (tx != null)
|
||||
return tx;
|
||||
tx = await _Client.GetTransactionAsync(txId, cancellation);
|
||||
_Cache.AddToCache(tx);
|
||||
return tx;
|
||||
}
|
||||
|
||||
public async Task<NetworkCoins> GetCoins(DerivationStrategyBase strategy, KnownState state, CancellationToken cancellation = default(CancellationToken))
|
||||
{
|
||||
var changes = await _Client.SyncAsync(strategy, state?.ConfirmedHash, state?.UnconfirmedHash, true, cancellation).ConfigureAwait(false);
|
||||
var changes = await _Client.GetUTXOsAsync(strategy, state?.PreviousCall, false, cancellation).ConfigureAwait(false);
|
||||
return new NetworkCoins()
|
||||
{
|
||||
TimestampedCoins = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).Select(c => new NetworkCoins.TimestampedCoin() { Coin = c.AsCoin(), DateTime = c.Timestamp }).ToArray(),
|
||||
State = new KnownState() { ConfirmedHash = changes.Confirmed.Hash, UnconfirmedHash = changes.Unconfirmed.Hash },
|
||||
State = new KnownState() { PreviousCall = changes },
|
||||
Strategy = strategy,
|
||||
Wallet = this
|
||||
};
|
||||
@ -93,10 +106,18 @@ namespace BTCPayServer.Services.Wallets
|
||||
|
||||
public async Task<Money> GetBalance(DerivationStrategyBase derivationStrategy)
|
||||
{
|
||||
var result = await _Client.SyncAsync(derivationStrategy, null, true);
|
||||
return result.Confirmed.UTXOs.Select(u => u.Value)
|
||||
.Concat(result.Unconfirmed.UTXOs.Select(u => u.Value))
|
||||
.Sum();
|
||||
var result = await _Client.GetUTXOsAsync(derivationStrategy, null, true);
|
||||
|
||||
Dictionary<OutPoint, UTXO> received = new Dictionary<OutPoint, UTXO>();
|
||||
foreach(var utxo in result.Confirmed.UTXOs.Concat(result.Unconfirmed.UTXOs))
|
||||
{
|
||||
received.TryAdd(utxo.Outpoint, utxo);
|
||||
}
|
||||
foreach (var utxo in result.Confirmed.SpentOutpoints.Concat(result.Unconfirmed.SpentOutpoints))
|
||||
{
|
||||
received.Remove(utxo);
|
||||
}
|
||||
return received.Values.Select(c => c.Value).Sum();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,11 +10,15 @@ namespace BTCPayServer.Services.Wallets
|
||||
{
|
||||
private ExplorerClientProvider _Client;
|
||||
BTCPayNetworkProvider _NetworkProvider;
|
||||
public BTCPayWalletProvider(ExplorerClientProvider client, BTCPayNetworkProvider networkProvider)
|
||||
TransactionCacheProvider _TransactionCacheProvider;
|
||||
public BTCPayWalletProvider(ExplorerClientProvider client,
|
||||
TransactionCacheProvider transactionCacheProvider,
|
||||
BTCPayNetworkProvider networkProvider)
|
||||
{
|
||||
if (client == null)
|
||||
throw new ArgumentNullException(nameof(client));
|
||||
_Client = client;
|
||||
_TransactionCacheProvider = transactionCacheProvider;
|
||||
_NetworkProvider = networkProvider;
|
||||
}
|
||||
|
||||
@ -32,7 +36,12 @@ namespace BTCPayServer.Services.Wallets
|
||||
var client = _Client.GetExplorerClient(cryptoCode);
|
||||
if (network == null || client == null)
|
||||
return null;
|
||||
return new BTCPayWallet(client, network);
|
||||
return new BTCPayWallet(client, _TransactionCacheProvider.GetTransactionCache(network), network);
|
||||
}
|
||||
|
||||
public bool IsAvailable(BTCPayNetwork network)
|
||||
{
|
||||
return _Client.IsAvailable(network);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,10 +57,10 @@
|
||||
<h2>Video tutorials</h2>
|
||||
<div class="row">
|
||||
<div class="col-md-6 text-center">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/xh3Eac66qc4" frameborder="0" allowfullscreen></iframe>
|
||||
<iframe width="400" height="225" src="https://www.youtube.com/embed/xh3Eac66qc4" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
<div class="col-md-6 text-center">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/Xo_vApXTZBU" frameborder="0" allowfullscreen></iframe>
|
||||
<iframe width="400" height="225" src="https://www.youtube.com/embed/Xo_vApXTZBU" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -21,7 +21,7 @@
|
||||
@Model.ToJSVariableModel("srvModel")
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/1.7.1/clipboard.min.js"></script>
|
||||
<script src="~/js/vue.js" type="text/javascript" defer="defer"></script>
|
||||
<script src="~/js/vue.min.js" type="text/javascript" defer="defer"></script>
|
||||
<script src="~/js/vue-qrcode.js" type="text/javascript" defer="defer"></script>
|
||||
<script src="~/js/core.js" type="text/javascript" defer="defer"></script>
|
||||
<!-- <script src="img/Intl.js" type="text/javascript" defer="defer"></script>
|
||||
@ -492,7 +492,7 @@
|
||||
<div>
|
||||
<div class="expired__body">
|
||||
<div class="expired__header" i18n="">What happened?</div>
|
||||
<div class="expired__text" i18n="">This invoice has expired. An invoice is only valid for 15 minutes. You can <div class="expired__text__link i18n-return-to-merchant">return to {{srvModel.storeName}}</div> if you would like to submit your payment again.</div>
|
||||
<div class="expired__text" i18n="">This invoice has expired. An invoice is only valid for @Model.MaxTimeMinutes minutes. You can <div class="expired__text__link i18n-return-to-merchant">return to {{srvModel.storeName}}</div> if you would like to submit your payment again.</div>
|
||||
<div class="expired__text" i18n="">If you tried to send a payment, it has not yet been accepted by the Bitcoin network. We have not yet received your funds.</div>
|
||||
<div class="expired__text" i18n="">
|
||||
If the transaction
|
||||
@ -504,7 +504,7 @@
|
||||
<span class="expired__text__bullet" i18n="">Invoice ID:</span> {{srvModel.invoiceId}}<br>
|
||||
<!---->
|
||||
<span>
|
||||
<span class="expired__text__bullet" i18n="">Order ID:</span> {{srvModel.OrderId}}
|
||||
<span class="expired__text__bullet" i18n="">Order ID:</span> {{srvModel.orderId}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -53,6 +53,10 @@
|
||||
<th>Monitoring date</th>
|
||||
<td>@Model.MonitoringDate</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Transaction speed</th>
|
||||
<td>@Model.TransactionSpeed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<td>@Model.Status</td>
|
||||
@ -77,6 +81,10 @@
|
||||
<th>Notification Url</th>
|
||||
<td>@Model.NotificationUrl</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Redirect Url</th>
|
||||
<td>@Model.RedirectUrl</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@ -221,5 +229,28 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Events</h3>
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(var evt in Model.Events)
|
||||
{
|
||||
<tr>
|
||||
<td>@evt.Timestamp</td>
|
||||
<td>@evt.Message</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
94
BTCPayServer/Views/Shared/SyncModal.cshtml
Normal file
94
BTCPayServer/Views/Shared/SyncModal.cshtml
Normal file
@ -0,0 +1,94 @@
|
||||
@inject BTCPayServer.HostedServices.NBXplorerDashboard dashboard
|
||||
<!-- Modal -->
|
||||
<div id="modalDialog" class="modal-dialog animated bounceInRight">
|
||||
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Your nodes are synching...</h4>
|
||||
<button type="button" class="close" onclick="dismissSyncModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Some of your nodes are still synching...<br />
|
||||
BTCPay Server will not work correctly until it is over.
|
||||
</p>
|
||||
@foreach (var line in dashboard.GetAll())
|
||||
{
|
||||
<h4>@line.Network.CryptoCode</h4>
|
||||
@if (line.Status == null)
|
||||
{
|
||||
<ul>
|
||||
<li>The node is offline</li>
|
||||
@if (line.Error != null)
|
||||
{
|
||||
<li>Last error: @line.Error</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ul>
|
||||
<li>NBXplorer headers height: @line.Status.ChainHeight</li>
|
||||
@if (line.Status.BitcoinStatus == null)
|
||||
{
|
||||
if (line.State == BTCPayServer.HostedServices.NBXplorerState.Synching)
|
||||
{
|
||||
<li>The node is starting...</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li>The node is offline</li>
|
||||
@if (line.Error != null)
|
||||
{
|
||||
<li>Last error: @line.Error</li>
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (line.Status.BitcoinStatus.IsSynched)
|
||||
{
|
||||
<li>The node is synched (Height: @line.Status.BitcoinStatus.Headers)</li>
|
||||
@if (line.Status.BitcoinStatus.IsSynched &&
|
||||
line.Status.SyncHeight.HasValue &&
|
||||
line.Status.SyncHeight.Value < line.Status.BitcoinStatus.Headers)
|
||||
{
|
||||
<li>NBXplorer is synching... (Height: @line.Status.SyncHeight.Value)</li>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<li>Node headers height: @line.Status.BitcoinStatus.Headers</li>
|
||||
<li>Validated blocks: @line.Status.BitcoinStatus.Blocks</li>
|
||||
}
|
||||
</ul>
|
||||
@if (!line.Status.IsFullySynched && line.Status.BitcoinStatus != null)
|
||||
{
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="@((int)(line.Status.BitcoinStatus.VerificationProgress * 100))"
|
||||
aria-valuemin="0" aria-valuemax="100" style="width:@((int)(line.Status.BitcoinStatus.VerificationProgress * 100))%">
|
||||
@((int)(line.Status.BitcoinStatus.VerificationProgress * 100))%
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<link href="~/vendor/animatecss/animate.css" rel="stylesheet" />
|
||||
<script type="text/javascript">
|
||||
function dismissSyncModal() {
|
||||
$("#modalDialog").addClass('animated bounceOutRight')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
#modalDialog {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
margin: 0px;
|
||||
}
|
||||
</style>
|
@ -38,7 +38,7 @@
|
||||
<body id="page-top">
|
||||
|
||||
@{
|
||||
if(ViewBag.AlwaysShrinkNavBar == null)
|
||||
if (ViewBag.AlwaysShrinkNavBar == null)
|
||||
{
|
||||
ViewBag.AlwaysShrinkNavBar = true;
|
||||
}
|
||||
@ -48,30 +48,36 @@
|
||||
<!-- Navigation -->
|
||||
<nav class='navbar navbar-expand-lg navbar-light fixed-top @additionalStyle' id="mainNav">
|
||||
<div class="container">
|
||||
<a class="navbar-brand js-scroll-trigger" href="~/"><img src="~/img/logo.png" height="45"></a>
|
||||
<a class="navbar-brand js-scroll-trigger" href="~/">
|
||||
<img src="~/img/logo.png" height="45">
|
||||
@if(env.ChainType != NBXplorer.ChainType.Main)
|
||||
{
|
||||
<span class="badge badge-warning" style="font-size:10px;">@env.ChainType.ToString()</span>
|
||||
}
|
||||
</a>
|
||||
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
@if(SignInManager.IsSignedIn(User))
|
||||
{
|
||||
@if(User.IsInRole(Roles.ServerAdmin))
|
||||
{
|
||||
<li class="nav-item"><a asp-area="" asp-controller="Server" asp-action="ListUsers" class="nav-link js-scroll-trigger">Server settings</a></li>
|
||||
}
|
||||
<li class="nav-item"><a asp-area="" asp-controller="Stores" asp-action="ListStores" class="nav-link js-scroll-trigger">Stores</a></li>
|
||||
<li class="nav-item"><a asp-area="" asp-controller="Invoice" asp-action="ListInvoices" class="nav-link js-scroll-trigger">Invoices</a></li>
|
||||
<li class="nav-item">
|
||||
<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage" class="nav-link js-scroll-trigger">My settings</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a asp-area="" asp-controller="Account" asp- asp-action="Logout" title="Manage" class="nav-link js-scroll-trigger">Log out</a>
|
||||
</li>}
|
||||
else
|
||||
{
|
||||
<li class="nav-item"><a asp-area="" asp-controller="Account" asp-action="Register" class="nav-link js-scroll-trigger">Register</a></li>
|
||||
<li class="nav-item"><a asp-area="" asp-controller="Account" asp-action="Login" class="nav-link js-scroll-trigger">Log in</a></li>}
|
||||
{
|
||||
@if(User.IsInRole(Roles.ServerAdmin))
|
||||
{
|
||||
<li class="nav-item"><a asp-area="" asp-controller="Server" asp-action="ListUsers" class="nav-link js-scroll-trigger">Server settings</a></li>
|
||||
}
|
||||
<li class="nav-item"><a asp-area="" asp-controller="Stores" asp-action="ListStores" class="nav-link js-scroll-trigger">Stores</a></li>
|
||||
<li class="nav-item"><a asp-area="" asp-controller="Invoice" asp-action="ListInvoices" class="nav-link js-scroll-trigger">Invoices</a></li>
|
||||
<li class="nav-item">
|
||||
<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage" class="nav-link js-scroll-trigger">My settings</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a asp-area="" asp-controller="Account" asp- asp-action="Logout" title="Manage" class="nav-link js-scroll-trigger">Log out</a>
|
||||
</li>}
|
||||
else
|
||||
{
|
||||
<li class="nav-item"><a asp-area="" asp-controller="Account" asp-action="Register" class="nav-link js-scroll-trigger">Register</a></li>
|
||||
<li class="nav-item"><a asp-area="" asp-controller="Account" asp-action="Login" class="nav-link js-scroll-trigger">Log in</a></li>}
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
@ -80,77 +86,6 @@
|
||||
|
||||
@RenderBody()
|
||||
|
||||
|
||||
@if(!dashboard.IsFullySynched())
|
||||
{
|
||||
<!-- Modal -->
|
||||
<div id="synching-modal" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Your nodes are synching...</h4>
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Some of your nodes are still synching...<br />
|
||||
BTCPay Server will not work correctly until it is over.
|
||||
</p>
|
||||
@foreach(var line in dashboard.GetAll())
|
||||
{
|
||||
<h4>@line.Network.CryptoCode</h4>
|
||||
@if(line.Status == null)
|
||||
{
|
||||
<p>NBXplorer is offline</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ul>
|
||||
<li>NBXplorer headers height: @line.Status.ChainHeight</li>
|
||||
@if(line.Status.BitcoinStatus == null)
|
||||
{
|
||||
if(line.State == BTCPayServer.HostedServices.NBXplorerState.Synching)
|
||||
{
|
||||
<li>The node is starting...</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li>The node is offline</li>
|
||||
}
|
||||
}
|
||||
else if(line.Status.BitcoinStatus.IsSynched)
|
||||
{
|
||||
<li>The node is synched</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li>Node headers height: @line.Status.BitcoinStatus.Headers</li>
|
||||
<li>Validated blocks: @line.Status.BitcoinStatus.Blocks</li>
|
||||
}
|
||||
</ul>
|
||||
@if(!line.Status.IsFullySynched && line.Status.BitcoinStatus != null)
|
||||
{
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="@((int)(line.Status.BitcoinStatus.VerificationProgress * 100))"
|
||||
aria-valuemin="0" aria-valuemax="100" style="width:@((int)(line.Status.BitcoinStatus.VerificationProgress * 100))%">
|
||||
@((int)(line.Status.BitcoinStatus.VerificationProgress * 100))%
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<footer class="bg-dark">
|
||||
<div class="container text-right"><span style="font-size:8px;">@env.ToString()</span></div>
|
||||
</footer>
|
||||
@ -168,13 +103,10 @@
|
||||
<!-- Custom scripts for this template -->
|
||||
<script src="~/js/creative.js"></script>
|
||||
|
||||
@if(!dashboard.IsFullySynched())
|
||||
|
||||
@if (!dashboard.IsFullySynched())
|
||||
{
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
$("#synching-modal").modal();
|
||||
});
|
||||
</script>
|
||||
@Html.Partial("SyncModal")
|
||||
}
|
||||
|
||||
@RenderSection("Scripts", required: false)
|
||||
|
@ -15,29 +15,26 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<h5>Derivation Scheme</h5>
|
||||
@if(Model.AddressSamples.Count == 0)
|
||||
{
|
||||
@if(!Model.Confirmation)
|
||||
{
|
||||
<div class="form-group">
|
||||
<h5>Derivation Scheme</h5>
|
||||
<span>The DerivationScheme represents the destination of the funds received by your invoice. It is generated by your wallet software. Please, verify that you are generating the right addresses by clicking on 'Check ExtPubKey'</span>
|
||||
}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="CryptoCurrency"></label>
|
||||
<select asp-for="CryptoCurrency" asp-items="Model.CryptoCurrencies" class="form-control"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="CryptoCurrency"></label>
|
||||
<select asp-for="CryptoCurrency" asp-items="Model.CryptoCurrencies" class="form-control"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<input asp-for="DerivationScheme" class="form-control" />
|
||||
<span asp-validation-for="DerivationScheme" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="DerivationSchemeFormat"></label>
|
||||
<select asp-for="DerivationSchemeFormat" asp-items="Model.DerivationSchemeFormats" class="form-control"></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@if(Model.AddressSamples.Count == 0)
|
||||
{
|
||||
<div class="form-group">
|
||||
<input asp-for="DerivationScheme" class="form-control" />
|
||||
<span asp-validation-for="DerivationScheme" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="DerivationSchemeFormat"></label>
|
||||
<select asp-for="DerivationSchemeFormat" asp-items="Model.DerivationSchemeFormats" class="form-control"></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<span>BTCPay format memo</span>
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
@ -73,30 +70,39 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<button name="command" type="submit" class="btn btn-info">Continue</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
<div class="form-group">
|
||||
<h5>Confirm the addresses (@Model.CryptoCurrency)</h5>
|
||||
<span>Please check that your @Model.CryptoCurrency wallet is generating the same addresses as below.</span>
|
||||
</div>
|
||||
<input type="hidden" asp-for="CryptoCurrency" />
|
||||
<input type="hidden" asp-for="Confirmation" />
|
||||
<input type="hidden" asp-for="DerivationScheme" />
|
||||
<div class="form-group">
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th>Key path</th>
|
||||
<th>Address</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(var sample in Model.AddressSamples)
|
||||
{
|
||||
<tr>
|
||||
<th>Key path</th>
|
||||
<th>Address</th>
|
||||
<td>@sample.KeyPath</td>
|
||||
<td>@sample.Address</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(var sample in Model.AddressSamples)
|
||||
{
|
||||
<tr>
|
||||
<td>@sample.KeyPath</td>
|
||||
<td>@sample.Address</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<button name="command" type="submit" class="btn btn-success">Confirm</button>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
<button name="command" type="submit" class="btn btn-success" value="Save">Save</button>
|
||||
<button name="command" type="submit" class="btn btn-default" value="Check">Check ExtPubKey</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -38,6 +38,24 @@
|
||||
<label asp-for="NetworkFee"></label>
|
||||
<input asp-for="NetworkFee" type="checkbox" class="form-check" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="PreferredExchange"></label>
|
||||
<input asp-for="PreferredExchange" class="form-control" />
|
||||
<span asp-validation-for="PreferredExchange" class="text-danger"></span>
|
||||
<p id="PreferredExchangeHelpBlock" class="form-text text-muted">
|
||||
Current price source is <a href="@Model.RateSource" target="_blank">@Model.PreferredExchange</a>.<small> (using 1 minute cache)</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="RateMultiplier"></label>
|
||||
<input asp-for="RateMultiplier" class="form-control" />
|
||||
<span asp-validation-for="RateMultiplier" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="InvoiceExpiration"></label>
|
||||
<input asp-for="InvoiceExpiration" class="form-control" />
|
||||
<span asp-validation-for="InvoiceExpiration" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="MonitoringExpiration"></label>
|
||||
<input asp-for="MonitoringExpiration" class="form-control" />
|
||||
|
@ -43,7 +43,7 @@ h6 {
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
/*font-size: 16px;*/
|
||||
line-height: 1.5;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
@ -14,6 +14,12 @@
|
||||
*/
|
||||
|
||||
// TODO: Vue controller... complete migrate to it for binding, animations can stay in jQuery
|
||||
Vue.config.ignoredElements = [
|
||||
'line-items',
|
||||
'low-fee-timeline',
|
||||
// Ignoring custom HTML5 elements, eg: bp-spinner
|
||||
/^bp-/
|
||||
];
|
||||
var checkoutCtrl = new Vue({
|
||||
el: '#checkoutCtrl',
|
||||
components: {
|
||||
@ -22,7 +28,7 @@ var checkoutCtrl = new Vue({
|
||||
data: {
|
||||
srvModel: srvModel
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
var display = $(".timer-row__time-left"); // Timer container
|
||||
|
||||
@ -82,7 +88,7 @@ function emailForm() {
|
||||
$("#emailAddressForm").addClass("ng-touched ng-dirty ng-submitted ng-invalid");
|
||||
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/* =============== Even listeners =============== */
|
||||
@ -130,7 +136,7 @@ $("#copy-tab").click(function () {
|
||||
$(".payment-tabs__slider").addClass("slide-right");
|
||||
}
|
||||
|
||||
if (!($("#copy").is(".active"))) {
|
||||
if (!$("#copy").is(".active")) {
|
||||
$("#copy").show();
|
||||
$("#copy").addClass("active");
|
||||
|
||||
@ -278,7 +284,7 @@ function progressStart(timerMax) {
|
||||
|
||||
var now = new Date();
|
||||
var timeDiff = end.getTime() - now.getTime();
|
||||
var perc = 100 - Math.round((timeDiff / timerMax) * 100);
|
||||
var perc = 100 - Math.round(timeDiff / timerMax * 100);
|
||||
|
||||
if (perc === 75 && (status === "paidPartial" || status === "new")) {
|
||||
$(".timer-row").addClass("expiring-soon");
|
||||
|
File diff suppressed because it is too large
Load Diff
6
BTCPayServer/wwwroot/js/vue.min.js
vendored
Normal file
6
BTCPayServer/wwwroot/js/vue.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1579
BTCPayServer/wwwroot/vendor/animatecss/animate.css
vendored
Normal file
1579
BTCPayServer/wwwroot/vendor/animatecss/animate.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -9890,4 +9890,3 @@ a.text-dark:focus, a.text-dark:hover {
|
||||
.invisible {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap.css.map */
|
||||
|
@ -8593,4 +8593,3 @@ a.text-dark:focus, a.text-dark:hover {
|
||||
.invisible {
|
||||
visibility: hidden !important
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap.min.css.map */
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM microsoft/aspnetcore-build AS builder
|
||||
FROM microsoft/aspnetcore-build:2.0.5-2.1.4-stretch AS builder
|
||||
WORKDIR /source
|
||||
COPY BTCPayServer/BTCPayServer.csproj BTCPayServer.csproj
|
||||
# Cache some dependencies
|
||||
@ -6,7 +6,7 @@ RUN dotnet restore
|
||||
COPY BTCPayServer/. .
|
||||
RUN dotnet publish --output /app/ --configuration Release
|
||||
|
||||
FROM microsoft/aspnetcore:2.0.3
|
||||
FROM microsoft/aspnetcore:2.0.5-stretch
|
||||
WORKDIR /app
|
||||
|
||||
RUN mkdir /datadir
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 btcpayserver
|
||||
Copyright (c) 2017-2018 btcpayserver
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
10
README.md
10
README.md
@ -8,17 +8,17 @@
|
||||
|
||||
## Introduction
|
||||
|
||||
BTCPay Server is an Open Source payment processor conforms to the invoice API of Bitpay.
|
||||
BTCPay Server is an Open Source payment processor, written in C#, that conforms to the invoice API of [Bitpay](https://bitpay.com/).
|
||||
This allows easy migration of your code base to your own, self-hosted payment processor.
|
||||
|
||||
This solution is for you if:
|
||||
|
||||
* You currently use Bitpay as a payment processor but are worry about their commitment to Bitcoin in the future
|
||||
* You are currently using Bitpay as a payment processor but are worried about their commitment to Bitcoin in the future
|
||||
* You want to be in control of your own funds
|
||||
* Bitpay compliance team decided to reject your application
|
||||
* You want lower fee (we support Segwit)
|
||||
* You want to become a payment processor yourself and offer BTCPay hosted solution to merchants
|
||||
* You want to a way support other currency than those offered by Bitpay
|
||||
* You want lower fees (we support Segwit)
|
||||
* You want to become a payment processor yourself and offer a BTCPay hosted solution to merchants
|
||||
* You want a way to support currencies other than those offered by Bitpay
|
||||
|
||||
## Documentation
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
{ "sdk": { "version": "2.0.0" } }
|
Reference in New Issue
Block a user