mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Use callback to update invoice state instead of long polling
This commit is contained in:
@@ -64,16 +64,20 @@ namespace BTCPayServer.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
IWebHost _Host;
|
IWebHost _Host;
|
||||||
|
public int Port
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
if(!Directory.Exists(_Directory))
|
if(!Directory.Exists(_Directory))
|
||||||
Directory.CreateDirectory(_Directory);
|
Directory.CreateDirectory(_Directory);
|
||||||
|
|
||||||
HDPrivateKey = new ExtKey();
|
HDPrivateKey = new ExtKey();
|
||||||
var port = Utils.FreeTcpPort();
|
|
||||||
StringBuilder config = new StringBuilder();
|
StringBuilder config = new StringBuilder();
|
||||||
config.AppendLine($"regtest=1");
|
config.AppendLine($"regtest=1");
|
||||||
config.AppendLine($"port={port}");
|
config.AppendLine($"port={Port}");
|
||||||
config.AppendLine($"explorer.url={NBXplorerUri.AbsoluteUri}");
|
config.AppendLine($"explorer.url={NBXplorerUri.AbsoluteUri}");
|
||||||
config.AppendLine($"explorer.cookiefile={CookieFile}");
|
config.AppendLine($"explorer.cookiefile={CookieFile}");
|
||||||
config.AppendLine($"hdpubkey={HDPrivateKey.Neuter().ToString(Network.RegTest)}");
|
config.AppendLine($"hdpubkey={HDPrivateKey.Neuter().ToString(Network.RegTest)}");
|
||||||
@@ -81,7 +85,7 @@ namespace BTCPayServer.Tests
|
|||||||
config.AppendLine($"postgres=" + Postgres);
|
config.AppendLine($"postgres=" + Postgres);
|
||||||
File.WriteAllText(Path.Combine(_Directory, "settings.config"), config.ToString());
|
File.WriteAllText(Path.Combine(_Directory, "settings.config"), config.ToString());
|
||||||
|
|
||||||
ServerUri = new Uri("http://127.0.0.1:" + port + "/");
|
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 });
|
||||||
|
|
||||||
@@ -94,6 +98,7 @@ namespace BTCPayServer.Tests
|
|||||||
{
|
{
|
||||||
l.SetMinimumLevel(LogLevel.Information)
|
l.SetMinimumLevel(LogLevel.Information)
|
||||||
.AddFilter("Microsoft", LogLevel.Error)
|
.AddFilter("Microsoft", LogLevel.Error)
|
||||||
|
.AddFilter("Hangfire", LogLevel.Error)
|
||||||
.AddProvider(Logs.LogProvider);
|
.AddProvider(Logs.LogProvider);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -110,6 +115,11 @@ namespace BTCPayServer.Tests
|
|||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
public string HostName
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
internal set;
|
||||||
|
}
|
||||||
|
|
||||||
public T GetController<T>(string userId = null) where T : Controller
|
public T GetController<T>(string userId = null) where T : Controller
|
||||||
{
|
{
|
||||||
@@ -129,7 +139,8 @@ namespace BTCPayServer.Tests
|
|||||||
httpAccessor.HttpContext = context;
|
httpAccessor.HttpContext = context;
|
||||||
|
|
||||||
var controller = (T)ActivatorUtilities.CreateInstance(provider, typeof(T));
|
var controller = (T)ActivatorUtilities.CreateInstance(provider, typeof(T));
|
||||||
controller.Url = new UrlHelperMock();
|
|
||||||
|
controller.Url = new UrlHelperMock(new Uri($"http://{HostName}:{Port}/"));
|
||||||
controller.ControllerContext = new ControllerContext()
|
controller.ControllerContext = new ControllerContext()
|
||||||
{
|
{
|
||||||
HttpContext = context
|
HttpContext = context
|
||||||
|
|||||||
@@ -8,16 +8,21 @@ namespace BTCPayServer.Tests.Mocks
|
|||||||
{
|
{
|
||||||
public class UrlHelperMock : IUrlHelper
|
public class UrlHelperMock : IUrlHelper
|
||||||
{
|
{
|
||||||
|
Uri _BaseUrl;
|
||||||
|
public UrlHelperMock(Uri baseUrl)
|
||||||
|
{
|
||||||
|
_BaseUrl = baseUrl;
|
||||||
|
}
|
||||||
public ActionContext ActionContext => throw new NotImplementedException();
|
public ActionContext ActionContext => throw new NotImplementedException();
|
||||||
|
|
||||||
public string Action(UrlActionContext actionContext)
|
public string Action(UrlActionContext actionContext)
|
||||||
{
|
{
|
||||||
return "http://127.0.0.1/mock";
|
return $"{_BaseUrl}mock";
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Content(string contentPath)
|
public string Content(string contentPath)
|
||||||
{
|
{
|
||||||
return "http://127.0.0.1/mock";
|
return $"{_BaseUrl}{contentPath}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsLocalUrl(string url)
|
public bool IsLocalUrl(string url)
|
||||||
@@ -27,12 +32,12 @@ namespace BTCPayServer.Tests.Mocks
|
|||||||
|
|
||||||
public string Link(string routeName, object values)
|
public string Link(string routeName, object values)
|
||||||
{
|
{
|
||||||
return "http://127.0.0.1/mock";
|
return _BaseUrl.AbsoluteUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string RouteUrl(UrlRouteContext routeContext)
|
public string RouteUrl(UrlRouteContext routeContext)
|
||||||
{
|
{
|
||||||
return "http://127.0.0.1/mock";
|
return _BaseUrl.AbsoluteUri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
BTCPayServer.Tests/Properties/launchSettings.json
Normal file
7
BTCPayServer.Tests/Properties/launchSettings.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"BTCPayServer.Tests": {
|
||||||
|
"commandName": "Project"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,21 @@
|
|||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
using BTCPayServer.Models.AccountViewModels;
|
using BTCPayServer.Models.AccountViewModels;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using NBitcoin.RPC;
|
using NBitcoin.RPC;
|
||||||
using NBitpayClient;
|
using NBitpayClient;
|
||||||
using NBXplorer;
|
using NBXplorer;
|
||||||
|
using NBXplorer.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
@@ -28,6 +32,11 @@ namespace BTCPayServer.Tests
|
|||||||
_Directory = scope;
|
_Directory = scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool Dockerized
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
if(Directory.Exists(_Directory))
|
if(Directory.Exists(_Directory))
|
||||||
@@ -35,6 +44,8 @@ namespace BTCPayServer.Tests
|
|||||||
if(!Directory.Exists(_Directory))
|
if(!Directory.Exists(_Directory))
|
||||||
Directory.CreateDirectory(_Directory);
|
Directory.CreateDirectory(_Directory);
|
||||||
|
|
||||||
|
|
||||||
|
FakeCallback = bool.Parse(GetEnvironment("TESTS_FAKECALLBACK", "true"));
|
||||||
ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_RPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), Network);
|
ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_RPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), Network);
|
||||||
ExplorerClient = new ExplorerClient(Network, new Uri(GetEnvironment("TESTS_NBXPLORERURL", "http://127.0.0.1:32838/")));
|
ExplorerClient = new ExplorerClient(Network, new Uri(GetEnvironment("TESTS_NBXPLORERURL", "http://127.0.0.1:32838/")));
|
||||||
PayTester = new BTCPayServerTester(Path.Combine(_Directory, "pay"))
|
PayTester = new BTCPayServerTester(Path.Combine(_Directory, "pay"))
|
||||||
@@ -42,6 +53,8 @@ namespace BTCPayServer.Tests
|
|||||||
NBXplorerUri = ExplorerClient.Address,
|
NBXplorerUri = ExplorerClient.Address,
|
||||||
Postgres = GetEnvironment("TESTS_POSTGRES", "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver")
|
Postgres = GetEnvironment("TESTS_POSTGRES", "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver")
|
||||||
};
|
};
|
||||||
|
PayTester.Port = int.Parse(GetEnvironment("TESTS_PORT", Utils.FreeTcpPort().ToString()));
|
||||||
|
PayTester.HostName = GetEnvironment("TESTS_HOSTNAME", "127.0.0.1");
|
||||||
PayTester.Start();
|
PayTester.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +69,11 @@ namespace BTCPayServer.Tests
|
|||||||
return new TestAccount(this);
|
return new TestAccount(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool FakeCallback
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
public RPCClient ExplorerNode
|
public RPCClient ExplorerNode
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
@@ -66,13 +84,143 @@ namespace BTCPayServer.Tests
|
|||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HttpClient _Http = new HttpClient();
|
||||||
|
|
||||||
|
class MockHttpRequest : HttpRequest
|
||||||
|
{
|
||||||
|
Uri serverUri;
|
||||||
|
public MockHttpRequest(Uri serverUri)
|
||||||
|
{
|
||||||
|
this.serverUri = serverUri;
|
||||||
|
}
|
||||||
|
public override HttpContext HttpContext => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public override string Method
|
||||||
|
{
|
||||||
|
get => throw new NotImplementedException();
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
public override string Scheme
|
||||||
|
{
|
||||||
|
get => serverUri.Scheme;
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
public override bool IsHttps
|
||||||
|
{
|
||||||
|
get => throw new NotImplementedException();
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
public override HostString Host
|
||||||
|
{
|
||||||
|
get => new HostString(serverUri.Host, serverUri.Port);
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
public override PathString PathBase
|
||||||
|
{
|
||||||
|
get => "";
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
public override PathString Path
|
||||||
|
{
|
||||||
|
get => throw new NotImplementedException();
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
public override QueryString QueryString
|
||||||
|
{
|
||||||
|
get => throw new NotImplementedException();
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
public override IQueryCollection Query
|
||||||
|
{
|
||||||
|
get => throw new NotImplementedException();
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
public override string Protocol
|
||||||
|
{
|
||||||
|
get => throw new NotImplementedException();
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IHeaderDictionary Headers => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public override IRequestCookieCollection Cookies
|
||||||
|
{
|
||||||
|
get => throw new NotImplementedException();
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
public override long? ContentLength
|
||||||
|
{
|
||||||
|
get => throw new NotImplementedException();
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
public override string ContentType
|
||||||
|
{
|
||||||
|
get => throw new NotImplementedException();
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
public override Stream Body
|
||||||
|
{
|
||||||
|
get => throw new NotImplementedException();
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool HasFormContentType => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public override IFormCollection Form
|
||||||
|
{
|
||||||
|
get => throw new NotImplementedException();
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Simulating callback from NBXplorer. NBXplorer can't reach the host during tests as it is not running on localhost.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address"></param>
|
||||||
|
public void SimulateCallback(BitcoinAddress address = null)
|
||||||
|
{
|
||||||
|
if(!FakeCallback) //The callback of NBXplorer should work
|
||||||
|
return;
|
||||||
|
|
||||||
|
var req = new MockHttpRequest(PayTester.ServerUri);
|
||||||
|
var controller = PayTester.GetController<CallbackController>();
|
||||||
|
if(address != null)
|
||||||
|
{
|
||||||
|
|
||||||
|
var match = new TransactionMatch();
|
||||||
|
match.Outputs.Add(new KeyPathInformation() { ScriptPubKey = address.ScriptPubKey });
|
||||||
|
var content = new StringContent(new NBXplorer.Serializer(Network).ToString(match), new UTF8Encoding(false), "application/json");
|
||||||
|
var uri = controller.GetCallbackUriAsync(req).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
HttpRequestMessage message = new HttpRequestMessage();
|
||||||
|
message.Method = HttpMethod.Post;
|
||||||
|
message.RequestUri = uri;
|
||||||
|
message.Content = content;
|
||||||
|
|
||||||
|
_Http.SendAsync(message).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
var uri = controller.GetCallbackBlockUriAsync(req).GetAwaiter().GetResult();
|
||||||
|
HttpRequestMessage message = new HttpRequestMessage();
|
||||||
|
message.Method = HttpMethod.Post;
|
||||||
|
message.RequestUri = uri;
|
||||||
|
_Http.SendAsync(message).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public BTCPayServerTester PayTester
|
public BTCPayServerTester PayTester
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Network Network
|
public Network Network
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
Eventually(() =>
|
Eventually(() =>
|
||||||
{
|
{
|
||||||
|
tester.SimulateCallback(url.Address);
|
||||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||||
Assert.Equal("paid", localInvoice.Status);
|
Assert.Equal("paid", localInvoice.Status);
|
||||||
Assert.True(localInvoice.Refundable);
|
Assert.True(localInvoice.Refundable);
|
||||||
@@ -153,6 +154,8 @@ namespace BTCPayServer.Tests
|
|||||||
});
|
});
|
||||||
BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21);
|
BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21);
|
||||||
tester.ExplorerNode.SendToAddress(url.Address, url.Amount);
|
tester.ExplorerNode.SendToAddress(url.Address, url.Amount);
|
||||||
|
Thread.Sleep(5000);
|
||||||
|
tester.SimulateCallback(url.Address);
|
||||||
callbackServer.ProcessNextRequest((ctx) =>
|
callbackServer.ProcessNextRequest((ctx) =>
|
||||||
{
|
{
|
||||||
var ipn = new StreamReader(ctx.Request.Body).ReadToEnd();
|
var ipn = new StreamReader(ctx.Request.Body).ReadToEnd();
|
||||||
@@ -228,6 +231,7 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
Eventually(() =>
|
Eventually(() =>
|
||||||
{
|
{
|
||||||
|
tester.SimulateCallback(invoiceAddress);
|
||||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||||
Assert.Equal("paidPartial", localInvoice.Status);
|
Assert.Equal("paidPartial", localInvoice.Status);
|
||||||
Assert.Equal(firstPayment, localInvoice.BtcPaid);
|
Assert.Equal(firstPayment, localInvoice.BtcPaid);
|
||||||
@@ -240,6 +244,7 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
Eventually(() =>
|
Eventually(() =>
|
||||||
{
|
{
|
||||||
|
tester.SimulateCallback(invoiceAddress);
|
||||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||||
Assert.Equal("paid", localInvoice.Status);
|
Assert.Equal("paid", localInvoice.Status);
|
||||||
Assert.Equal(firstPayment + secondPayment, localInvoice.BtcPaid);
|
Assert.Equal(firstPayment + secondPayment, localInvoice.BtcPaid);
|
||||||
@@ -251,6 +256,7 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
Eventually(() =>
|
Eventually(() =>
|
||||||
{
|
{
|
||||||
|
tester.SimulateCallback();
|
||||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||||
Assert.Equal("confirmed", localInvoice.Status);
|
Assert.Equal("confirmed", localInvoice.Status);
|
||||||
});
|
});
|
||||||
@@ -259,6 +265,7 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
Eventually(() =>
|
Eventually(() =>
|
||||||
{
|
{
|
||||||
|
tester.SimulateCallback();
|
||||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||||
Assert.Equal("complete", localInvoice.Status);
|
Assert.Equal("complete", localInvoice.Status);
|
||||||
});
|
});
|
||||||
@@ -280,6 +287,7 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
Eventually(() =>
|
Eventually(() =>
|
||||||
{
|
{
|
||||||
|
tester.SimulateCallback(invoiceAddress);
|
||||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||||
Assert.Equal("paidOver", localInvoice.Status);
|
Assert.Equal("paidOver", localInvoice.Status);
|
||||||
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
||||||
@@ -290,6 +298,7 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
Eventually(() =>
|
Eventually(() =>
|
||||||
{
|
{
|
||||||
|
tester.SimulateCallback();
|
||||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||||
Assert.Equal("confirmed", localInvoice.Status);
|
Assert.Equal("confirmed", localInvoice.Status);
|
||||||
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
||||||
|
|||||||
@@ -10,11 +10,18 @@ services:
|
|||||||
TESTS_RPCCONNECTION: server=http://bitcoind:43782;ceiwHEbqWI83:DwubwWsoo3
|
TESTS_RPCCONNECTION: server=http://bitcoind:43782;ceiwHEbqWI83:DwubwWsoo3
|
||||||
TESTS_NBXPLORERURL: http://nbxplorer:32838/
|
TESTS_NBXPLORERURL: http://nbxplorer:32838/
|
||||||
TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver
|
TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver
|
||||||
|
TESTS_FAKECALLBACK: 'true'
|
||||||
|
TESTS_PORT: 80
|
||||||
|
TESTS_HOSTNAME: tests
|
||||||
|
expose:
|
||||||
|
- "80"
|
||||||
links:
|
links:
|
||||||
- nbxplorer
|
- nbxplorer
|
||||||
|
extra_hosts:
|
||||||
|
- "tests:127.0.0.1"
|
||||||
|
|
||||||
nbxplorer:
|
nbxplorer:
|
||||||
image: nicolasdorier/nbxplorer:1.0.0.16
|
image: nicolasdorier/nbxplorer:1.0.0.18
|
||||||
ports:
|
ports:
|
||||||
- "32838:32838"
|
- "32838:32838"
|
||||||
expose:
|
expose:
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<PackageReference Include="NBitcoin" Version="4.0.0.38" />
|
<PackageReference Include="NBitcoin" Version="4.0.0.38" />
|
||||||
<PackageReference Include="NBitpayClient" Version="1.0.0.10" />
|
<PackageReference Include="NBitpayClient" Version="1.0.0.10" />
|
||||||
<PackageReference Include="DBreeze" Version="1.87.0" />
|
<PackageReference Include="DBreeze" Version="1.87.0" />
|
||||||
<PackageReference Include="NBXplorer.Client" Version="1.0.0.16" />
|
<PackageReference Include="NBXplorer.Client" Version="1.0.0.17" />
|
||||||
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.1" />
|
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.1" />
|
||||||
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.2" />
|
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.2" />
|
||||||
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.13" />
|
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.13" />
|
||||||
|
|||||||
116
BTCPayServer/Controllers/CallbackController.cs
Normal file
116
BTCPayServer/Controllers/CallbackController.cs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
using BTCPayServer.Logging;
|
||||||
|
using BTCPayServer.Servcices.Invoices;
|
||||||
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Wallets;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NBitcoin;
|
||||||
|
using NBXplorer;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NBXplorer.DerivationStrategy;
|
||||||
|
using NBXplorer.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Controllers
|
||||||
|
{
|
||||||
|
public class CallbackController : Controller
|
||||||
|
{
|
||||||
|
public class CallbackSettings
|
||||||
|
{
|
||||||
|
public string Token
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SettingsRepository _Settings;
|
||||||
|
Network _Network;
|
||||||
|
InvoiceWatcher _Watcher;
|
||||||
|
ExplorerClient _Explorer;
|
||||||
|
|
||||||
|
public CallbackController(SettingsRepository repo,
|
||||||
|
ExplorerClient explorer,
|
||||||
|
InvoiceWatcher watcher,
|
||||||
|
Network network)
|
||||||
|
{
|
||||||
|
_Settings = repo;
|
||||||
|
_Network = network;
|
||||||
|
_Watcher = watcher;
|
||||||
|
_Explorer = explorer;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("callbacks/transactions")]
|
||||||
|
[HttpPost]
|
||||||
|
public async Task NewTransaction(string token)
|
||||||
|
{
|
||||||
|
await AssertToken(token);
|
||||||
|
Logs.PayServer.LogInformation("New transaction callback");
|
||||||
|
//We don't want to register all the json converter at MVC level, so we parse here
|
||||||
|
var serializer = new NBXplorer.Serializer(_Network);
|
||||||
|
var content = await new StreamReader(Request.Body, new UTF8Encoding(false), false, 1024, true).ReadToEndAsync();
|
||||||
|
var match = serializer.ToObject<TransactionMatch>(content);
|
||||||
|
|
||||||
|
foreach(var output in match.Outputs)
|
||||||
|
{
|
||||||
|
await _Watcher.NotifyReceived(output.ScriptPubKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("callbacks/blocks")]
|
||||||
|
[HttpPost]
|
||||||
|
public async Task NewBlock(string token)
|
||||||
|
{
|
||||||
|
await AssertToken(token);
|
||||||
|
Logs.PayServer.LogInformation("New block callback");
|
||||||
|
await _Watcher.NotifyBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AssertToken(string token)
|
||||||
|
{
|
||||||
|
var callback = await _Settings.GetSettingAsync<CallbackSettings>();
|
||||||
|
if(await GetToken() != token)
|
||||||
|
throw new BTCPayServer.BitpayHttpException(400, "invalid-callback-token");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Uri> GetCallbackUriAsync(HttpRequest request)
|
||||||
|
{
|
||||||
|
string token = await GetToken();
|
||||||
|
return new Uri(request.GetAbsoluteRoot() + "/callbacks/transactions?token=" + token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RegisterCallbackUriAsync(DerivationStrategyBase derivationScheme, HttpRequest request)
|
||||||
|
{
|
||||||
|
var uri = await GetCallbackUriAsync(request);
|
||||||
|
await _Explorer.SubscribeToWalletAsync(uri, derivationScheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> GetToken()
|
||||||
|
{
|
||||||
|
var callback = await _Settings.GetSettingAsync<CallbackSettings>();
|
||||||
|
if(callback == null)
|
||||||
|
{
|
||||||
|
callback = new CallbackSettings() { Token = Guid.NewGuid().ToString() };
|
||||||
|
await _Settings.UpdateSetting(callback);
|
||||||
|
}
|
||||||
|
var token = callback.Token;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Uri> GetCallbackBlockUriAsync(HttpRequest request)
|
||||||
|
{
|
||||||
|
string token = await GetToken();
|
||||||
|
return new Uri(request.GetAbsoluteRoot() + "/callbacks/blocks?token=" + token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Uri> RegisterCallbackBlockUriAsync(HttpRequest request)
|
||||||
|
{
|
||||||
|
var uri = await GetCallbackBlockUriAsync(request);
|
||||||
|
await _Explorer.SubscribeToBlocksAsync(uri);
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@ using BTCPayServer.Validations;
|
|||||||
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
using NBXplorer.DerivationStrategy;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
@@ -98,7 +99,7 @@ namespace BTCPayServer.Controllers
|
|||||||
entity.TxFee = (await _FeeProvider.GetFeeRateAsync()).GetFee(100); // assume price for 100 bytes
|
entity.TxFee = (await _FeeProvider.GetFeeRateAsync()).GetFee(100); // assume price for 100 bytes
|
||||||
entity.Rate = (double)await _RateProvider.GetRateAsync(invoice.Currency);
|
entity.Rate = (double)await _RateProvider.GetRateAsync(invoice.Currency);
|
||||||
entity.PosData = invoice.PosData;
|
entity.PosData = invoice.PosData;
|
||||||
entity.DepositAddress = await _Wallet.ReserveAddressAsync(derivationStrategy);
|
entity.DepositAddress = await _Wallet.ReserveAddressAsync(ParseDerivationStrategy(derivationStrategy));
|
||||||
|
|
||||||
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity);
|
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity);
|
||||||
await _Wallet.MapAsync(entity.DepositAddress.ScriptPubKey, entity.Id);
|
await _Wallet.MapAsync(entity.DepositAddress.ScriptPubKey, entity.Id);
|
||||||
@@ -107,6 +108,11 @@ namespace BTCPayServer.Controllers
|
|||||||
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
|
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DerivationStrategyBase ParseDerivationStrategy(string derivationStrategy)
|
||||||
|
{
|
||||||
|
return new DerivationStrategyFactory(_Network).Parse(derivationStrategy);
|
||||||
|
}
|
||||||
|
|
||||||
private TDest Map<TFrom, TDest>(TFrom data)
|
private TDest Map<TFrom, TDest>(TFrom data)
|
||||||
{
|
{
|
||||||
return JsonConvert.DeserializeObject<TDest>(JsonConvert.SerializeObject(data));
|
return JsonConvert.DeserializeObject<TDest>(JsonConvert.SerializeObject(data));
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ namespace BTCPayServer.Controllers
|
|||||||
public StoresController(
|
public StoresController(
|
||||||
StoreRepository repo,
|
StoreRepository repo,
|
||||||
TokenRepository tokenRepo,
|
TokenRepository tokenRepo,
|
||||||
|
CallbackController callbackController,
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
AccessTokenController tokenController,
|
AccessTokenController tokenController,
|
||||||
BTCPayWallet wallet,
|
BTCPayWallet wallet,
|
||||||
@@ -39,8 +40,10 @@ namespace BTCPayServer.Controllers
|
|||||||
_Wallet = wallet;
|
_Wallet = wallet;
|
||||||
_Env = env;
|
_Env = env;
|
||||||
_Network = network;
|
_Network = network;
|
||||||
|
_CallbackController = callbackController;
|
||||||
}
|
}
|
||||||
Network _Network;
|
Network _Network;
|
||||||
|
CallbackController _CallbackController;
|
||||||
BTCPayWallet _Wallet;
|
BTCPayWallet _Wallet;
|
||||||
AccessTokenController _TokenController;
|
AccessTokenController _TokenController;
|
||||||
StoreRepository _Repo;
|
StoreRepository _Repo;
|
||||||
@@ -86,7 +89,7 @@ namespace BTCPayServer.Controllers
|
|||||||
StoresViewModel result = new StoresViewModel();
|
StoresViewModel result = new StoresViewModel();
|
||||||
result.StatusMessage = StatusMessage;
|
result.StatusMessage = StatusMessage;
|
||||||
var stores = await _Repo.GetStoresByUserId(GetUserId());
|
var stores = await _Repo.GetStoresByUserId(GetUserId());
|
||||||
var balances = stores.Select(async s => string.IsNullOrEmpty(s.DerivationStrategy) ? Money.Zero : await _Wallet.GetBalance(s.DerivationStrategy)).ToArray();
|
var balances = stores.Select(async s => string.IsNullOrEmpty(s.DerivationStrategy) ? Money.Zero : await _Wallet.GetBalance(ParseDerivationStrategy(s.DerivationStrategy))).ToArray();
|
||||||
|
|
||||||
for(int i = 0; i < stores.Length; i++)
|
for(int i = 0; i < stores.Length; i++)
|
||||||
{
|
{
|
||||||
@@ -156,7 +159,9 @@ namespace BTCPayServer.Controllers
|
|||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _Wallet.TrackAsync(model.DerivationScheme);
|
var strategy = ParseDerivationStrategy(model.DerivationScheme);
|
||||||
|
await _Wallet.TrackAsync(strategy);
|
||||||
|
await _CallbackController.RegisterCallbackUriAsync(strategy, Request);
|
||||||
store.DerivationStrategy = model.DerivationScheme;
|
store.DerivationStrategy = model.DerivationScheme;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -192,6 +197,11 @@ namespace BTCPayServer.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DerivationStrategyBase ParseDerivationStrategy(string derivationScheme)
|
||||||
|
{
|
||||||
|
return new DerivationStrategyFactory(_Network).Parse(derivationScheme);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("{storeId}/Tokens")]
|
[Route("{storeId}/Tokens")]
|
||||||
public async Task<IActionResult> ListTokens(string storeId)
|
public async Task<IActionResult> ListTokens(string storeId)
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ namespace BTCPayServer.Data
|
|||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DbSet<PendingInvoiceData> PendingInvoices
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
public DbSet<RefundAddressesData> RefundAddresses
|
public DbSet<RefundAddressesData> RefundAddresses
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
|
|||||||
15
BTCPayServer/Data/PendingInvoiceData.cs
Normal file
15
BTCPayServer/Data/PendingInvoiceData.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Data
|
||||||
|
{
|
||||||
|
public class PendingInvoiceData
|
||||||
|
{
|
||||||
|
public string Id
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -140,6 +140,7 @@ namespace BTCPayServer.Hosting
|
|||||||
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
|
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
|
||||||
services.TryAddSingleton<IAuthorizationHandler, OwnStoreHandler>();
|
services.TryAddSingleton<IAuthorizationHandler, OwnStoreHandler>();
|
||||||
services.AddTransient<AccessTokenController>();
|
services.AddTransient<AccessTokenController>();
|
||||||
|
services.AddTransient<CallbackController>();
|
||||||
// Add application services.
|
// Add application services.
|
||||||
services.AddTransient<IEmailSender, EmailSender>();
|
services.AddTransient<IEmailSender, EmailSender>();
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ using BTCPayServer.Configuration;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.AspNetCore.Http.Extensions;
|
using Microsoft.AspNetCore.Http.Extensions;
|
||||||
|
using BTCPayServer.Controllers;
|
||||||
|
|
||||||
namespace BTCPayServer.Hosting
|
namespace BTCPayServer.Hosting
|
||||||
{
|
{
|
||||||
@@ -28,14 +29,27 @@ namespace BTCPayServer.Hosting
|
|||||||
{
|
{
|
||||||
TokenRepository _TokenRepository;
|
TokenRepository _TokenRepository;
|
||||||
RequestDelegate _Next;
|
RequestDelegate _Next;
|
||||||
public BTCPayMiddleware(RequestDelegate next, TokenRepository tokenRepo)
|
CallbackController _CallbackController;
|
||||||
|
public BTCPayMiddleware(RequestDelegate next,
|
||||||
|
TokenRepository tokenRepo,
|
||||||
|
CallbackController callbackController)
|
||||||
{
|
{
|
||||||
_TokenRepository = tokenRepo ?? throw new ArgumentNullException(nameof(tokenRepo));
|
_TokenRepository = tokenRepo ?? throw new ArgumentNullException(nameof(tokenRepo));
|
||||||
_Next = next ?? throw new ArgumentNullException(nameof(next));
|
_Next = next ?? throw new ArgumentNullException(nameof(next));
|
||||||
|
_CallbackController = callbackController;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool _Registered;
|
||||||
public async Task Invoke(HttpContext httpContext)
|
public async Task Invoke(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
|
if(!_Registered)
|
||||||
|
{
|
||||||
|
var callback = await _CallbackController.RegisterCallbackBlockUriAsync(httpContext.Request);
|
||||||
|
Logs.PayServer.LogInformation($"Registering block callback to " + callback);
|
||||||
|
_Registered = true;
|
||||||
|
}
|
||||||
|
|
||||||
httpContext.Request.Headers.TryGetValue("x-signature", out StringValues values);
|
httpContext.Request.Headers.TryGetValue("x-signature", out StringValues values);
|
||||||
var sig = values.FirstOrDefault();
|
var sig = values.FirstOrDefault();
|
||||||
httpContext.Request.Headers.TryGetValue("x-identity", out values);
|
httpContext.Request.Headers.TryGetValue("x-identity", out values);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ using Hangfire.Dashboard;
|
|||||||
using Hangfire.Annotations;
|
using Hangfire.Annotations;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace BTCPayServer.Hosting
|
namespace BTCPayServer.Hosting
|
||||||
{
|
{
|
||||||
@@ -59,6 +60,7 @@ namespace BTCPayServer.Hosting
|
|||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.ConfigureBTCPayServer(Configuration);
|
services.ConfigureBTCPayServer(Configuration);
|
||||||
|
|||||||
450
BTCPayServer/Migrations/20171012020112_PendingInvoices.Designer.cs
generated
Normal file
450
BTCPayServer/Migrations/20171012020112_PendingInvoices.Designer.cs
generated
Normal file
@@ -0,0 +1,450 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Servcices.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("20171012020112_PendingInvoices")]
|
||||||
|
partial class PendingInvoices
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
|
||||||
|
|
||||||
|
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Address")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<string>("InvoiceDataId");
|
||||||
|
|
||||||
|
b.HasKey("Address");
|
||||||
|
|
||||||
|
b.HasIndex("InvoiceDataId");
|
||||||
|
|
||||||
|
b.ToTable("AddressInvoices");
|
||||||
|
});
|
||||||
|
|
||||||
|
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.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<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>("DerivationStrategy");
|
||||||
|
|
||||||
|
b.Property<int>("SpeedPolicy");
|
||||||
|
|
||||||
|
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()
|
||||||
|
.HasForeignKey("InvoiceDataId");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("StoreDataId");
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
BTCPayServer/Migrations/20171012020112_PendingInvoices.cs
Normal file
47
BTCPayServer/Migrations/20171012020112_PendingInvoices.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Migrations
|
||||||
|
{
|
||||||
|
public partial class PendingInvoices : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Name",
|
||||||
|
table: "PairingCodes");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Name",
|
||||||
|
table: "PairedSINData");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PendingInvoices",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<string>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PendingInvoices", x => x.Id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PendingInvoices");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Name",
|
||||||
|
table: "PairingCodes",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Name",
|
||||||
|
table: "PairedSINData",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -71,8 +71,6 @@ namespace BTCPayServer.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Label");
|
b.Property<string>("Label");
|
||||||
|
|
||||||
b.Property<string>("Name");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("PairingTime");
|
b.Property<DateTimeOffset>("PairingTime");
|
||||||
|
|
||||||
b.Property<string>("SIN");
|
b.Property<string>("SIN");
|
||||||
@@ -101,8 +99,6 @@ namespace BTCPayServer.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Label");
|
b.Property<string>("Label");
|
||||||
|
|
||||||
b.Property<string>("Name");
|
|
||||||
|
|
||||||
b.Property<string>("SIN");
|
b.Property<string>("SIN");
|
||||||
|
|
||||||
b.Property<string>("StoreDataId");
|
b.Property<string>("StoreDataId");
|
||||||
@@ -130,6 +126,16 @@ namespace BTCPayServer.Migrations
|
|||||||
b.ToTable("Payments");
|
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 =>
|
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Id")
|
b.Property<string>("Id")
|
||||||
|
|||||||
@@ -56,38 +56,31 @@ namespace BTCPayServer.Servcices.Invoices
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public Task AddPendingInvoice(string invoiceId)
|
public async Task AddPendingInvoice(string invoiceId)
|
||||||
{
|
{
|
||||||
using(var tx = _Engine.GetTransaction())
|
using(var ctx = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
tx.Insert<string, byte[]>("T-Pending", invoiceId, new byte[0]);
|
ctx.PendingInvoices.Add(new PendingInvoiceData() { Id = invoiceId });
|
||||||
tx.Commit();
|
await ctx.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task RemovePendingInvoice(string invoiceId)
|
public async Task RemovePendingInvoice(string invoiceId)
|
||||||
{
|
{
|
||||||
using(var tx = _Engine.GetTransaction())
|
using(var ctx = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
tx.RemoveKey("T-Pending", invoiceId);
|
ctx.PendingInvoices.Remove(new PendingInvoiceData() { Id = invoiceId });
|
||||||
tx.Commit();
|
await ctx.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public string[] GetPendingInvoices()
|
public async Task<string[]> GetPendingInvoices()
|
||||||
{
|
{
|
||||||
List<string> pending = new List<string>();
|
using(var ctx = _ContextFactory.CreateContext())
|
||||||
using(var tx = _Engine.GetTransaction())
|
|
||||||
{
|
{
|
||||||
foreach(var row in tx.SelectForward<string, byte[]>("T-Pending"))
|
return await ctx.PendingInvoices.Select(p => p.Id).ToArrayAsync();
|
||||||
{
|
|
||||||
pending.Add(row.Key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return pending.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateInvoice(string invoiceId, UpdateCustomerModel data)
|
public async Task UpdateInvoice(string invoiceId, UpdateCustomerModel data)
|
||||||
|
|||||||
@@ -36,9 +36,23 @@ namespace BTCPayServer.Servcices.Invoices
|
|||||||
_NotificationManager = notificationManager ?? throw new ArgumentNullException(nameof(notificationManager));
|
_NotificationManager = notificationManager ?? throw new ArgumentNullException(nameof(notificationManager));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StartWatchInvoice(string invoiceId)
|
public async Task NotifyReceived(Script scriptPubKey)
|
||||||
{
|
{
|
||||||
Logs.PayServer.LogInformation("Watching invoice " + invoiceId);
|
var invoice = await _Wallet.GetInvoiceId(scriptPubKey);
|
||||||
|
_WatchRequests.Add(invoice);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task NotifyBlock()
|
||||||
|
{
|
||||||
|
foreach(var invoice in await _InvoiceRepository.GetPendingInvoices())
|
||||||
|
{
|
||||||
|
_WatchRequests.Add(invoice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateInvoice(string invoiceId)
|
||||||
|
{
|
||||||
|
Logs.PayServer.LogInformation("Updating invoice " + invoiceId);
|
||||||
UTXOChanges changes = null;
|
UTXOChanges changes = null;
|
||||||
while(true)
|
while(true)
|
||||||
{
|
{
|
||||||
@@ -53,7 +67,8 @@ namespace BTCPayServer.Servcices.Invoices
|
|||||||
if(result.NeedSave)
|
if(result.NeedSave)
|
||||||
await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.Status, invoice.ExceptionStatus).ConfigureAwait(false);
|
await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.Status, invoice.ExceptionStatus).ConfigureAwait(false);
|
||||||
|
|
||||||
if(stateBefore != invoice.Status)
|
var changed = stateBefore != invoice.Status;
|
||||||
|
if(changed)
|
||||||
{
|
{
|
||||||
Logs.PayServer.LogInformation($"Invoice {invoice.Id}: {stateBefore} => {invoice.Status}");
|
Logs.PayServer.LogInformation($"Invoice {invoice.Id}: {stateBefore} => {invoice.Status}");
|
||||||
}
|
}
|
||||||
@@ -64,6 +79,9 @@ namespace BTCPayServer.Servcices.Invoices
|
|||||||
Logs.PayServer.LogInformation("Stopped watching invoice " + invoiceId);
|
Logs.PayServer.LogInformation("Stopped watching invoice " + invoiceId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!changed || _Cts.Token.IsCancellationRequested)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
catch(OperationCanceledException) when(_Cts.Token.IsCancellationRequested)
|
catch(OperationCanceledException) when(_Cts.Token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -96,7 +114,7 @@ namespace BTCPayServer.Servcices.Invoices
|
|||||||
if(invoice.Status == "new" || invoice.Status == "paidPartial")
|
if(invoice.Status == "new" || invoice.Status == "paidPartial")
|
||||||
{
|
{
|
||||||
var strategy = _DerivationFactory.Parse(invoice.DerivationStrategy);
|
var strategy = _DerivationFactory.Parse(invoice.DerivationStrategy);
|
||||||
changes = await _ExplorerClient.SyncAsync(strategy, changes, false, _Cts.Token).ConfigureAwait(false);
|
changes = await _ExplorerClient.SyncAsync(strategy, changes, true, _Cts.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
var utxos = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).ToArray();
|
var utxos = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).ToArray();
|
||||||
var invoiceIds = utxos.Select(u => _Wallet.GetInvoiceId(u.Output.ScriptPubKey)).ToArray();
|
var invoiceIds = utxos.Select(u => _Wallet.GetInvoiceId(u.Output.ScriptPubKey)).ToArray();
|
||||||
@@ -232,34 +250,39 @@ namespace BTCPayServer.Servcices.Invoices
|
|||||||
Thread _Thread;
|
Thread _Thread;
|
||||||
TaskCompletionSource<bool> _RunningTask;
|
TaskCompletionSource<bool> _RunningTask;
|
||||||
CancellationTokenSource _Cts;
|
CancellationTokenSource _Cts;
|
||||||
|
Timer _UpdatePendingInvoices;
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
foreach(var pending in _InvoiceRepository.GetPendingInvoices())
|
|
||||||
{
|
|
||||||
_WatchRequests.Add(pending);
|
|
||||||
}
|
|
||||||
_RunningTask = new TaskCompletionSource<bool>();
|
_RunningTask = new TaskCompletionSource<bool>();
|
||||||
_Cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
_Cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
_Thread = new Thread(Run) { Name = "InvoiceWatcher" };
|
_Thread = new Thread(Run) { Name = "InvoiceWatcher" };
|
||||||
_Thread.Start();
|
_Thread.Start();
|
||||||
|
_UpdatePendingInvoices = new Timer(async s =>
|
||||||
|
{
|
||||||
|
foreach(var pending in await _InvoiceRepository.GetPendingInvoices())
|
||||||
|
{
|
||||||
|
_WatchRequests.Add(pending);
|
||||||
|
}
|
||||||
|
}, null, 0, (int)TimeSpan.FromMinutes(1.0).TotalMilliseconds);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Run()
|
void Run()
|
||||||
{
|
{
|
||||||
Logs.PayServer.LogInformation("Start watching invoices");
|
Logs.PayServer.LogInformation("Start watching invoices");
|
||||||
List<Task> watching = new List<Task>();
|
ConcurrentDictionary<string, Lazy<Task>> updating = new ConcurrentDictionary<string, Lazy<Task>>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach(var item in _WatchRequests.GetConsumingEnumerable(_Cts.Token))
|
foreach(var item in _WatchRequests.GetConsumingEnumerable(_Cts.Token))
|
||||||
{
|
{
|
||||||
watching.Add(StartWatchInvoice(item));
|
var localItem = item;
|
||||||
foreach(var task in watching.ToList())
|
|
||||||
|
// If the invoice is already updating, ignore
|
||||||
|
Lazy<Task> updateInvoice =new Lazy<Task>(() => UpdateInvoice(localItem), false);
|
||||||
|
if(updating.TryAdd(item, updateInvoice))
|
||||||
{
|
{
|
||||||
if(task.Status != TaskStatus.Running)
|
updateInvoice.Value.ContinueWith(i => updating.TryRemove(item, out updateInvoice));
|
||||||
{
|
|
||||||
watching.Remove(task);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,7 +290,7 @@ namespace BTCPayServer.Servcices.Invoices
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Task.WaitAll(watching.ToArray());
|
Task.WaitAll(updating.Select(c => c.Value.Value).ToArray());
|
||||||
}
|
}
|
||||||
catch(AggregateException) { }
|
catch(AggregateException) { }
|
||||||
_RunningTask.TrySetResult(true);
|
_RunningTask.TrySetResult(true);
|
||||||
@@ -287,6 +310,7 @@ namespace BTCPayServer.Servcices.Invoices
|
|||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
_UpdatePendingInvoices.Dispose();
|
||||||
_Cts.Cancel();
|
_Cts.Cancel();
|
||||||
return Task.WhenAny(_RunningTask.Task, Task.Delay(-1, cancellationToken));
|
return Task.WhenAny(_RunningTask.Task, Task.Delay(-1, cancellationToken));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ namespace BTCPayServer.Services.Wallets
|
|||||||
{
|
{
|
||||||
private ExplorerClient _Client;
|
private ExplorerClient _Client;
|
||||||
private Serializer _Serializer;
|
private Serializer _Serializer;
|
||||||
private DerivationStrategyFactory _DerivationStrategyFactory;
|
|
||||||
ApplicationDbContextFactory _DBFactory;
|
ApplicationDbContextFactory _DBFactory;
|
||||||
|
|
||||||
public BTCPayWallet(ExplorerClient client, ApplicationDbContextFactory factory)
|
public BTCPayWallet(ExplorerClient client, ApplicationDbContextFactory factory)
|
||||||
@@ -26,19 +25,18 @@ namespace BTCPayServer.Services.Wallets
|
|||||||
_Client = client;
|
_Client = client;
|
||||||
_DBFactory = factory;
|
_DBFactory = factory;
|
||||||
_Serializer = new NBXplorer.Serializer(_Client.Network);
|
_Serializer = new NBXplorer.Serializer(_Client.Network);
|
||||||
_DerivationStrategyFactory = new DerivationStrategyFactory(_Client.Network);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<BitcoinAddress> ReserveAddressAsync(string walletIdentifier)
|
public async Task<BitcoinAddress> ReserveAddressAsync(DerivationStrategyBase derivationStrategy)
|
||||||
{
|
{
|
||||||
var pathInfo = await _Client.GetUnusedAsync(_DerivationStrategyFactory.Parse(walletIdentifier), DerivationFeature.Deposit, 0, true).ConfigureAwait(false);
|
var pathInfo = await _Client.GetUnusedAsync(derivationStrategy, DerivationFeature.Deposit, 0, true).ConfigureAwait(false);
|
||||||
return pathInfo.ScriptPubKey.GetDestinationAddress(_DerivationStrategyFactory.Network);
|
return pathInfo.ScriptPubKey.GetDestinationAddress(_Client.Network);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task TrackAsync(string walletIdentifier)
|
public async Task TrackAsync(DerivationStrategyBase derivationStrategy)
|
||||||
{
|
{
|
||||||
return _Client.TrackAsync(_DerivationStrategyFactory.Parse(walletIdentifier));
|
await _Client.TrackAsync(derivationStrategy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetInvoiceId(Script scriptPubKey)
|
public async Task<string> GetInvoiceId(Script scriptPubKey)
|
||||||
@@ -74,9 +72,9 @@ namespace BTCPayServer.Services.Wallets
|
|||||||
return Task.WhenAll(tasks);
|
return Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Money> GetBalance(string derivationStrategy)
|
public async Task<Money> GetBalance(DerivationStrategyBase derivationStrategy)
|
||||||
{
|
{
|
||||||
var result = await _Client.SyncAsync(_DerivationStrategyFactory.Parse(derivationStrategy), null, true);
|
var result = await _Client.SyncAsync(derivationStrategy, null, true);
|
||||||
return result.Confirmed.UTXOs.Select(u => u.Output.Value)
|
return result.Confirmed.UTXOs.Select(u => u.Output.Value)
|
||||||
.Concat(result.Unconfirmed.UTXOs.Select(u => u.Output.Value))
|
.Concat(result.Unconfirmed.UTXOs.Select(u => u.Output.Value))
|
||||||
.Sum();
|
.Sum();
|
||||||
|
|||||||
Reference in New Issue
Block a user